forked from 0xProject/0x-mesh
-
Notifications
You must be signed in to change notification settings - Fork 0
/
shared.go
187 lines (168 loc) · 6.1 KB
/
shared.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
package orderfilter
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"github.com/0xProject/0x-mesh/ethereum"
"github.com/0xProject/0x-mesh/zeroex"
"github.com/ethereum/go-ethereum/common"
canonicaljson "github.com/gibson042/canonicaljson-go"
peer "github.com/libp2p/go-libp2p-core/peer"
pubsub "github.com/libp2p/go-libp2p-pubsub"
log "github.com/sirupsen/logrus"
)
const (
pubsubTopicVersion = 3
pubsubTopicVersionV4 = 4
topicVersionFormat = "/0x-orders/version/%d%s"
topicChainIDAndSchemaFormat = "/chain/%d/schema/%s"
fullTopicFormat = "/0x-orders/version/%d/chain/%d/schema/%s"
rendezvousVersion = 1
fullRendezvousFormat = "/0x-custom-filter-rendezvous/version/%d/chain/%d/schema/%s"
)
type WrongTopicVersionError struct {
expectedVersion int
actualVersion int
}
func (e WrongTopicVersionError) Error() string {
return fmt.Sprintf("wrong topic version: expected %d but got %d", e.expectedVersion, e.actualVersion)
}
func GetDefaultFilter(chainID int, contractAddresses ethereum.ContractAddresses) (*Filter, error) {
return New(chainID, DefaultCustomOrderSchema, contractAddresses)
}
func GetDefaultTopic(chainID int, contractAddresses ethereum.ContractAddresses) (string, error) {
defaultFilter, err := GetDefaultFilter(chainID, contractAddresses)
if err != nil {
return "", err
}
return defaultFilter.Topic(), nil
}
func GetDefaultTopicV4(chainID int, contractAddresses ethereum.ContractAddresses) (string, error) {
defaultFilter, err := GetDefaultFilter(chainID, contractAddresses)
if err != nil {
return "", err
}
return defaultFilter.TopicV4(), nil
}
// MatchOrder returns true if the order passes the filter. It only returns an
// error if there was a problem with validation. For details about
// orders that do not pass the filter, use ValidateOrder.
func (f *Filter) MatchOrder(order *zeroex.SignedOrder) (bool, error) {
result, err := f.ValidateOrder(order)
if err != nil {
return false, err
}
return result.Valid(), nil
}
func NewFromTopic(topic string, contractAddresses ethereum.ContractAddresses) (*Filter, error) {
// TODO(albrow): Use a cache for topic -> filter
var version int
var chainIDAndSchema string
if _, err := fmt.Sscanf(topic, topicVersionFormat, &version, &chainIDAndSchema); err != nil {
return nil, fmt.Errorf("could not parse topic version for topic: %q", topic)
}
if version != pubsubTopicVersion {
return nil, WrongTopicVersionError{
expectedVersion: pubsubTopicVersion,
actualVersion: version,
}
}
var chainID int
var base64EncodedSchema string
if _, err := fmt.Sscanf(chainIDAndSchema, topicChainIDAndSchemaFormat, &chainID, &base64EncodedSchema); err != nil {
return nil, fmt.Errorf("could not parse chainID and schema from topic: %q", topic)
}
customOrderSchema, err := base64.URLEncoding.DecodeString(base64EncodedSchema)
if err != nil {
return nil, fmt.Errorf("could not base64-decode order schema: %q", base64EncodedSchema)
}
return New(chainID, string(customOrderSchema), contractAddresses)
}
func (f *Filter) Rendezvous() string {
if f.encodedSchema == "" {
f.encodedSchema = f.generateEncodedSchema()
}
return fmt.Sprintf(fullRendezvousFormat, rendezvousVersion, f.chainID, f.encodedSchema)
}
func (f *Filter) Topic() string {
if f.encodedSchema == "" {
f.encodedSchema = f.generateEncodedSchema()
}
return fmt.Sprintf(fullTopicFormat, pubsubTopicVersion, f.chainID, f.encodedSchema)
}
func (f *Filter) TopicV4() string {
if f.encodedSchema == "" {
f.encodedSchema = f.generateEncodedSchema()
}
return fmt.Sprintf(fullTopicFormat, pubsubTopicVersionV4, f.chainID, f.encodedSchema)
}
// Dummy declaration to ensure that ValidatePubSubMessage matches the expected
// signature for pubsub.Validator.
var _ pubsub.Validator = (&Filter{}).ValidatePubSubMessage
// ValidatePubSubMessage is an implementation of pubsub.Validator and will
// return true if the contents of the message pass the message JSON Schema.
func (f *Filter) ValidatePubSubMessage(ctx context.Context, sender peer.ID, msg *pubsub.Message) bool {
isValid, err := f.MatchOrderMessageJSON(msg.Data)
if err != nil {
log.WithError(err).Error("MatchOrderMessageJSON returned an error")
return false
}
return isValid
}
func (f *Filter) generateEncodedSchema() string {
// Note(albrow): We use canonicaljson to eliminate any differences in spacing,
// formatting, and the order of field names. This ensures that two filters
// that are semantically the same JSON object always encode to exactly the
// same canonical topic string.
//
// So for example:
//
// {
// "foo": "bar",
// "biz": "baz"
// }
//
// Will encode to the same topic string as:
//
// {
// "biz":"baz",
// "foo":"bar"
// }
//
var holder interface{} = struct{}{}
_ = canonicaljson.Unmarshal([]byte(f.rawCustomOrderSchema), &holder)
canonicalOrderSchemaJSON, _ := canonicaljson.Marshal(holder)
return base64.URLEncoding.EncodeToString(canonicalOrderSchemaJSON)
}
// NOTE(jalextowle): Due to the discrepancy between orderfilters used in browser
// nodes and those used in standalone nodes, we cannot simply encode orderfilter.Filter.
// Instead, we marshal a minimal representation of the filter, and then we recreate
// the filter in the node that unmarshals the filter. This ensures that any node
// that unmarshals the orderfilter will be capable of using the filter.
type jsonMarshallerForFilter struct {
CustomOrderSchema string `json:"customOrderSchema"`
ChainID int `json:"chainID"`
ExchangeAddress common.Address `json:"exchangeAddress"`
}
func (f *Filter) MarshalJSON() ([]byte, error) {
j := jsonMarshallerForFilter{
CustomOrderSchema: f.rawCustomOrderSchema,
ChainID: f.chainID,
ExchangeAddress: f.exchangeAddress,
}
return json.Marshal(j)
}
func (f *Filter) UnmarshalJSON(data []byte) error {
j := jsonMarshallerForFilter{}
err := json.Unmarshal(data, &j)
if err != nil {
return err
}
filter, err := New(j.ChainID, j.CustomOrderSchema, ethereum.ContractAddresses{Exchange: j.ExchangeAddress})
if err != nil {
return err
}
*f = *filter
return nil
}