From 20eba82751e27fc39462152110ec82ed0a3da443 Mon Sep 17 00:00:00 2001 From: Domino Valdano Date: Tue, 12 Jun 2018 19:34:10 -0700 Subject: [PATCH] eth/filters,interfaces.go: Expand & modify TxFilterQuery --- eth/filters/api.go | 39 ++++++++++++++++++++++------ eth/filters/filter.go | 43 ++++++++++++++++--------------- eth/filters/filter_system.go | 6 ++--- eth/filters/filter_system_test.go | 38 ++++++++++++++++----------- interfaces.go | 5 ++-- 5 files changed, 82 insertions(+), 49 deletions(-) diff --git a/eth/filters/api.go b/eth/filters/api.go index 8daa06c921..fcca632bb2 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -234,7 +234,7 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er func (api *PublicFilterAPI) NewReturnDataFilter(crit TxFilterCriteria) rpc.ID { var ( retCh = make(chan []*types.ReturnData) - retSub = api.events.SubscribeReturnData(retCh, ethereum.TxFilterQuery(crit)) + retSub = api.events.SubscribeReturnData(retCh, crit) ) api.filtersMu.Lock() @@ -280,7 +280,7 @@ func (api *PublicFilterAPI) ReturnData(ctx context.Context, crit TxFilterCriteri go func() { retCh := make(chan []*types.ReturnData) - retSub := api.events.SubscribeReturnData(retCh, ethereum.TxFilterQuery(crit)) + retSub := api.events.SubscribeReturnData(retCh, crit) for { select { @@ -623,10 +623,16 @@ func (args *FilterCriteria) UnmarshalJSON(data []byte) error { // UnmarshalJSON sets *args fields with given data. func (args *TxFilterCriteria) UnmarshalJSON(data []byte) error { - var err error + var ( + err error + from []common.Address + to []common.Address + ) + type input struct { - From interface{} `json:"from"` - To interface{} `json:"to"` + From interface{} `json:"from"` + To interface{} `json:"to"` + HasReturnData interface{} `json:"hasReturnData"` } var raw input @@ -638,17 +644,34 @@ func (args *TxFilterCriteria) UnmarshalJSON(data []byte) error { args.To = nil if raw.From != nil { - args.From, err = decodeAddresses(raw.From, "from") + args.From = make(map[common.Address]struct{}) + from, err = decodeAddresses(raw.From, "from") if err != nil { return err } + for _, addr := range from { + args.From[addr] = struct{}{} + } } if raw.To != nil { - args.To, err = decodeAddresses(raw.To, "to") + args.To = make(map[common.Address]struct{}) + to, err = decodeAddresses(raw.To, "to") if err != nil { return err } + for _, addr := range to { + args.To[addr] = struct{}{} + } + } + + if raw.HasReturnData != nil { + switch hasReturnData := raw.HasReturnData.(type) { + case bool: + args.HasReturnData = hasReturnData + default: + return fmt.Errorf("Invalid HasReturnData field specified--must be true or false") + } } return nil @@ -656,7 +679,7 @@ func (args *TxFilterCriteria) UnmarshalJSON(data []byte) error { // Decodes address field from JSON, which could be either // a single common.Address or an array []common.Address. -// second arg is the name of the field being decoded, for error reporting. +// Second arg is the name of the field being decoded, for error reporting. func decodeAddresses(addresses interface{}, field string) ([]common.Address, error) { decoded := []common.Address{} diff --git a/eth/filters/filter.go b/eth/filters/filter.go index 0f72af34b8..ddd11f1d8c 100644 --- a/eth/filters/filter.go +++ b/eth/filters/filter.go @@ -276,34 +276,35 @@ Logs: return ret } -func filterTxs(txEvents []*core.TransactionEvent, from []common.Address, to []common.Address) (retData []*types.ReturnData) { +func filterTxs(txEvents []*core.TransactionEvent, crit TxFilterCriteria) (retData []*types.ReturnData) { for _, ev := range txEvents { - // check that From and To fields each match one address in the lists from and to, if specified - - if from != nil { - for _, addr := range from { - if ev.From != nil && addr == *ev.From { - goto checkRecipient - } + // if HasReturnData is true, filter out any transactions with no return data + if crit.HasReturnData { + if len(ev.RetData.Data) == 0 { + continue } - continue } - checkRecipient: - if to != nil { - for _, addr := range to { - ev_to := ev.To - if ev_to == nil { // contract creation - ev_to = &common.Address{} - } - if addr == *ev_to { - goto bothMatch - } + // check that From field of event matches an address in From, if specified + if crit.From != nil && ev.From != nil { + _, found := crit.From[*ev.From] + if !found { + continue + } + } + + // check that To field of event matches an address in To, if specified + if crit.To != nil { + ev_to := ev.To + if ev_to == nil { + ev_to = &common.Address{} + } + _, found := crit.To[*ev.To] + if !found { + continue } - continue } - bothMatch: retData = append(retData, ev.RetData) } diff --git a/eth/filters/filter_system.go b/eth/filters/filter_system.go index 3470be31c3..d1bc08a064 100644 --- a/eth/filters/filter_system.go +++ b/eth/filters/filter_system.go @@ -88,7 +88,7 @@ type subscription struct { logs chan []*types.Log hashes chan common.Hash headers chan *types.Header - txCrit ethereum.TxFilterQuery + txCrit TxFilterCriteria retData chan []*types.ReturnData installed chan struct{} // closed when the filter is installed err chan error // closed when the filter is uninstalled @@ -328,7 +328,7 @@ func (es *EventSystem) SubscribePendingTxEvents(hashes chan common.Hash) *Subscr // SubscribeReturnData creates a subscription that captures return data for transactions // executed by a particular rpc client -func (es *EventSystem) SubscribeReturnData(retCh chan []*types.ReturnData, crit ethereum.TxFilterQuery) *Subscription { +func (es *EventSystem) SubscribeReturnData(retCh chan []*types.ReturnData, crit TxFilterCriteria) *Subscription { sub := &subscription{ id: rpc.NewID(), typ: ReturnDataSubscription, @@ -382,7 +382,7 @@ func (es *EventSystem) broadcast(filters filterIndex, ev interface{}) { case []*core.TransactionEvent: if len(e) > 0 { for _, f := range filters[ReturnDataSubscription] { - if matchedTxs := filterTxs(e, f.txCrit.From, f.txCrit.To); len(matchedTxs) > 0 { + if matchedTxs := filterTxs(e, f.txCrit); len(matchedTxs) > 0 { f.retData <- matchedTxs } } diff --git a/eth/filters/filter_system_test.go b/eth/filters/filter_system_test.go index 461ebd21c8..dafcf874ac 100644 --- a/eth/filters/filter_system_test.go +++ b/eth/filters/filter_system_test.go @@ -299,7 +299,7 @@ func TestReturnDataFilter(t *testing.T) { firstRetData = common.Hex2Bytes("535353535353535353535353535353535353535353535353535353") secondRetData = common.Hex2Bytes("8888888888888888888888888888888888888888888888888888888888888888888888888888888888888") thirdRetData []byte = nil // no return data for this tx - forthRetData = common.Hex2Bytes("0x77") + forthRetData = common.Hex2Bytes("77") backend = &testBackend{mux, db, 0, txPreFeed, txPostFeed, rmLogsFeed, logsFeed, chainFeed} api = NewPublicFilterAPI(backend, false) ) @@ -320,24 +320,33 @@ func TestReturnDataFilter(t *testing.T) { {TxHash: retData[4].TxHash, RetData: &retData[4], From: &firstFromAddr, To: &secondToAddr}, } + makeMap := func(addressList ...common.Address) map[common.Address]struct{} { + addressSet := make(map[common.Address]struct{}) + for _, address := range addressList { + addressSet[address] = struct{}{} + } + return addressSet + } + testCases := []struct { crit TxFilterCriteria expected []types.ReturnData id rpc.ID }{ - 0: {TxFilterCriteria{From: []common.Address{secondFromAddr}}, []types.ReturnData{retData[1], retData[3]}, ""}, - 1: {TxFilterCriteria{From: []common.Address{firstFromAddr}}, []types.ReturnData{retData[0], retData[2], retData[4]}, ""}, - 2: {TxFilterCriteria{From: []common.Address{notUsedAddr}}, []types.ReturnData{}, ""}, - 3: {TxFilterCriteria{From: []common.Address{notUsedAddr, secondFromAddr}}, []types.ReturnData{retData[1], retData[3]}, ""}, - 4: {TxFilterCriteria{From: []common.Address{notUsedAddr, firstFromAddr, secondFromAddr}}, []types.ReturnData{retData[0], retData[1], retData[2], retData[3], retData[4]}, ""}, - 5: {TxFilterCriteria{To: []common.Address{firstToAddr}}, []types.ReturnData{retData[0], retData[3]}, ""}, - 6: {TxFilterCriteria{To: []common.Address{secondToAddr}}, []types.ReturnData{retData[1], retData[2], retData[4]}, ""}, - 7: {TxFilterCriteria{To: []common.Address{firstToAddr, secondToAddr}}, []types.ReturnData{retData[0], retData[1], retData[2], retData[3], retData[4]}, ""}, - 8: {TxFilterCriteria{To: []common.Address{notUsedAddr}}, []types.ReturnData{}, ""}, - 9: {TxFilterCriteria{From: []common.Address{firstFromAddr}, To: []common.Address{secondToAddr}}, []types.ReturnData{retData[2], retData[4]}, ""}, - 10: {TxFilterCriteria{From: []common.Address{notUsedAddr}, To: []common.Address{secondToAddr}}, []types.ReturnData{}, ""}, - 11: {TxFilterCriteria{From: []common.Address{secondFromAddr}, To: []common.Address{secondToAddr, notUsedAddr}}, []types.ReturnData{retData[1]}, ""}, + 0: {TxFilterCriteria{From: makeMap(secondFromAddr)}, []types.ReturnData{retData[1], retData[3]}, ""}, + 1: {TxFilterCriteria{From: makeMap(firstFromAddr)}, []types.ReturnData{retData[0], retData[2], retData[4]}, ""}, + 2: {TxFilterCriteria{From: makeMap(notUsedAddr)}, []types.ReturnData{}, ""}, + 3: {TxFilterCriteria{From: makeMap(notUsedAddr, secondFromAddr)}, []types.ReturnData{retData[1], retData[3]}, ""}, + 4: {TxFilterCriteria{From: makeMap(notUsedAddr, firstFromAddr, secondFromAddr)}, []types.ReturnData{retData[0], retData[1], retData[2], retData[3], retData[4]}, ""}, + 5: {TxFilterCriteria{To: makeMap(firstToAddr)}, []types.ReturnData{retData[0], retData[3]}, ""}, + 6: {TxFilterCriteria{To: makeMap(secondToAddr)}, []types.ReturnData{retData[1], retData[2], retData[4]}, ""}, + 7: {TxFilterCriteria{To: makeMap(firstToAddr, secondToAddr)}, []types.ReturnData{retData[0], retData[1], retData[2], retData[3], retData[4]}, ""}, + 8: {TxFilterCriteria{To: makeMap(notUsedAddr)}, []types.ReturnData{}, ""}, + 9: {TxFilterCriteria{From: makeMap(firstFromAddr), To: makeMap(secondToAddr)}, []types.ReturnData{retData[2], retData[4]}, ""}, + 10: {TxFilterCriteria{From: makeMap(notUsedAddr), To: makeMap(secondToAddr)}, []types.ReturnData{}, ""}, + 11: {TxFilterCriteria{From: makeMap(secondFromAddr), To: makeMap(secondToAddr, notUsedAddr)}, []types.ReturnData{retData[1]}, ""}, 12: {TxFilterCriteria{}, []types.ReturnData{retData[0], retData[1], retData[2], retData[3], retData[4]}, ""}, + 13: {TxFilterCriteria{HasReturnData: true}, []types.ReturnData{retData[0], retData[1], retData[3]}, ""}, } // create all filters @@ -373,8 +382,7 @@ func TestReturnDataFilter(t *testing.T) { } if len(fetched) != len(tt.expected) { - t.Errorf("invalid number of return data events for case %d, want %d events, got %d", i, len(tt.expected), len(fetched)) - //return + t.Fatalf("invalid number of return data events for case %d, want %d events, got %d", i, len(tt.expected), len(fetched)) } for j := range fetched { diff --git a/interfaces.go b/interfaces.go index ab3fc549e9..6bb2a0893e 100644 --- a/interfaces.go +++ b/interfaces.go @@ -150,8 +150,9 @@ type FilterQuery struct { } type TxFilterQuery struct { - From []common.Address // restricts matches to transactions originating from specific addresses - To []common.Address // restricts matches to transactions sent to specific addresses + From map[common.Address]struct{} // restricts matches to transactions originating from specific addresses + To map[common.Address]struct{} // restricts matches to transactions sent to specific addresses + HasReturnData bool // restricts matches to transactions which executed a contract function returning data. } // LogFilterer provides access to contract log events using a one-off query or continuous