From 5ac0cff06fcfa518488c4f904f5a57dba217effb Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Thu, 12 Sep 2019 23:05:53 +0200 Subject: [PATCH 01/29] fix uncle rewards --- consensus/ubqhash/consensus.go | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/consensus/ubqhash/consensus.go b/consensus/ubqhash/consensus.go index 3547c268b5bc..8c3eac4532b0 100644 --- a/consensus/ubqhash/consensus.go +++ b/consensus/ubqhash/consensus.go @@ -24,7 +24,7 @@ import ( "runtime" "time" - mapset "github.com/deckarep/golang-set" + mapset "github.com/deckarep/golang-set" "github.com/ubiq/go-ubiq/common" "github.com/ubiq/go-ubiq/consensus" "github.com/ubiq/go-ubiq/consensus/misc" @@ -38,9 +38,9 @@ import ( // Ubqhash proof-of-work protocol constants. var ( - blockReward *big.Int = big.NewInt(8e+18) // Block reward in wei for successfully mining a block - maxUncles = 2 // Maximum number of uncles allowed in a single block - allowedFutureBlockTime = 15 * time.Second // Max time from current time allowed for blocks, before they're considered future blocks + blockReward *big.Int = big.NewInt(8e+18) // Block reward in wei for successfully mining a block + maxUncles = 2 // Maximum number of uncles allowed in a single block + allowedFutureBlockTime = 15 * time.Second // Max time from current time allowed for blocks, before they're considered future blocks ) // Diff algo constants. @@ -666,7 +666,7 @@ func (ubqhash *Ubqhash) Prepare(chain consensus.ChainReader, header *types.Heade // setting the final state and assembling the block. func (ubqhash *Ubqhash) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header, receipts []*types.Receipt) (*types.Block, error) { // Accumulate any block and uncle rewards and commit the final state root - accumulateRewards(state, header, uncles) + accumulateRewards(chain.Config(), state, header, uncles) header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) // Header seems complete, assemble into a block and return @@ -706,7 +706,7 @@ func (ubqhash *Ubqhash) SealHash(header *types.Header) (hash common.Hash) { // AccumulateRewards credits the coinbase of the given block with the mining // reward. The total reward consists of the static block reward and rewards for // included uncles. The coinbase of each uncle block is also rewarded. -func accumulateRewards(state *state.StateDB, header *types.Header, uncles []*types.Header) { +func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) { reward := new(big.Int).Set(blockReward) if header.Number.Cmp(big.NewInt(358363)) > 0 { @@ -731,16 +731,22 @@ func accumulateRewards(state *state.StateDB, header *types.Header, uncles []*typ reward = big.NewInt(1e+18) } + // Uncle reward step down fix. + ufixReward := new(big.Int).Set(blockReward) + if config.IsByzantium(header.Number) { + ufixReward = reward + } + r := new(big.Int) for _, uncle := range uncles { r.Add(uncle.Number, big2) r.Sub(r, header.Number) - r.Mul(r, blockReward) + r.Mul(r, ufixReward) r.Div(r, big2) if header.Number.Cmp(big.NewInt(10)) < 0 { state.AddBalance(uncle.Coinbase, r) - r.Div(blockReward, big32) + r.Div(ufixReward, big32) if r.Cmp(big.NewInt(0)) < 0 { r = big.NewInt(0) } @@ -749,7 +755,7 @@ func accumulateRewards(state *state.StateDB, header *types.Header, uncles []*typ r = big.NewInt(0) } state.AddBalance(uncle.Coinbase, r) - r.Div(blockReward, big32) + r.Div(ufixReward, big32) } reward.Add(reward, r) From a6903c886a8f5d796f888c7c1644374ccd2e9bd7 Mon Sep 17 00:00:00 2001 From: Felix Lange Date: Mon, 4 Feb 2019 13:47:34 +0100 Subject: [PATCH 02/29] rpc: implement full bi-directional communication (backport) New APIs added: client.RegisterName(namespace, service) // makes service available to server client.Notify(ctx, method, args...) // sends a notification ClientFromContext(ctx) // to get a client in handler method This is essentially a rewrite of the server-side code. JSON-RPC processing code is now the same on both server and client side. Many minor issues were fixed in the process and there is a new test suite for JSON-RPC spec compliance (and non-compliance in some cases). List of behavior changes: - Method handlers are now called with a per-request context instead of a per-connection context. The context is canceled right after the method returns. - Subscription error channels are always closed when the connection ends. There is no need to also wait on the Notifier's Closed channel to detect whether the subscription has ended. - Client now omits "params" instead of sending "params": null when there are no arguments to a call. The previous behavior was not compliant with the spec. The server still accepts "params": null. - Floating point numbers are allowed as "id". The spec doesn't allow them, but we handle request "id" as json.RawMessage and guarantee that the same number will be sent back. - Logging is improved significantly. There is now a message at DEBUG level for each RPC call served. --- rpc/client.go | 548 ++++++++++------------------- rpc/client_test.go | 145 ++++---- rpc/doc.go | 82 +++-- rpc/errors.go | 39 +- rpc/handler.go | 397 +++++++++++++++++++++ rpc/http.go | 74 ++-- rpc/inproc.go | 4 +- rpc/ipc.go | 16 +- rpc/json.go | 469 ++++++++++++------------ rpc/json_test.go | 178 ---------- rpc/server.go | 443 ++++------------------- rpc/server_test.go | 208 ++++++----- rpc/service.go | 285 +++++++++++++++ rpc/stdio.go | 20 +- rpc/subscription.go | 338 +++++++++++++----- rpc/subscription_test.go | 319 ++++++----------- rpc/testdata/invalid-badid.js | 7 + rpc/testdata/invalid-batch.js | 14 + rpc/testdata/invalid-idonly.js | 7 + rpc/testdata/invalid-nonobj.js | 4 + rpc/testdata/invalid-syntax.json | 5 + rpc/testdata/reqresp-batch.js | 8 + rpc/testdata/reqresp-echo.js | 16 + rpc/testdata/reqresp-namedparam.js | 5 + rpc/testdata/reqresp-noargsrets.js | 4 + rpc/testdata/reqresp-nomethod.js | 4 + rpc/testdata/reqresp-noparam.js | 4 + rpc/testdata/reqresp-paramsnull.js | 4 + rpc/testdata/revcall.js | 6 + rpc/testdata/revcall2.js | 7 + rpc/testdata/subscription.js | 12 + rpc/testservice_test.go | 180 ++++++++++ rpc/types.go | 82 +---- rpc/utils.go | 226 ------------ rpc/utils_test.go | 43 --- rpc/websocket.go | 53 ++- 36 files changed, 2149 insertions(+), 2107 deletions(-) create mode 100644 rpc/handler.go delete mode 100644 rpc/json_test.go create mode 100644 rpc/service.go create mode 100644 rpc/testdata/invalid-badid.js create mode 100644 rpc/testdata/invalid-batch.js create mode 100644 rpc/testdata/invalid-idonly.js create mode 100644 rpc/testdata/invalid-nonobj.js create mode 100644 rpc/testdata/invalid-syntax.json create mode 100644 rpc/testdata/reqresp-batch.js create mode 100644 rpc/testdata/reqresp-echo.js create mode 100644 rpc/testdata/reqresp-namedparam.js create mode 100644 rpc/testdata/reqresp-noargsrets.js create mode 100644 rpc/testdata/reqresp-nomethod.js create mode 100644 rpc/testdata/reqresp-noparam.js create mode 100644 rpc/testdata/reqresp-paramsnull.js create mode 100644 rpc/testdata/revcall.js create mode 100644 rpc/testdata/revcall2.js create mode 100644 rpc/testdata/subscription.js create mode 100644 rpc/testservice_test.go delete mode 100644 rpc/utils.go delete mode 100644 rpc/utils_test.go diff --git a/rpc/client.go b/rpc/client.go index d6f18d54c98f..11444d61fec8 100644 --- a/rpc/client.go +++ b/rpc/client.go @@ -18,17 +18,13 @@ package rpc import ( "bytes" - "container/list" "context" "encoding/json" "errors" "fmt" - "net" "net/url" "reflect" "strconv" - "strings" - "sync" "sync/atomic" "time" @@ -39,13 +35,14 @@ var ( ErrClientQuit = errors.New("client is closed") ErrNoResult = errors.New("no result in JSON-RPC response") ErrSubscriptionQueueOverflow = errors.New("subscription queue overflow") + errClientReconnected = errors.New("client reconnected") + errDead = errors.New("connection lost") ) const ( // Timeouts tcpKeepAliveInterval = 30 * time.Second - defaultDialTimeout = 10 * time.Second // used when dialing if the context has no deadline - defaultWriteTimeout = 10 * time.Second // used for calls if the context has no deadline + defaultDialTimeout = 10 * time.Second // used if context has no deadline subscribeTimeout = 5 * time.Second // overall timeout eth_subscribe, rpc_modules calls ) @@ -76,56 +73,57 @@ type BatchElem struct { Error error } -// A value of this type can a JSON-RPC request, notification, successful response or -// error response. Which one it is depends on the fields. -type jsonrpcMessage struct { - Version string `json:"jsonrpc"` - ID json.RawMessage `json:"id,omitempty"` - Method string `json:"method,omitempty"` - Params json.RawMessage `json:"params,omitempty"` - Error *jsonError `json:"error,omitempty"` - Result json.RawMessage `json:"result,omitempty"` -} +// Client represents a connection to an RPC server. +type Client struct { + idgen func() ID // for subscriptions + isHTTP bool + services *serviceRegistry -func (msg *jsonrpcMessage) isNotification() bool { - return msg.ID == nil && msg.Method != "" -} + idCounter uint32 -func (msg *jsonrpcMessage) isResponse() bool { - return msg.hasValidID() && msg.Method == "" && len(msg.Params) == 0 -} + // This function, if non-nil, is called when the connection is lost. + reconnectFunc reconnectFunc + + // writeConn is used for writing to the connection on the caller's goroutine. It should + // only be accessed outside of dispatch, with the write lock held. The write lock is + // taken by sending on requestOp and released by sending on sendDone. + writeConn jsonWriter -func (msg *jsonrpcMessage) hasValidID() bool { - return len(msg.ID) > 0 && msg.ID[0] != '{' && msg.ID[0] != '[' + // for dispatch + close chan struct{} + closing chan struct{} // closed when client is quitting + didClose chan struct{} // closed when client quits + reconnected chan ServerCodec // where write/reconnect sends the new connection + readOp chan readOp // read messages + readErr chan error // errors from read + reqInit chan *requestOp // register response IDs, takes write lock + reqSent chan error // signals write completion, releases write lock + reqTimeout chan *requestOp // removes response IDs when call timeout expires } -func (msg *jsonrpcMessage) String() string { - b, _ := json.Marshal(msg) - return string(b) +type reconnectFunc func(ctx context.Context) (ServerCodec, error) + +type clientContextKey struct{} + +type clientConn struct { + codec ServerCodec + handler *handler } -// Client represents a connection to an RPC server. -type Client struct { - idCounter uint32 - connectFunc func(ctx context.Context) (net.Conn, error) - isHTTP bool +func (c *Client) newClientConn(conn ServerCodec) *clientConn { + ctx := context.WithValue(context.Background(), clientContextKey{}, c) + handler := newHandler(ctx, conn, c.idgen, c.services) + return &clientConn{conn, handler} +} - // writeConn is only safe to access outside dispatch, with the - // write lock held. The write lock is taken by sending on - // requestOp and released by sending on sendDone. - writeConn net.Conn +func (cc *clientConn) close(err error, inflightReq *requestOp) { + cc.handler.close(err, inflightReq) + cc.codec.Close() +} - // for dispatch - close chan struct{} - closing chan struct{} // closed when client is quitting - didClose chan struct{} // closed when client quits - reconnected chan net.Conn // where write/reconnect sends the new connection - readErr chan error // errors from read - readResp chan []*jsonrpcMessage // valid messages from read - requestOp chan *requestOp // for registering response IDs - sendDone chan error // signals write completion, releases write lock - respWait map[string]*requestOp // active requests - subs map[string]*ClientSubscription // active subscriptions +type readOp struct { + msgs []*jsonrpcMessage + batch bool } type requestOp struct { @@ -135,9 +133,14 @@ type requestOp struct { sub *ClientSubscription // only set for EthSubscribe requests } -func (op *requestOp) wait(ctx context.Context) (*jsonrpcMessage, error) { +func (op *requestOp) wait(ctx context.Context, c *Client) (*jsonrpcMessage, error) { select { case <-ctx.Done(): + // Send the timeout to dispatch so it can remove the request IDs. + select { + case c.reqTimeout <- op: + case <-c.closing: + } return nil, ctx.Err() case resp := <-op.resp: return resp, op.err @@ -181,36 +184,57 @@ func DialContext(ctx context.Context, rawurl string) (*Client, error) { } } -func newClient(initctx context.Context, connectFunc func(context.Context) (net.Conn, error)) (*Client, error) { - conn, err := connectFunc(initctx) +// Client retrieves the client from the context, if any. This can be used to perform +// 'reverse calls' in a handler method. +func ClientFromContext(ctx context.Context) (*Client, bool) { + client, ok := ctx.Value(clientContextKey{}).(*Client) + return client, ok +} + +func newClient(initctx context.Context, connect reconnectFunc) (*Client, error) { + conn, err := connect(initctx) if err != nil { return nil, err } + c := initClient(conn, randomIDGenerator(), new(serviceRegistry)) + c.reconnectFunc = connect + return c, nil +} + +func initClient(conn ServerCodec, idgen func() ID, services *serviceRegistry) *Client { _, isHTTP := conn.(*httpConn) c := &Client{ - writeConn: conn, + idgen: idgen, isHTTP: isHTTP, - connectFunc: connectFunc, + services: services, + writeConn: conn, close: make(chan struct{}), closing: make(chan struct{}), didClose: make(chan struct{}), - reconnected: make(chan net.Conn), + reconnected: make(chan ServerCodec), + readOp: make(chan readOp), readErr: make(chan error), - readResp: make(chan []*jsonrpcMessage), - requestOp: make(chan *requestOp), - sendDone: make(chan error, 1), - respWait: make(map[string]*requestOp), - subs: make(map[string]*ClientSubscription), + reqInit: make(chan *requestOp), + reqSent: make(chan error, 1), + reqTimeout: make(chan *requestOp), } if !isHTTP { go c.dispatch(conn) } - return c, nil + return c +} + +// RegisterName creates a service for the given receiver type under the given name. When no +// methods on the given receiver match the criteria to be either a RPC method or a +// subscription an error is returned. Otherwise a new service is created and added to the +// service collection this client provides to the server. +func (c *Client) RegisterName(name string, receiver interface{}) error { + return c.services.registerName(name, receiver) } func (c *Client) nextID() json.RawMessage { id := atomic.AddUint32(&c.idCounter, 1) - return []byte(strconv.FormatUint(uint64(id), 10)) + return strconv.AppendUint(nil, uint64(id), 10) } // SupportedModules calls the rpc_modules method, retrieving the list of @@ -267,7 +291,7 @@ func (c *Client) CallContext(ctx context.Context, result interface{}, method str } // dispatch has accepted the request and will close the channel when it quits. - switch resp, err := op.wait(ctx); { + switch resp, err := op.wait(ctx, c); { case err != nil: return err case resp.Error != nil: @@ -325,7 +349,7 @@ func (c *Client) BatchCallContext(ctx context.Context, b []BatchElem) error { // Wait for all responses to come back. for n := 0; n < len(b) && err == nil; n++ { var resp *jsonrpcMessage - resp, err = op.wait(ctx) + resp, err = op.wait(ctx, c) if err != nil { break } @@ -352,6 +376,22 @@ func (c *Client) BatchCallContext(ctx context.Context, b []BatchElem) error { return err } +// Notify sends a notification, i.e. a method call that doesn't expect a response. +func (c *Client) Notify(ctx context.Context, method string, args ...interface{}) error { + op := new(requestOp) + msg, err := c.newMessage(method, args...) + if err != nil { + return err + } + msg.ID = nil + + if c.isHTTP { + return c.sendHTTP(ctx, op, msg) + } else { + return c.send(ctx, op, msg) + } +} + // EthSubscribe registers a subscripion under the "eth" namespace. func (c *Client) EthSubscribe(ctx context.Context, channel interface{}, args ...interface{}) (*ClientSubscription, error) { return c.Subscribe(ctx, "eth", channel, args...) @@ -402,30 +442,30 @@ func (c *Client) Subscribe(ctx context.Context, namespace string, channel interf if err := c.send(ctx, op, msg); err != nil { return nil, err } - if _, err := op.wait(ctx); err != nil { + if _, err := op.wait(ctx, c); err != nil { return nil, err } return op.sub, nil } func (c *Client) newMessage(method string, paramsIn ...interface{}) (*jsonrpcMessage, error) { - params, err := json.Marshal(paramsIn) - if err != nil { - return nil, err + msg := &jsonrpcMessage{Version: vsn, ID: c.nextID(), Method: method} + if paramsIn != nil { // prevent sending "params":null + var err error + if msg.Params, err = json.Marshal(paramsIn); err != nil { + return nil, err + } } - return &jsonrpcMessage{Version: "2.0", ID: c.nextID(), Method: method, Params: params}, nil + return msg, nil } // send registers op with the dispatch loop, then sends msg on the connection. // if sending fails, op is deregistered. func (c *Client) send(ctx context.Context, op *requestOp, msg interface{}) error { select { - case c.requestOp <- op: - log.Trace("", "msg", log.Lazy{Fn: func() string { - return fmt.Sprint("sending ", msg) - }}) + case c.reqInit <- op: err := c.write(ctx, msg) - c.sendDone <- err + c.reqSent <- err return err case <-ctx.Done(): // This can happen if the client is overloaded or unable to keep up with @@ -433,25 +473,17 @@ func (c *Client) send(ctx context.Context, op *requestOp, msg interface{}) error return ctx.Err() case <-c.closing: return ErrClientQuit - case <-c.didClose: - return ErrClientQuit } } func (c *Client) write(ctx context.Context, msg interface{}) error { - deadline, ok := ctx.Deadline() - if !ok { - deadline = time.Now().Add(defaultWriteTimeout) - } // The previous write failed. Try to establish a new connection. if c.writeConn == nil { if err := c.reconnect(ctx); err != nil { return err } } - c.writeConn.SetWriteDeadline(deadline) - err := json.NewEncoder(c.writeConn).Encode(msg) - c.writeConn.SetWriteDeadline(time.Time{}) + err := c.writeConn.Write(ctx, msg) if err != nil { c.writeConn = nil } @@ -459,9 +491,18 @@ func (c *Client) write(ctx context.Context, msg interface{}) error { } func (c *Client) reconnect(ctx context.Context) error { - newconn, err := c.connectFunc(ctx) + if c.reconnectFunc == nil { + return errDead + } + + if _, ok := ctx.Deadline(); !ok { + var cancel func() + ctx, cancel = context.WithTimeout(ctx, defaultDialTimeout) + defer cancel() + } + newconn, err := c.reconnectFunc(ctx) if err != nil { - log.Trace(fmt.Sprintf("reconnect failed: %v", err)) + log.Trace("RPC client reconnect failed", "err", err) return err } select { @@ -477,322 +518,107 @@ func (c *Client) reconnect(ctx context.Context) error { // dispatch is the main loop of the client. // It sends read messages to waiting calls to Call and BatchCall // and subscription notifications to registered subscriptions. -func (c *Client) dispatch(conn net.Conn) { - // Spawn the initial read loop. - go c.read(conn) - +func (c *Client) dispatch(codec ServerCodec) { var ( - lastOp *requestOp // tracks last send operation - requestOpLock = c.requestOp // nil while the send lock is held - reading = true // if true, a read loop is running + lastOp *requestOp // tracks last send operation + reqInitLock = c.reqInit // nil while the send lock is held + conn = c.newClientConn(codec) + reading = true ) - defer close(c.didClose) defer func() { close(c.closing) - c.closeRequestOps(ErrClientQuit) - conn.Close() if reading { - // Empty read channels until read is dead. - for { - select { - case <-c.readResp: - case <-c.readErr: - return - } - } + conn.close(ErrClientQuit, nil) + c.drainRead() } + close(c.didClose) }() + // Spawn the initial read loop. + go c.read(codec) + for { select { case <-c.close: return - // Read path. - case batch := <-c.readResp: - for _, msg := range batch { - switch { - case msg.isNotification(): - log.Trace("", "msg", log.Lazy{Fn: func() string { - return fmt.Sprint("<-readResp: notification ", msg) - }}) - c.handleNotification(msg) - case msg.isResponse(): - log.Trace("", "msg", log.Lazy{Fn: func() string { - return fmt.Sprint("<-readResp: response ", msg) - }}) - c.handleResponse(msg) - default: - log.Debug("", "msg", log.Lazy{Fn: func() string { - return fmt.Sprint("<-readResp: dropping weird message", msg) - }}) - // TODO: maybe close - } + // Read path: + case op := <-c.readOp: + if op.batch { + conn.handler.handleBatch(op.msgs) + } else { + conn.handler.handleMsg(op.msgs[0]) } case err := <-c.readErr: - log.Debug("<-readErr", "err", err) - c.closeRequestOps(err) - conn.Close() + conn.handler.log.Debug("RPC connection read error", "err", err) + conn.close(err, lastOp) reading = false - case newconn := <-c.reconnected: - log.Debug("<-reconnected", "reading", reading, "remote", conn.RemoteAddr()) + // Reconnect: + case newcodec := <-c.reconnected: + log.Debug("RPC client reconnected", "reading", reading, "conn", newcodec.RemoteAddr()) if reading { - // Wait for the previous read loop to exit. This is a rare case. - conn.Close() - <-c.readErr + // Wait for the previous read loop to exit. This is a rare case which + // happens if this loop isn't notified in time after the connection breaks. + // In those cases the caller will notice first and reconnect. Closing the + // handler terminates all waiting requests (closing op.resp) except for + // lastOp, which will be transferred to the new handler. + conn.close(errClientReconnected, lastOp) + c.drainRead() } - go c.read(newconn) + go c.read(newcodec) reading = true - conn = newconn - - // Send path. - case op := <-requestOpLock: - // Stop listening for further send ops until the current one is done. - requestOpLock = nil + conn = c.newClientConn(newcodec) + // Re-register the in-flight request on the new handler + // because that's where it will be sent. + conn.handler.addRequestOp(lastOp) + + // Send path: + case op := <-reqInitLock: + // Stop listening for further requests until the current one has been sent. + reqInitLock = nil lastOp = op - for _, id := range op.ids { - c.respWait[string(id)] = op - } + conn.handler.addRequestOp(op) - case err := <-c.sendDone: + case err := <-c.reqSent: if err != nil { - // Remove response handlers for the last send. We remove those here - // because the error is already handled in Call or BatchCall. When the - // read loop goes down, it will signal all other current operations. - for _, id := range lastOp.ids { - delete(c.respWait, string(id)) - } + // Remove response handlers for the last send. When the read loop + // goes down, it will signal all other current operations. + conn.handler.removeRequestOp(lastOp) } - // Listen for send ops again. - requestOpLock = c.requestOp + // Let the next request in. + reqInitLock = c.reqInit lastOp = nil - } - } -} - -// closeRequestOps unblocks pending send ops and active subscriptions. -func (c *Client) closeRequestOps(err error) { - didClose := make(map[*requestOp]bool) - for id, op := range c.respWait { - // Remove the op so that later calls will not close op.resp again. - delete(c.respWait, id) - - if !didClose[op] { - op.err = err - close(op.resp) - didClose[op] = true + case op := <-c.reqTimeout: + conn.handler.removeRequestOp(op) } } - for id, sub := range c.subs { - delete(c.subs, id) - sub.quitWithError(err, false) - } -} - -func (c *Client) handleNotification(msg *jsonrpcMessage) { - if !strings.HasSuffix(msg.Method, notificationMethodSuffix) { - log.Debug("dropping non-subscription message", "msg", msg) - return - } - var subResult struct { - ID string `json:"subscription"` - Result json.RawMessage `json:"result"` - } - if err := json.Unmarshal(msg.Params, &subResult); err != nil { - log.Debug("dropping invalid subscription message", "msg", msg) - return - } - if c.subs[subResult.ID] != nil { - c.subs[subResult.ID].deliver(subResult.Result) - } -} - -func (c *Client) handleResponse(msg *jsonrpcMessage) { - op := c.respWait[string(msg.ID)] - if op == nil { - log.Debug("unsolicited response", "msg", msg) - return - } - delete(c.respWait, string(msg.ID)) - // For normal responses, just forward the reply to Call/BatchCall. - if op.sub == nil { - op.resp <- msg - return - } - // For subscription responses, start the subscription if the server - // indicates success. EthSubscribe gets unblocked in either case through - // the op.resp channel. - defer close(op.resp) - if msg.Error != nil { - op.err = msg.Error - return - } - if op.err = json.Unmarshal(msg.Result, &op.sub.subid); op.err == nil { - go op.sub.start() - c.subs[op.sub.subid] = op.sub - } } -// Reading happens on a dedicated goroutine. - -func (c *Client) read(conn net.Conn) error { - var ( - buf json.RawMessage - dec = json.NewDecoder(conn) - ) - readMessage := func() (rs []*jsonrpcMessage, err error) { - buf = buf[:0] - if err = dec.Decode(&buf); err != nil { - return nil, err - } - if isBatch(buf) { - err = json.Unmarshal(buf, &rs) - } else { - rs = make([]*jsonrpcMessage, 1) - err = json.Unmarshal(buf, &rs[0]) - } - return rs, err - } - +// drainRead drops read messages until an error occurs. +func (c *Client) drainRead() { for { - resp, err := readMessage() - if err != nil { - c.readErr <- err - return err - } - c.readResp <- resp - } -} - -// Subscriptions. - -// A ClientSubscription represents a subscription established through EthSubscribe. -type ClientSubscription struct { - client *Client - etype reflect.Type - channel reflect.Value - namespace string - subid string - in chan json.RawMessage - - quitOnce sync.Once // ensures quit is closed once - quit chan struct{} // quit is closed when the subscription exits - errOnce sync.Once // ensures err is closed once - err chan error -} - -func newClientSubscription(c *Client, namespace string, channel reflect.Value) *ClientSubscription { - sub := &ClientSubscription{ - client: c, - namespace: namespace, - etype: channel.Type().Elem(), - channel: channel, - quit: make(chan struct{}), - err: make(chan error, 1), - in: make(chan json.RawMessage), - } - return sub -} - -// Err returns the subscription error channel. The intended use of Err is to schedule -// resubscription when the client connection is closed unexpectedly. -// -// The error channel receives a value when the subscription has ended due -// to an error. The received error is nil if Close has been called -// on the underlying client and no other error has occurred. -// -// The error channel is closed when Unsubscribe is called on the subscription. -func (sub *ClientSubscription) Err() <-chan error { - return sub.err -} - -// Unsubscribe unsubscribes the notification and closes the error channel. -// It can safely be called more than once. -func (sub *ClientSubscription) Unsubscribe() { - sub.quitWithError(nil, true) - sub.errOnce.Do(func() { close(sub.err) }) -} - -func (sub *ClientSubscription) quitWithError(err error, unsubscribeServer bool) { - sub.quitOnce.Do(func() { - // The dispatch loop won't be able to execute the unsubscribe call - // if it is blocked on deliver. Close sub.quit first because it - // unblocks deliver. - close(sub.quit) - if unsubscribeServer { - sub.requestUnsubscribe() - } - if err != nil { - if err == ErrClientQuit { - err = nil // Adhere to subscription semantics. - } - sub.err <- err + select { + case <-c.readOp: + case <-c.readErr: + return } - }) -} - -func (sub *ClientSubscription) deliver(result json.RawMessage) (ok bool) { - select { - case sub.in <- result: - return true - case <-sub.quit: - return false } } -func (sub *ClientSubscription) start() { - sub.quitWithError(sub.forward()) -} - -func (sub *ClientSubscription) forward() (err error, unsubscribeServer bool) { - cases := []reflect.SelectCase{ - {Dir: reflect.SelectRecv, Chan: reflect.ValueOf(sub.quit)}, - {Dir: reflect.SelectRecv, Chan: reflect.ValueOf(sub.in)}, - {Dir: reflect.SelectSend, Chan: sub.channel}, - } - buffer := list.New() - defer buffer.Init() +// read decodes RPC messages from a codec, feeding them into dispatch. +func (c *Client) read(codec ServerCodec) { for { - var chosen int - var recv reflect.Value - if buffer.Len() == 0 { - // Idle, omit send case. - chosen, recv, _ = reflect.Select(cases[:2]) - } else { - // Non-empty buffer, send the first queued item. - cases[2].Send = reflect.ValueOf(buffer.Front().Value) - chosen, recv, _ = reflect.Select(cases) + msgs, batch, err := codec.Read() + if _, ok := err.(*json.SyntaxError); ok { + codec.Write(context.Background(), errorMessage(&parseError{err.Error()})) } - - switch chosen { - case 0: // <-sub.quit - return nil, false - case 1: // <-sub.in - val, err := sub.unmarshal(recv.Interface().(json.RawMessage)) - if err != nil { - return err, true - } - if buffer.Len() == maxClientSubscriptionBuffer { - return ErrSubscriptionQueueOverflow, true - } - buffer.PushBack(val) - case 2: // sub.channel<- - cases[2].Send = reflect.Value{} // Don't hold onto the value. - buffer.Remove(buffer.Front()) + if err != nil { + c.readErr <- err + return } + c.readOp <- readOp{msgs, batch} } } - -func (sub *ClientSubscription) unmarshal(result json.RawMessage) (interface{}, error) { - val := reflect.New(sub.etype) - err := json.Unmarshal(result, val.Interface()) - return val.Elem().Interface(), err -} - -func (sub *ClientSubscription) requestUnsubscribe() error { - var result interface{} - return sub.client.Call(&result, sub.namespace+unsubscribeMethodSuffix, sub.subid) -} diff --git a/rpc/client_test.go b/rpc/client_test.go index 8072c57fadce..dab36ea12757 100644 --- a/rpc/client_test.go +++ b/rpc/client_test.go @@ -35,13 +35,13 @@ import ( ) func TestClientRequest(t *testing.T) { - server := newTestServer("service", new(Service)) + server := newTestServer() defer server.Stop() client := DialInProc(server) defer client.Close() var resp Result - if err := client.Call(&resp, "service_echo", "hello", 10, &Args{"world"}); err != nil { + if err := client.Call(&resp, "test_echo", "hello", 10, &Args{"world"}); err != nil { t.Fatal(err) } if !reflect.DeepEqual(resp, Result{"hello", 10, &Args{"world"}}) { @@ -50,19 +50,19 @@ func TestClientRequest(t *testing.T) { } func TestClientBatchRequest(t *testing.T) { - server := newTestServer("service", new(Service)) + server := newTestServer() defer server.Stop() client := DialInProc(server) defer client.Close() batch := []BatchElem{ { - Method: "service_echo", + Method: "test_echo", Args: []interface{}{"hello", 10, &Args{"world"}}, Result: new(Result), }, { - Method: "service_echo", + Method: "test_echo", Args: []interface{}{"hello2", 11, &Args{"world"}}, Result: new(Result), }, @@ -77,12 +77,12 @@ func TestClientBatchRequest(t *testing.T) { } wantResult := []BatchElem{ { - Method: "service_echo", + Method: "test_echo", Args: []interface{}{"hello", 10, &Args{"world"}}, Result: &Result{"hello", 10, &Args{"world"}}, }, { - Method: "service_echo", + Method: "test_echo", Args: []interface{}{"hello2", 11, &Args{"world"}}, Result: &Result{"hello2", 11, &Args{"world"}}, }, @@ -90,7 +90,7 @@ func TestClientBatchRequest(t *testing.T) { Method: "no_such_method", Args: []interface{}{1, 2, 3}, Result: new(int), - Error: &jsonError{Code: -32601, Message: "The method no_such_method_ does not exist/is not available"}, + Error: &jsonError{Code: -32601, Message: "the method no_such_method does not exist/is not available"}, }, } if !reflect.DeepEqual(batch, wantResult) { @@ -98,6 +98,17 @@ func TestClientBatchRequest(t *testing.T) { } } +func TestClientNotify(t *testing.T) { + server := newTestServer() + defer server.Stop() + client := DialInProc(server) + defer client.Close() + + if err := client.Notify(context.Background(), "test_echo", "hello", 10, &Args{"world"}); err != nil { + t.Fatal(err) + } +} + // func TestClientCancelInproc(t *testing.T) { testClientCancel("inproc", t) } func TestClientCancelWebsocket(t *testing.T) { testClientCancel("ws", t) } func TestClientCancelHTTP(t *testing.T) { testClientCancel("http", t) } @@ -106,7 +117,12 @@ func TestClientCancelIPC(t *testing.T) { testClientCancel("ipc", t) } // This test checks that requests made through CallContext can be canceled by canceling // the context. func testClientCancel(transport string, t *testing.T) { - server := newTestServer("service", new(Service)) + // These tests take a lot of time, run them all at once. + // You probably want to run with -parallel 1 or comment out + // the call to t.Parallel if you enable the logging. + t.Parallel() + + server := newTestServer() defer server.Stop() // What we want to achieve is that the context gets canceled @@ -142,11 +158,6 @@ func testClientCancel(transport string, t *testing.T) { panic("unknown transport: " + transport) } - // These tests take a lot of time, run them all at once. - // You probably want to run with -parallel 1 or comment out - // the call to t.Parallel if you enable the logging. - t.Parallel() - // The actual test starts here. var ( wg sync.WaitGroup @@ -174,7 +185,8 @@ func testClientCancel(transport string, t *testing.T) { } // Now perform a call with the context. // The key thing here is that no call will ever complete successfully. - err := client.CallContext(ctx, nil, "service_sleep", 2*maxContextCancelTimeout) + sleepTime := maxContextCancelTimeout + 20*time.Millisecond + err := client.CallContext(ctx, nil, "test_sleep", sleepTime) if err != nil { log.Debug(fmt.Sprint("got expected error:", err)) } else { @@ -191,7 +203,7 @@ func testClientCancel(transport string, t *testing.T) { } func TestClientSubscribeInvalidArg(t *testing.T) { - server := newTestServer("service", new(Service)) + server := newTestServer() defer server.Stop() client := DialInProc(server) defer client.Close() @@ -221,46 +233,14 @@ func TestClientSubscribeInvalidArg(t *testing.T) { } func TestClientSubscribe(t *testing.T) { - server := newTestServer("eth", new(NotificationTestService)) - defer server.Stop() - client := DialInProc(server) - defer client.Close() - - nc := make(chan int) - count := 10 - sub, err := client.EthSubscribe(context.Background(), nc, "someSubscription", count, 0) - if err != nil { - t.Fatal("can't subscribe:", err) - } - for i := 0; i < count; i++ { - if val := <-nc; val != i { - t.Fatalf("value mismatch: got %d, want %d", val, i) - } - } - - sub.Unsubscribe() - select { - case v := <-nc: - t.Fatal("received value after unsubscribe:", v) - case err := <-sub.Err(): - if err != nil { - t.Fatalf("Err returned a non-nil error after explicit unsubscribe: %q", err) - } - case <-time.After(1 * time.Second): - t.Fatalf("subscription not closed within 1s after unsubscribe") - } -} - -func TestClientSubscribeCustomNamespace(t *testing.T) { - namespace := "custom" - server := newTestServer(namespace, new(NotificationTestService)) + server := newTestServer() defer server.Stop() client := DialInProc(server) defer client.Close() nc := make(chan int) count := 10 - sub, err := client.Subscribe(context.Background(), namespace, nc, "someSubscription", count, 0) + sub, err := client.Subscribe(context.Background(), "nftest", nc, "someSubscription", count, 0) if err != nil { t.Fatal("can't subscribe:", err) } @@ -283,14 +263,17 @@ func TestClientSubscribeCustomNamespace(t *testing.T) { } } -// In this test, the connection drops while EthSubscribe is -// waiting for a response. +// In this test, the connection drops while Subscribe is waiting for a response. func TestClientSubscribeClose(t *testing.T) { - service := &NotificationTestService{ + server := newTestServer() + service := ¬ificationTestService{ gotHangSubscriptionReq: make(chan struct{}), unblockHangSubscription: make(chan struct{}), } - server := newTestServer("eth", service) + if err := server.RegisterName("nftest2", service); err != nil { + t.Fatal(err) + } + defer server.Stop() client := DialInProc(server) defer client.Close() @@ -302,7 +285,7 @@ func TestClientSubscribeClose(t *testing.T) { err error ) go func() { - sub, err = client.EthSubscribe(context.Background(), nc, "hangSubscription", 999) + sub, err = client.Subscribe(context.Background(), "nftest2", nc, "hangSubscription", 999) errc <- err }() @@ -313,27 +296,26 @@ func TestClientSubscribeClose(t *testing.T) { select { case err := <-errc: if err == nil { - t.Errorf("EthSubscribe returned nil error after Close") + t.Errorf("Subscribe returned nil error after Close") } if sub != nil { - t.Error("EthSubscribe returned non-nil subscription after Close") + t.Error("Subscribe returned non-nil subscription after Close") } case <-time.After(1 * time.Second): - t.Fatalf("EthSubscribe did not return within 1s after Close") + t.Fatalf("Subscribe did not return within 1s after Close") } } // This test reproduces https://github.com/ubiq/go-ubiq/issues/17837 where the // client hangs during shutdown when Unsubscribe races with Client.Close. func TestClientCloseUnsubscribeRace(t *testing.T) { - service := &NotificationTestService{} - server := newTestServer("eth", service) + server := newTestServer() defer server.Stop() for i := 0; i < 20; i++ { client := DialInProc(server) nc := make(chan int) - sub, err := client.EthSubscribe(context.Background(), nc, "someSubscription", 3, 1) + sub, err := client.Subscribe(context.Background(), "nftest", nc, "someSubscription", 3, 1) if err != nil { t.Fatal(err) } @@ -350,7 +332,7 @@ func TestClientCloseUnsubscribeRace(t *testing.T) { // This test checks that Client doesn't lock up when a single subscriber // doesn't read subscription events. func TestClientNotificationStorm(t *testing.T) { - server := newTestServer("eth", new(NotificationTestService)) + server := newTestServer() defer server.Stop() doTest := func(count int, wantError bool) { @@ -362,7 +344,7 @@ func TestClientNotificationStorm(t *testing.T) { // Subscribe on the server. It will start sending many notifications // very quickly. nc := make(chan int) - sub, err := client.EthSubscribe(ctx, nc, "someSubscription", count, 0) + sub, err := client.Subscribe(ctx, "nftest", nc, "someSubscription", count, 0) if err != nil { t.Fatal("can't subscribe:", err) } @@ -384,7 +366,7 @@ func TestClientNotificationStorm(t *testing.T) { return } var r int - err := client.CallContext(ctx, &r, "eth_echo", i) + err := client.CallContext(ctx, &r, "nftest_echo", i) if err != nil { if !wantError { t.Fatalf("(%d/%d) call error: %v", i, count, err) @@ -399,7 +381,7 @@ func TestClientNotificationStorm(t *testing.T) { } func TestClientHTTP(t *testing.T) { - server := newTestServer("service", new(Service)) + server := newTestServer() defer server.Stop() client, hs := httpTestClient(server, "http", nil) @@ -416,7 +398,7 @@ func TestClientHTTP(t *testing.T) { for i := range results { i := i go func() { - errc <- client.Call(&results[i], "service_echo", + errc <- client.Call(&results[i], "test_echo", wantResult.String, wantResult.Int, wantResult.Args) }() } @@ -445,16 +427,16 @@ func TestClientHTTP(t *testing.T) { func TestClientReconnect(t *testing.T) { startServer := func(addr string) (*Server, net.Listener) { - srv := newTestServer("service", new(Service)) + srv := newTestServer() l, err := net.Listen("tcp", addr) if err != nil { - t.Fatal(err) + t.Fatal("can't listen:", err) } go http.Serve(l, srv.WebsocketHandler([]string{"*"})) return srv, l } - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second) defer cancel() // Start a server and corresponding client. @@ -466,21 +448,22 @@ func TestClientReconnect(t *testing.T) { // Perform a call. This should work because the server is up. var resp Result - if err := client.CallContext(ctx, &resp, "service_echo", "", 1, nil); err != nil { + if err := client.CallContext(ctx, &resp, "test_echo", "", 1, nil); err != nil { t.Fatal(err) } - // Shut down the server and try calling again. It shouldn't work. + // Shut down the server and allow for some cool down time so we can listen on the same + // address again. l1.Close() s1.Stop() - if err := client.CallContext(ctx, &resp, "service_echo", "", 2, nil); err == nil { + time.Sleep(2 * time.Second) + + // Try calling again. It shouldn't work. + if err := client.CallContext(ctx, &resp, "test_echo", "", 2, nil); err == nil { t.Error("successful call while the server is down") t.Logf("resp: %#v", resp) } - // Allow for some cool down time so we can listen on the same address again. - time.Sleep(2 * time.Second) - // Start it up again and call again. The connection should be reestablished. // We spawn multiple calls here to check whether this hangs somehow. s2, l2 := startServer(l1.Addr().String()) @@ -493,7 +476,7 @@ func TestClientReconnect(t *testing.T) { go func() { <-start var resp Result - errors <- client.CallContext(ctx, &resp, "service_echo", "", 3, nil) + errors <- client.CallContext(ctx, &resp, "test_echo", "", 3, nil) }() } close(start) @@ -503,20 +486,12 @@ func TestClientReconnect(t *testing.T) { errcount++ } } - t.Log("err:", err) + t.Logf("%d errors, last error: %v", errcount, err) if errcount > 1 { t.Errorf("expected one error after disconnect, got %d", errcount) } } -func newTestServer(serviceName string, service interface{}) *Server { - server := NewServer() - if err := server.RegisterName(serviceName, service); err != nil { - panic(err) - } - return server -} - func httpTestClient(srv *Server, transport string, fl *flakeyListener) (*Client, *httptest.Server) { // Create the HTTP server. var hs *httptest.Server diff --git a/rpc/doc.go b/rpc/doc.go index c60381b5aef8..e5840c32d3f6 100644 --- a/rpc/doc.go +++ b/rpc/doc.go @@ -15,43 +15,49 @@ // along with the go-ethereum library. If not, see . /* -Package rpc provides access to the exported methods of an object across a network -or other I/O connection. After creating a server instance objects can be registered, -making it visible from the outside. Exported methods that follow specific -conventions can be called remotely. It also has support for the publish/subscribe -pattern. + +Package rpc implements bi-directional JSON-RPC 2.0 on multiple transports. + +It provides access to the exported methods of an object across a network or other I/O +connection. After creating a server or client instance, objects can be registered to make +them visible as 'services'. Exported methods that follow specific conventions can be +called remotely. It also has support for the publish/subscribe pattern. + +RPC Methods Methods that satisfy the following criteria are made available for remote access: - - object must be exported + - method must be exported - method returns 0, 1 (response or error) or 2 (response and error) values - method argument(s) must be exported or builtin types - method returned value(s) must be exported or builtin types An example method: + func (s *CalcService) Add(a, b int) (int, error) -When the returned error isn't nil the returned integer is ignored and the error is -sent back to the client. Otherwise the returned integer is sent back to the client. +When the returned error isn't nil the returned integer is ignored and the error is sent +back to the client. Otherwise the returned integer is sent back to the client. -Optional arguments are supported by accepting pointer values as arguments. E.g. -if we want to do the addition in an optional finite field we can accept a mod -argument as pointer value. +Optional arguments are supported by accepting pointer values as arguments. E.g. if we want +to do the addition in an optional finite field we can accept a mod argument as pointer +value. - func (s *CalService) Add(a, b int, mod *int) (int, error) + func (s *CalcService) Add(a, b int, mod *int) (int, error) -This RPC method can be called with 2 integers and a null value as third argument. -In that case the mod argument will be nil. Or it can be called with 3 integers, -in that case mod will be pointing to the given third argument. Since the optional -argument is the last argument the RPC package will also accept 2 integers as -arguments. It will pass the mod argument as nil to the RPC method. +This RPC method can be called with 2 integers and a null value as third argument. In that +case the mod argument will be nil. Or it can be called with 3 integers, in that case mod +will be pointing to the given third argument. Since the optional argument is the last +argument the RPC package will also accept 2 integers as arguments. It will pass the mod +argument as nil to the RPC method. -The server offers the ServeCodec method which accepts a ServerCodec instance. It will -read requests from the codec, process the request and sends the response back to the -client using the codec. The server can execute requests concurrently. Responses -can be sent back to the client out of order. +The server offers the ServeCodec method which accepts a ServerCodec instance. It will read +requests from the codec, process the request and sends the response back to the client +using the codec. The server can execute requests concurrently. Responses can be sent back +to the client out of order. An example server which uses the JSON codec: + type CalculatorService struct {} func (s *CalculatorService) Add(a, b int) int { @@ -73,26 +79,40 @@ An example server which uses the JSON codec: for { c, _ := l.AcceptUnix() codec := v2.NewJSONCodec(c) - go server.ServeCodec(codec) + go server.ServeCodec(codec, 0) } +Subscriptions + The package also supports the publish subscribe pattern through the use of subscriptions. -A method that is considered eligible for notifications must satisfy the following criteria: - - object must be exported +A method that is considered eligible for notifications must satisfy the following +criteria: + - method must be exported - first method argument type must be context.Context - method argument(s) must be exported or builtin types - - method must return the tuple Subscription, error + - method must have return types (rpc.Subscription, error) An example method: - func (s *BlockChainService) NewBlocks(ctx context.Context) (Subscription, error) { + + func (s *BlockChainService) NewBlocks(ctx context.Context) (rpc.Subscription, error) { ... } -Subscriptions are deleted when: - - the user sends an unsubscribe request - - the connection which was used to create the subscription is closed. This can be initiated - by the client and server. The server will close the connection on a write error or when - the queue of buffered notifications gets too big. +When the service containing the subscription method is registered to the server, for +example under the "blockchain" namespace, a subscription is created by calling the +"blockchain_subscribe" method. + +Subscriptions are deleted when the user sends an unsubscribe request or when the +connection which was used to create the subscription is closed. This can be initiated by +the client and server. The server will close the connection for any write error. + +For more information about subscriptions, see https://github.com/ethereum/go-ethereum/wiki/RPC-PUB-SUB. + +Reverse Calls + +In any method handler, an instance of rpc.Client can be accessed through the +ClientFromContext method. Using this client instance, server-to-client method calls can be +performed on the RPC connection. */ package rpc diff --git a/rpc/errors.go b/rpc/errors.go index 9cf9dc60c29f..c3aa826cc88a 100644 --- a/rpc/errors.go +++ b/rpc/errors.go @@ -18,18 +18,31 @@ package rpc import "fmt" -// request is for an unknown service -type methodNotFoundError struct { - service string - method string -} +const defaultErrorCode = -32000 + +type methodNotFoundError struct{ method string } func (e *methodNotFoundError) ErrorCode() int { return -32601 } func (e *methodNotFoundError) Error() string { - return fmt.Sprintf("The method %s%s%s does not exist/is not available", e.service, serviceMethodSeparator, e.method) + return fmt.Sprintf("the method %s does not exist/is not available", e.method) +} + +type subscriptionNotFoundError struct{ namespace, subscription string } + +func (e *subscriptionNotFoundError) ErrorCode() int { return -32601 } + +func (e *subscriptionNotFoundError) Error() string { + return fmt.Sprintf("no %q subscription in %s namespace", e.subscription, e.namespace) } +// Invalid JSON was received by the server. +type parseError struct{ message string } + +func (e *parseError) ErrorCode() int { return -32700 } + +func (e *parseError) Error() string { return e.message } + // received message isn't a valid request type invalidRequestError struct{ message string } @@ -50,17 +63,3 @@ type invalidParamsError struct{ message string } func (e *invalidParamsError) ErrorCode() int { return -32602 } func (e *invalidParamsError) Error() string { return e.message } - -// logic error, callback returned an error -type callbackError struct{ message string } - -func (e *callbackError) ErrorCode() int { return -32000 } - -func (e *callbackError) Error() string { return e.message } - -// issued when a request is received after the server is issued to stop. -type shutdownError struct{} - -func (e *shutdownError) ErrorCode() int { return -32000 } - -func (e *shutdownError) Error() string { return "server is shutting down" } diff --git a/rpc/handler.go b/rpc/handler.go new file mode 100644 index 000000000000..f1806f73be6f --- /dev/null +++ b/rpc/handler.go @@ -0,0 +1,397 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rpc + +import ( + "context" + "encoding/json" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/ubiq/go-ubiq/log" +) + +// handler handles JSON-RPC messages. There is one handler per connection. Note that +// handler is not safe for concurrent use. Message handling never blocks indefinitely +// because RPCs are processed on background goroutines launched by handler. +// +// The entry points for incoming messages are: +// +// h.handleMsg(message) +// h.handleBatch(message) +// +// Outgoing calls use the requestOp struct. Register the request before sending it +// on the connection: +// +// op := &requestOp{ids: ...} +// h.addRequestOp(op) +// +// Now send the request, then wait for the reply to be delivered through handleMsg: +// +// if err := op.wait(...); err != nil { +// h.removeRequestOp(op) // timeout, etc. +// } +// +type handler struct { + reg *serviceRegistry + unsubscribeCb *callback + idgen func() ID // subscription ID generator + respWait map[string]*requestOp // active client requests + clientSubs map[string]*ClientSubscription // active client subscriptions + callWG sync.WaitGroup // pending call goroutines + rootCtx context.Context // canceled by close() + cancelRoot func() // cancel function for rootCtx + conn jsonWriter // where responses will be sent + log log.Logger + allowSubscribe bool + + subLock sync.Mutex + serverSubs map[ID]*Subscription +} + +type callProc struct { + ctx context.Context + notifiers []*Notifier +} + +func newHandler(connCtx context.Context, conn jsonWriter, idgen func() ID, reg *serviceRegistry) *handler { + rootCtx, cancelRoot := context.WithCancel(connCtx) + h := &handler{ + reg: reg, + idgen: idgen, + conn: conn, + respWait: make(map[string]*requestOp), + clientSubs: make(map[string]*ClientSubscription), + rootCtx: rootCtx, + cancelRoot: cancelRoot, + allowSubscribe: true, + serverSubs: make(map[ID]*Subscription), + log: log.Root(), + } + if conn.RemoteAddr() != "" { + h.log = h.log.New("conn", conn.RemoteAddr()) + } + h.unsubscribeCb = newCallback(reflect.Value{}, reflect.ValueOf(h.unsubscribe)) + return h +} + +// handleBatch executes all messages in a batch and returns the responses. +func (h *handler) handleBatch(msgs []*jsonrpcMessage) { + // Emit error response for empty batches: + if len(msgs) == 0 { + h.startCallProc(func(cp *callProc) { + h.conn.Write(cp.ctx, errorMessage(&invalidRequestError{"empty batch"})) + }) + return + } + + // Handle non-call messages first: + calls := make([]*jsonrpcMessage, 0, len(msgs)) + for _, msg := range msgs { + if handled := h.handleImmediate(msg); !handled { + calls = append(calls, msg) + } + } + if len(calls) == 0 { + return + } + // Process calls on a goroutine because they may block indefinitely: + h.startCallProc(func(cp *callProc) { + answers := make([]*jsonrpcMessage, 0, len(msgs)) + for _, msg := range calls { + if answer := h.handleCallMsg(cp, msg); answer != nil { + answers = append(answers, answer) + } + } + h.addSubscriptions(cp.notifiers) + if len(answers) > 0 { + h.conn.Write(cp.ctx, answers) + } + for _, n := range cp.notifiers { + n.activate() + } + }) +} + +// handleMsg handles a single message. +func (h *handler) handleMsg(msg *jsonrpcMessage) { + if ok := h.handleImmediate(msg); ok { + return + } + h.startCallProc(func(cp *callProc) { + answer := h.handleCallMsg(cp, msg) + h.addSubscriptions(cp.notifiers) + if answer != nil { + h.conn.Write(cp.ctx, answer) + } + for _, n := range cp.notifiers { + n.activate() + } + }) +} + +// close cancels all requests except for inflightReq and waits for +// call goroutines to shut down. +func (h *handler) close(err error, inflightReq *requestOp) { + h.cancelAllRequests(err, inflightReq) + h.cancelRoot() + h.callWG.Wait() + h.cancelServerSubscriptions(err) +} + +// addRequestOp registers a request operation. +func (h *handler) addRequestOp(op *requestOp) { + for _, id := range op.ids { + h.respWait[string(id)] = op + } +} + +// removeRequestOps stops waiting for the given request IDs. +func (h *handler) removeRequestOp(op *requestOp) { + for _, id := range op.ids { + delete(h.respWait, string(id)) + } +} + +// cancelAllRequests unblocks and removes pending requests and active subscriptions. +func (h *handler) cancelAllRequests(err error, inflightReq *requestOp) { + didClose := make(map[*requestOp]bool) + if inflightReq != nil { + didClose[inflightReq] = true + } + + for id, op := range h.respWait { + // Remove the op so that later calls will not close op.resp again. + delete(h.respWait, id) + + if !didClose[op] { + op.err = err + close(op.resp) + didClose[op] = true + } + } + for id, sub := range h.clientSubs { + delete(h.clientSubs, id) + sub.quitWithError(err, false) + } +} + +func (h *handler) addSubscriptions(nn []*Notifier) { + h.subLock.Lock() + defer h.subLock.Unlock() + + for _, n := range nn { + if sub := n.takeSubscription(); sub != nil { + h.serverSubs[sub.ID] = sub + } + } +} + +// cancelServerSubscriptions removes all subscriptions and closes their error channels. +func (h *handler) cancelServerSubscriptions(err error) { + h.subLock.Lock() + defer h.subLock.Unlock() + + for id, s := range h.serverSubs { + s.err <- err + close(s.err) + delete(h.serverSubs, id) + } +} + +// startCallProc runs fn in a new goroutine and starts tracking it in the h.calls wait group. +func (h *handler) startCallProc(fn func(*callProc)) { + h.callWG.Add(1) + go func() { + ctx, cancel := context.WithCancel(h.rootCtx) + defer h.callWG.Done() + defer cancel() + fn(&callProc{ctx: ctx}) + }() +} + +// handleImmediate executes non-call messages. It returns false if the message is a +// call or requires a reply. +func (h *handler) handleImmediate(msg *jsonrpcMessage) bool { + start := time.Now() + switch { + case msg.isNotification(): + if strings.HasSuffix(msg.Method, notificationMethodSuffix) { + h.handleSubscriptionResult(msg) + return true + } + return false + case msg.isResponse(): + h.handleResponse(msg) + h.log.Trace("Handled RPC response", "reqid", idForLog{msg.ID}, "t", time.Since(start)) + return true + default: + return false + } +} + +// handleSubscriptionResult processes subscription notifications. +func (h *handler) handleSubscriptionResult(msg *jsonrpcMessage) { + var result subscriptionResult + if err := json.Unmarshal(msg.Params, &result); err != nil { + h.log.Debug("Dropping invalid subscription message") + return + } + if h.clientSubs[result.ID] != nil { + h.clientSubs[result.ID].deliver(result.Result) + } +} + +// handleResponse processes method call responses. +func (h *handler) handleResponse(msg *jsonrpcMessage) { + op := h.respWait[string(msg.ID)] + if op == nil { + h.log.Debug("Unsolicited RPC response", "reqid", idForLog{msg.ID}) + return + } + delete(h.respWait, string(msg.ID)) + // For normal responses, just forward the reply to Call/BatchCall. + if op.sub == nil { + op.resp <- msg + return + } + // For subscription responses, start the subscription if the server + // indicates success. EthSubscribe gets unblocked in either case through + // the op.resp channel. + defer close(op.resp) + if msg.Error != nil { + op.err = msg.Error + return + } + if op.err = json.Unmarshal(msg.Result, &op.sub.subid); op.err == nil { + go op.sub.start() + h.clientSubs[op.sub.subid] = op.sub + } +} + +// handleCallMsg executes a call message and returns the answer. +func (h *handler) handleCallMsg(ctx *callProc, msg *jsonrpcMessage) *jsonrpcMessage { + start := time.Now() + switch { + case msg.isNotification(): + h.handleCall(ctx, msg) + h.log.Debug("Served "+msg.Method, "t", time.Since(start)) + return nil + case msg.isCall(): + resp := h.handleCall(ctx, msg) + if resp.Error != nil { + h.log.Info("Served "+msg.Method, "reqid", idForLog{msg.ID}, "t", time.Since(start), "err", resp.Error.Message) + } else { + h.log.Debug("Served "+msg.Method, "reqid", idForLog{msg.ID}, "t", time.Since(start)) + } + return resp + case msg.hasValidID(): + return msg.errorResponse(&invalidRequestError{"invalid request"}) + default: + return errorMessage(&invalidRequestError{"invalid request"}) + } +} + +// handleCall processes method calls. +func (h *handler) handleCall(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage { + if msg.isSubscribe() { + return h.handleSubscribe(cp, msg) + } + var callb *callback + if msg.isUnsubscribe() { + callb = h.unsubscribeCb + } else { + callb = h.reg.callback(msg.Method) + } + if callb == nil { + return msg.errorResponse(&methodNotFoundError{method: msg.Method}) + } + args, err := parsePositionalArguments(msg.Params, callb.argTypes) + if err != nil { + return msg.errorResponse(&invalidParamsError{err.Error()}) + } + + return h.runMethod(cp.ctx, msg, callb, args) +} + +// handleSubscribe processes *_subscribe method calls. +func (h *handler) handleSubscribe(cp *callProc, msg *jsonrpcMessage) *jsonrpcMessage { + if !h.allowSubscribe { + return msg.errorResponse(ErrNotificationsUnsupported) + } + + // Subscription method name is first argument. + name, err := parseSubscriptionName(msg.Params) + if err != nil { + return msg.errorResponse(&invalidParamsError{err.Error()}) + } + namespace := msg.namespace() + callb := h.reg.subscription(namespace, name) + if callb == nil { + return msg.errorResponse(&subscriptionNotFoundError{namespace, name}) + } + + // Parse subscription name arg too, but remove it before calling the callback. + argTypes := append([]reflect.Type{stringType}, callb.argTypes...) + args, err := parsePositionalArguments(msg.Params, argTypes) + if err != nil { + return msg.errorResponse(&invalidParamsError{err.Error()}) + } + args = args[1:] + + // Install notifier in context so the subscription handler can find it. + n := &Notifier{h: h, namespace: namespace} + cp.notifiers = append(cp.notifiers, n) + ctx := context.WithValue(cp.ctx, notifierKey{}, n) + + return h.runMethod(ctx, msg, callb, args) +} + +// runMethod runs the Go callback for an RPC method. +func (h *handler) runMethod(ctx context.Context, msg *jsonrpcMessage, callb *callback, args []reflect.Value) *jsonrpcMessage { + result, err := callb.call(ctx, msg.Method, args) + if err != nil { + return msg.errorResponse(err) + } + return msg.response(result) +} + +// unsubscribe is the callback function for all *_unsubscribe calls. +func (h *handler) unsubscribe(ctx context.Context, id ID) (bool, error) { + h.subLock.Lock() + defer h.subLock.Unlock() + + s := h.serverSubs[id] + if s == nil { + return false, ErrSubscriptionNotFound + } + close(s.err) + delete(h.serverSubs, id) + return true, nil +} + +type idForLog struct{ json.RawMessage } + +func (id idForLog) String() string { + if s, err := strconv.Unquote(string(id.RawMessage)); err == nil { + return s + } + return string(id.RawMessage) +} diff --git a/rpc/http.go b/rpc/http.go index 82aecfd7a05f..44f246759423 100644 --- a/rpc/http.go +++ b/rpc/http.go @@ -37,38 +37,39 @@ import ( const ( maxRequestContentLength = 1024 * 512 + contentType = "application/json" ) -var ( - // https://www.jsonrpc.org/historical/json-rpc-over-http.html#id13 - acceptedContentTypes = []string{"application/json", "application/json-rpc", "application/jsonrequest"} - contentType = acceptedContentTypes[0] - nullAddr, _ = net.ResolveTCPAddr("tcp", "127.0.0.1:0") -) +// https://www.jsonrpc.org/historical/json-rpc-over-http.html#id13 +var acceptedContentTypes = []string{contentType, "application/json-rpc", "application/jsonrequest"} type httpConn struct { client *http.Client req *http.Request closeOnce sync.Once - closed chan struct{} + closed chan interface{} } // httpConn is treated specially by Client. -func (hc *httpConn) LocalAddr() net.Addr { return nullAddr } -func (hc *httpConn) RemoteAddr() net.Addr { return nullAddr } -func (hc *httpConn) SetReadDeadline(time.Time) error { return nil } -func (hc *httpConn) SetWriteDeadline(time.Time) error { return nil } -func (hc *httpConn) SetDeadline(time.Time) error { return nil } -func (hc *httpConn) Write([]byte) (int, error) { panic("Write called") } - -func (hc *httpConn) Read(b []byte) (int, error) { +func (hc *httpConn) Write(context.Context, interface{}) error { + panic("Write called on httpConn") +} + +func (hc *httpConn) RemoteAddr() string { + return hc.req.URL.String() +} + +func (hc *httpConn) Read() ([]*jsonrpcMessage, bool, error) { <-hc.closed - return 0, io.EOF + return nil, false, io.EOF } -func (hc *httpConn) Close() error { +func (hc *httpConn) Close() { hc.closeOnce.Do(func() { close(hc.closed) }) - return nil +} + +func (hc *httpConn) Closed() <-chan interface{} { + return hc.closed } // HTTPTimeouts represents the configuration params for the HTTP RPC server. @@ -114,8 +115,8 @@ func DialHTTPWithClient(endpoint string, client *http.Client) (*Client, error) { req.Header.Set("Accept", contentType) initctx := context.Background() - return newClient(initctx, func(context.Context) (net.Conn, error) { - return &httpConn{client: client, req: req, closed: make(chan struct{})}, nil + return newClient(initctx, func(context.Context) (ServerCodec, error) { + return &httpConn{client: client, req: req, closed: make(chan interface{})}, nil }) } @@ -184,17 +185,30 @@ func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadClos return resp.Body, nil } -// httpReadWriteNopCloser wraps a io.Reader and io.Writer with a NOP Close method. -type httpReadWriteNopCloser struct { +// httpServerConn turns a HTTP connection into a Conn. +type httpServerConn struct { io.Reader io.Writer + r *http.Request } -// Close does nothing and returns always nil -func (t *httpReadWriteNopCloser) Close() error { - return nil +func newHTTPServerConn(r *http.Request, w http.ResponseWriter) ServerCodec { + body := io.LimitReader(r.Body, maxRequestContentLength) + conn := &httpServerConn{Reader: body, Writer: w, r: r} + return NewJSONCodec(conn) } +// Close does nothing and always returns nil. +func (t *httpServerConn) Close() error { return nil } + +// RemoteAddr returns the peer address of the underlying connection. +func (t *httpServerConn) RemoteAddr() string { + return t.r.RemoteAddr +} + +// SetWriteDeadline does nothing and always returns nil. +func (t *httpServerConn) SetWriteDeadline(time.Time) error { return nil } + // NewHTTPServer creates a new HTTP RPC server around an API provider. // // Deprecated: Server implements http.Handler @@ -226,7 +240,7 @@ func NewHTTPServer(cors []string, vhosts []string, timeouts HTTPTimeouts, srv *S } // ServeHTTP serves JSON-RPC requests over HTTP. -func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { // Permit dumb empty requests for remote health-checks (AWS) if r.Method == http.MethodGet && r.ContentLength == 0 && r.URL.RawQuery == "" { return @@ -249,12 +263,10 @@ func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { ctx = context.WithValue(ctx, "Origin", origin) } - body := io.LimitReader(r.Body, maxRequestContentLength) - codec := NewJSONCodec(&httpReadWriteNopCloser{body, w}) - defer codec.Close() - w.Header().Set("content-type", contentType) - srv.ServeSingleRequest(ctx, codec, OptionMethodInvocation) + codec := newHTTPServerConn(r, w) + defer codec.Close() + s.serveSingleRequest(ctx, codec) } // validateRequest returns a non-zero response code and error message if the diff --git a/rpc/inproc.go b/rpc/inproc.go index cbe65d10e76e..c4456cfc4bfc 100644 --- a/rpc/inproc.go +++ b/rpc/inproc.go @@ -24,10 +24,10 @@ import ( // DialInProc attaches an in-process connection to the given RPC server. func DialInProc(handler *Server) *Client { initctx := context.Background() - c, _ := newClient(initctx, func(context.Context) (net.Conn, error) { + c, _ := newClient(initctx, func(context.Context) (ServerCodec, error) { p1, p2 := net.Pipe() go handler.ServeCodec(NewJSONCodec(p1), OptionMethodInvocation|OptionSubscriptions) - return p2, nil + return NewJSONCodec(p2), nil }) return c } diff --git a/rpc/ipc.go b/rpc/ipc.go index 22cbe1335fd5..8e3e211a81ea 100644 --- a/rpc/ipc.go +++ b/rpc/ipc.go @@ -25,17 +25,17 @@ import ( ) // ServeListener accepts connections on l, serving JSON-RPC on them. -func (srv *Server) ServeListener(l net.Listener) error { +func (s *Server) ServeListener(l net.Listener) error { for { conn, err := l.Accept() if netutil.IsTemporaryError(err) { - log.Warn("IPC accept error", "err", err) + log.Warn("RPC accept error", "err", err) continue } else if err != nil { return err } - log.Trace("IPC accepted connection") - go srv.ServeCodec(NewJSONCodec(conn), OptionMethodInvocation|OptionSubscriptions) + log.Trace("Accepted RPC connection", "conn", conn.RemoteAddr()) + go s.ServeCodec(NewJSONCodec(conn), OptionMethodInvocation|OptionSubscriptions) } } @@ -46,7 +46,11 @@ func (srv *Server) ServeListener(l net.Listener) error { // The context is used for the initial connection establishment. It does not // affect subsequent interactions with the client. func DialIPC(ctx context.Context, endpoint string) (*Client, error) { - return newClient(ctx, func(ctx context.Context) (net.Conn, error) { - return newIPCConnection(ctx, endpoint) + return newClient(ctx, func(ctx context.Context) (ServerCodec, error) { + conn, err := newIPCConnection(ctx, endpoint) + if err != nil { + return nil, err + } + return NewJSONCodec(conn), err }) } diff --git a/rpc/json.go b/rpc/json.go index 0d3988726bad..b2e8c7bab3f3 100644 --- a/rpc/json.go +++ b/rpc/json.go @@ -18,71 +18,110 @@ package rpc import ( "bytes" + "context" "encoding/json" + "errors" "fmt" "io" "reflect" - "strconv" "strings" "sync" - - "github.com/ubiq/go-ubiq/log" + "time" ) const ( - jsonrpcVersion = "2.0" + vsn = "2.0" serviceMethodSeparator = "_" subscribeMethodSuffix = "_subscribe" unsubscribeMethodSuffix = "_unsubscribe" notificationMethodSuffix = "_subscription" + + defaultWriteTimeout = 10 * time.Second // used if context has no deadline ) -type jsonRequest struct { - Method string `json:"method"` - Version string `json:"jsonrpc"` - Id json.RawMessage `json:"id,omitempty"` - Payload json.RawMessage `json:"params,omitempty"` +var null = json.RawMessage("null") + +type subscriptionResult struct { + ID string `json:"subscription"` + Result json.RawMessage `json:"result,omitempty"` } -type jsonSuccessResponse struct { - Version string `json:"jsonrpc"` - Id interface{} `json:"id,omitempty"` - Result interface{} `json:"result"` +// A value of this type can a JSON-RPC request, notification, successful response or +// error response. Which one it is depends on the fields. +type jsonrpcMessage struct { + Version string `json:"jsonrpc,omitempty"` + ID json.RawMessage `json:"id,omitempty"` + Method string `json:"method,omitempty"` + Params json.RawMessage `json:"params,omitempty"` + Error *jsonError `json:"error,omitempty"` + Result json.RawMessage `json:"result,omitempty"` } -type jsonError struct { - Code int `json:"code"` - Message string `json:"message"` - Data interface{} `json:"data,omitempty"` +func (msg *jsonrpcMessage) isNotification() bool { + return msg.ID == nil && msg.Method != "" } -type jsonErrResponse struct { - Version string `json:"jsonrpc"` - Id interface{} `json:"id,omitempty"` - Error jsonError `json:"error"` +func (msg *jsonrpcMessage) isCall() bool { + return msg.hasValidID() && msg.Method != "" } -type jsonSubscription struct { - Subscription string `json:"subscription"` - Result interface{} `json:"result,omitempty"` +func (msg *jsonrpcMessage) isResponse() bool { + return msg.hasValidID() && msg.Method == "" && msg.Params == nil && (msg.Result != nil || msg.Error != nil) } -type jsonNotification struct { - Version string `json:"jsonrpc"` - Method string `json:"method"` - Params jsonSubscription `json:"params"` +func (msg *jsonrpcMessage) hasValidID() bool { + return len(msg.ID) > 0 && msg.ID[0] != '{' && msg.ID[0] != '[' } -// jsonCodec reads and writes JSON-RPC messages to the underlying connection. It -// also has support for parsing arguments and serializing (result) objects. -type jsonCodec struct { - closer sync.Once // close closed channel once - closed chan interface{} // closed on Close - decMu sync.Mutex // guards the decoder - decode func(v interface{}) error // decoder to allow multiple transports - encMu sync.Mutex // guards the encoder - encode func(v interface{}) error // encoder to allow multiple transports - rw io.ReadWriteCloser // connection +func (msg *jsonrpcMessage) isSubscribe() bool { + return strings.HasSuffix(msg.Method, subscribeMethodSuffix) +} + +func (msg *jsonrpcMessage) isUnsubscribe() bool { + return strings.HasSuffix(msg.Method, unsubscribeMethodSuffix) +} + +func (msg *jsonrpcMessage) namespace() string { + elem := strings.SplitN(msg.Method, serviceMethodSeparator, 2) + return elem[0] +} + +func (msg *jsonrpcMessage) String() string { + b, _ := json.Marshal(msg) + return string(b) +} + +func (msg *jsonrpcMessage) errorResponse(err error) *jsonrpcMessage { + resp := errorMessage(err) + resp.ID = msg.ID + return resp +} + +func (msg *jsonrpcMessage) response(result interface{}) *jsonrpcMessage { + enc, err := json.Marshal(result) + if err != nil { + // TODO: wrap with 'internal server error' + return msg.errorResponse(err) + } + return &jsonrpcMessage{Version: vsn, ID: msg.ID, Result: enc} +} + +func errorMessage(err error) *jsonrpcMessage { + msg := &jsonrpcMessage{Version: vsn, ID: null, Error: &jsonError{ + Code: defaultErrorCode, + Message: err.Error(), + }} + ec, ok := err.(Error) + if ok { + msg.Error.Code = ec.ErrorCode() + } + return msg +} + +type jsonError struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data,omitempty"` } func (err *jsonError) Error() string { @@ -96,268 +135,196 @@ func (err *jsonError) ErrorCode() int { return err.Code } +// Conn is a subset of the methods of net.Conn which are sufficient for ServerCodec. +type Conn interface { + io.ReadWriteCloser + SetWriteDeadline(time.Time) error +} + +// ConnRemoteAddr wraps the RemoteAddr operation, which returns a description +// of the peer address of a connection. If a Conn also implements ConnRemoteAddr, this +// description is used in log messages. +type ConnRemoteAddr interface { + RemoteAddr() string +} + +// connWithRemoteAddr overrides the remote address of a connection. +type connWithRemoteAddr struct { + Conn + addr string +} + +func (c connWithRemoteAddr) RemoteAddr() string { return c.addr } + +// jsonCodec reads and writes JSON-RPC messages to the underlying connection. It also has +// support for parsing arguments and serializing (result) objects. +type jsonCodec struct { + remoteAddr string + closer sync.Once // close closed channel once + closed chan interface{} // closed on Close + decode func(v interface{}) error // decoder to allow multiple transports + encMu sync.Mutex // guards the encoder + encode func(v interface{}) error // encoder to allow multiple transports + conn Conn +} + // NewCodec creates a new RPC server codec with support for JSON-RPC 2.0 based // on explicitly given encoding and decoding methods. -func NewCodec(rwc io.ReadWriteCloser, encode, decode func(v interface{}) error) ServerCodec { - return &jsonCodec{ +func NewCodec(conn Conn, encode, decode func(v interface{}) error) ServerCodec { + codec := &jsonCodec{ closed: make(chan interface{}), encode: encode, decode: decode, - rw: rwc, + conn: conn, } + if ra, ok := conn.(ConnRemoteAddr); ok { + codec.remoteAddr = ra.RemoteAddr() + } + return codec } // NewJSONCodec creates a new RPC server codec with support for JSON-RPC 2.0. -func NewJSONCodec(rwc io.ReadWriteCloser) ServerCodec { - enc := json.NewEncoder(rwc) - dec := json.NewDecoder(rwc) +func NewJSONCodec(conn Conn) ServerCodec { + enc := json.NewEncoder(conn) + dec := json.NewDecoder(conn) dec.UseNumber() + return NewCodec(conn, enc.Encode, dec.Decode) +} - return &jsonCodec{ - closed: make(chan interface{}), - encode: enc.Encode, - decode: dec.Decode, - rw: rwc, - } +func (c *jsonCodec) RemoteAddr() string { + return c.remoteAddr } -// isBatch returns true when the first non-whitespace characters is '[' -func isBatch(msg json.RawMessage) bool { - for _, c := range msg { - // skip insignificant whitespace (http://www.ietf.org/rfc/rfc4627.txt) - if c == 0x20 || c == 0x09 || c == 0x0a || c == 0x0d { - continue - } - return c == '[' +func (c *jsonCodec) Read() (msg []*jsonrpcMessage, batch bool, err error) { + // Decode the next JSON object in the input stream. + // This verifies basic syntax, etc. + var rawmsg json.RawMessage + if err := c.decode(&rawmsg); err != nil { + return nil, false, err } - return false + msg, batch = parseMessage(rawmsg) + return msg, batch, nil } -// ReadRequestHeaders will read new requests without parsing the arguments. It will -// return a collection of requests, an indication if these requests are in batch -// form or an error when the incoming message could not be read/parsed. -func (c *jsonCodec) ReadRequestHeaders() ([]rpcRequest, bool, Error) { - c.decMu.Lock() - defer c.decMu.Unlock() +// Write sends a message to client. +func (c *jsonCodec) Write(ctx context.Context, v interface{}) error { + c.encMu.Lock() + defer c.encMu.Unlock() - var incomingMsg json.RawMessage - if err := c.decode(&incomingMsg); err != nil { - return nil, false, &invalidRequestError{err.Error()} - } - if isBatch(incomingMsg) { - return parseBatchRequest(incomingMsg) + deadline, ok := ctx.Deadline() + if !ok { + deadline = time.Now().Add(defaultWriteTimeout) } - return parseRequest(incomingMsg) + c.conn.SetWriteDeadline(deadline) + return c.encode(v) } -// checkReqId returns an error when the given reqId isn't valid for RPC method calls. -// valid id's are strings, numbers or null -func checkReqId(reqId json.RawMessage) error { - if len(reqId) == 0 { - return fmt.Errorf("missing request id") - } - if _, err := strconv.ParseFloat(string(reqId), 64); err == nil { - return nil - } - var str string - if err := json.Unmarshal(reqId, &str); err == nil { - return nil - } - return fmt.Errorf("invalid request id") +// Close the underlying connection +func (c *jsonCodec) Close() { + c.closer.Do(func() { + close(c.closed) + c.conn.Close() + }) } -// parseRequest will parse a single request from the given RawMessage. It will return -// the parsed request, an indication if the request was a batch or an error when -// the request could not be parsed. -func parseRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, Error) { - var in jsonRequest - if err := json.Unmarshal(incomingMsg, &in); err != nil { - return nil, false, &invalidMessageError{err.Error()} - } - - if err := checkReqId(in.Id); err != nil { - return nil, false, &invalidMessageError{err.Error()} - } - - // subscribe are special, they will always use `subscribeMethod` as first param in the payload - if strings.HasSuffix(in.Method, subscribeMethodSuffix) { - reqs := []rpcRequest{{id: &in.Id, isPubSub: true}} - if len(in.Payload) > 0 { - // first param must be subscription name - var subscribeMethod [1]string - if err := json.Unmarshal(in.Payload, &subscribeMethod); err != nil { - log.Debug(fmt.Sprintf("Unable to parse subscription method: %v\n", err)) - return nil, false, &invalidRequestError{"Unable to parse subscription request"} - } - - reqs[0].service, reqs[0].method = strings.TrimSuffix(in.Method, subscribeMethodSuffix), subscribeMethod[0] - reqs[0].params = in.Payload - return reqs, false, nil - } - return nil, false, &invalidRequestError{"Unable to parse subscription request"} - } - - if strings.HasSuffix(in.Method, unsubscribeMethodSuffix) { - return []rpcRequest{{id: &in.Id, isPubSub: true, - method: in.Method, params: in.Payload}}, false, nil - } +// Closed returns a channel which will be closed when Close is called +func (c *jsonCodec) Closed() <-chan interface{} { + return c.closed +} - elems := strings.Split(in.Method, serviceMethodSeparator) - if len(elems) != 2 { - return nil, false, &methodNotFoundError{in.Method, ""} +// parseMessage parses raw bytes as a (batch of) JSON-RPC message(s). There are no error +// checks in this function because the raw message has already been syntax-checked when it +// is called. Any non-JSON-RPC messages in the input return the zero value of +// jsonrpcMessage. +func parseMessage(raw json.RawMessage) ([]*jsonrpcMessage, bool) { + if !isBatch(raw) { + msgs := []*jsonrpcMessage{{}} + json.Unmarshal(raw, &msgs[0]) + return msgs, false } - - // regular RPC call - if len(in.Payload) == 0 { - return []rpcRequest{{service: elems[0], method: elems[1], id: &in.Id}}, false, nil + dec := json.NewDecoder(bytes.NewReader(raw)) + dec.Token() // skip '[' + var msgs []*jsonrpcMessage + for dec.More() { + msgs = append(msgs, new(jsonrpcMessage)) + dec.Decode(&msgs[len(msgs)-1]) } - - return []rpcRequest{{service: elems[0], method: elems[1], id: &in.Id, params: in.Payload}}, false, nil + return msgs, true } -// parseBatchRequest will parse a batch request into a collection of requests from the given RawMessage, an indication -// if the request was a batch or an error when the request could not be read. -func parseBatchRequest(incomingMsg json.RawMessage) ([]rpcRequest, bool, Error) { - var in []jsonRequest - if err := json.Unmarshal(incomingMsg, &in); err != nil { - return nil, false, &invalidMessageError{err.Error()} - } - - requests := make([]rpcRequest, len(in)) - for i, r := range in { - if err := checkReqId(r.Id); err != nil { - return nil, false, &invalidMessageError{err.Error()} - } - - id := &in[i].Id - - // subscribe are special, they will always use `subscriptionMethod` as first param in the payload - if strings.HasSuffix(r.Method, subscribeMethodSuffix) { - requests[i] = rpcRequest{id: id, isPubSub: true} - if len(r.Payload) > 0 { - // first param must be subscription name - var subscribeMethod [1]string - if err := json.Unmarshal(r.Payload, &subscribeMethod); err != nil { - log.Debug(fmt.Sprintf("Unable to parse subscription method: %v\n", err)) - return nil, false, &invalidRequestError{"Unable to parse subscription request"} - } - - requests[i].service, requests[i].method = strings.TrimSuffix(r.Method, subscribeMethodSuffix), subscribeMethod[0] - requests[i].params = r.Payload - continue - } - - return nil, true, &invalidRequestError{"Unable to parse (un)subscribe request arguments"} - } - - if strings.HasSuffix(r.Method, unsubscribeMethodSuffix) { - requests[i] = rpcRequest{id: id, isPubSub: true, method: r.Method, params: r.Payload} +// isBatch returns true when the first non-whitespace characters is '[' +func isBatch(raw json.RawMessage) bool { + for _, c := range raw { + // skip insignificant whitespace (http://www.ietf.org/rfc/rfc4627.txt) + if c == 0x20 || c == 0x09 || c == 0x0a || c == 0x0d { continue } - - if len(r.Payload) == 0 { - requests[i] = rpcRequest{id: id, params: nil} - } else { - requests[i] = rpcRequest{id: id, params: r.Payload} - } - if elem := strings.Split(r.Method, serviceMethodSeparator); len(elem) == 2 { - requests[i].service, requests[i].method = elem[0], elem[1] - } else { - requests[i].err = &methodNotFoundError{r.Method, ""} - } - } - - return requests, true, nil -} - -// ParseRequestArguments tries to parse the given params (json.RawMessage) with the given -// types. It returns the parsed values or an error when the parsing failed. -func (c *jsonCodec) ParseRequestArguments(argTypes []reflect.Type, params interface{}) ([]reflect.Value, Error) { - if args, ok := params.(json.RawMessage); !ok { - return nil, &invalidParamsError{"Invalid params supplied"} - } else { - return parsePositionalArguments(args, argTypes) + return c == '[' } + return false } // parsePositionalArguments tries to parse the given args to an array of values with the // given types. It returns the parsed values or an error when the args could not be // parsed. Missing optional arguments are returned as reflect.Zero values. -func parsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([]reflect.Value, Error) { - // Read beginning of the args array. +func parsePositionalArguments(rawArgs json.RawMessage, types []reflect.Type) ([]reflect.Value, error) { dec := json.NewDecoder(bytes.NewReader(rawArgs)) - if tok, _ := dec.Token(); tok != json.Delim('[') { - return nil, &invalidParamsError{"non-array args"} + var args []reflect.Value + tok, err := dec.Token() + switch { + case err == io.EOF || tok == nil && err == nil: + // "params" is optional and may be empty. Also allow "params":null even though it's + // not in the spec because our own client used to send it. + case err != nil: + return nil, err + case tok == json.Delim('['): + // Read argument array. + if args, err = parseArgumentArray(dec, types); err != nil { + return nil, err + } + default: + return nil, errors.New("non-array args") } - // Read args. + // Set any missing args to nil. + for i := len(args); i < len(types); i++ { + if types[i].Kind() != reflect.Ptr { + return nil, fmt.Errorf("missing value for required argument %d", i) + } + args = append(args, reflect.Zero(types[i])) + } + return args, nil +} + +func parseArgumentArray(dec *json.Decoder, types []reflect.Type) ([]reflect.Value, error) { args := make([]reflect.Value, 0, len(types)) for i := 0; dec.More(); i++ { if i >= len(types) { - return nil, &invalidParamsError{fmt.Sprintf("too many arguments, want at most %d", len(types))} + return args, fmt.Errorf("too many arguments, want at most %d", len(types)) } argval := reflect.New(types[i]) if err := dec.Decode(argval.Interface()); err != nil { - return nil, &invalidParamsError{fmt.Sprintf("invalid argument %d: %v", i, err)} + return args, fmt.Errorf("invalid argument %d: %v", i, err) } if argval.IsNil() && types[i].Kind() != reflect.Ptr { - return nil, &invalidParamsError{fmt.Sprintf("missing value for required argument %d", i)} + return args, fmt.Errorf("missing value for required argument %d", i) } args = append(args, argval.Elem()) } // Read end of args array. - if _, err := dec.Token(); err != nil { - return nil, &invalidParamsError{err.Error()} - } - // Set any missing args to nil. - for i := len(args); i < len(types); i++ { - if types[i].Kind() != reflect.Ptr { - return nil, &invalidParamsError{fmt.Sprintf("missing value for required argument %d", i)} - } - args = append(args, reflect.Zero(types[i])) - } - return args, nil -} - -// CreateResponse will create a JSON-RPC success response with the given id and reply as result. -func (c *jsonCodec) CreateResponse(id interface{}, reply interface{}) interface{} { - return &jsonSuccessResponse{Version: jsonrpcVersion, Id: id, Result: reply} -} - -// CreateErrorResponse will create a JSON-RPC error response with the given id and error. -func (c *jsonCodec) CreateErrorResponse(id interface{}, err Error) interface{} { - return &jsonErrResponse{Version: jsonrpcVersion, Id: id, Error: jsonError{Code: err.ErrorCode(), Message: err.Error()}} -} - -// CreateErrorResponseWithInfo will create a JSON-RPC error response with the given id and error. -// info is optional and contains additional information about the error. When an empty string is passed it is ignored. -func (c *jsonCodec) CreateErrorResponseWithInfo(id interface{}, err Error, info interface{}) interface{} { - return &jsonErrResponse{Version: jsonrpcVersion, Id: id, - Error: jsonError{Code: err.ErrorCode(), Message: err.Error(), Data: info}} -} - -// CreateNotification will create a JSON-RPC notification with the given subscription id and event as params. -func (c *jsonCodec) CreateNotification(subid, namespace string, event interface{}) interface{} { - return &jsonNotification{Version: jsonrpcVersion, Method: namespace + notificationMethodSuffix, - Params: jsonSubscription{Subscription: subid, Result: event}} -} - -// Write message to client -func (c *jsonCodec) Write(res interface{}) error { - c.encMu.Lock() - defer c.encMu.Unlock() - - return c.encode(res) + _, err := dec.Token() + return args, err } -// Close the underlying connection -func (c *jsonCodec) Close() { - c.closer.Do(func() { - close(c.closed) - c.rw.Close() - }) -} - -// Closed returns a channel which will be closed when Close is called -func (c *jsonCodec) Closed() <-chan interface{} { - return c.closed +// parseSubscriptionName extracts the subscription name from an encoded argument array. +func parseSubscriptionName(rawArgs json.RawMessage) (string, error) { + dec := json.NewDecoder(bytes.NewReader(rawArgs)) + if tok, _ := dec.Token(); tok != json.Delim('[') { + return "", errors.New("non-array args") + } + v, _ := dec.Token() + method, ok := v.(string) + if !ok { + return "", errors.New("expected subscription name as first argument") + } + return method, nil } diff --git a/rpc/json_test.go b/rpc/json_test.go deleted file mode 100644 index 5048d2f7a067..000000000000 --- a/rpc/json_test.go +++ /dev/null @@ -1,178 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package rpc - -import ( - "bufio" - "bytes" - "encoding/json" - "reflect" - "strconv" - "testing" -) - -type RWC struct { - *bufio.ReadWriter -} - -func (rwc *RWC) Close() error { - return nil -} - -func TestJSONRequestParsing(t *testing.T) { - server := NewServer() - service := new(Service) - - if err := server.RegisterName("calc", service); err != nil { - t.Fatalf("%v", err) - } - - req := bytes.NewBufferString(`{"id": 1234, "jsonrpc": "2.0", "method": "calc_add", "params": [11, 22]}`) - var str string - reply := bytes.NewBufferString(str) - rw := &RWC{bufio.NewReadWriter(bufio.NewReader(req), bufio.NewWriter(reply))} - - codec := NewJSONCodec(rw) - - requests, batch, err := codec.ReadRequestHeaders() - if err != nil { - t.Fatalf("%v", err) - } - - if batch { - t.Fatalf("Request isn't a batch") - } - - if len(requests) != 1 { - t.Fatalf("Expected 1 request but got %d requests - %v", len(requests), requests) - } - - if requests[0].service != "calc" { - t.Fatalf("Expected service 'calc' but got '%s'", requests[0].service) - } - - if requests[0].method != "add" { - t.Fatalf("Expected method 'Add' but got '%s'", requests[0].method) - } - - if rawId, ok := requests[0].id.(*json.RawMessage); ok { - id, e := strconv.ParseInt(string(*rawId), 0, 64) - if e != nil { - t.Fatalf("%v", e) - } - if id != 1234 { - t.Fatalf("Expected id 1234 but got %d", id) - } - } else { - t.Fatalf("invalid request, expected *json.RawMesage got %T", requests[0].id) - } - - var arg int - args := []reflect.Type{reflect.TypeOf(arg), reflect.TypeOf(arg)} - - v, err := codec.ParseRequestArguments(args, requests[0].params) - if err != nil { - t.Fatalf("%v", err) - } - - if len(v) != 2 { - t.Fatalf("Expected 2 argument values, got %d", len(v)) - } - - if v[0].Int() != 11 || v[1].Int() != 22 { - t.Fatalf("expected %d == 11 && %d == 22", v[0].Int(), v[1].Int()) - } -} - -func TestJSONRequestParamsParsing(t *testing.T) { - - var ( - stringT = reflect.TypeOf("") - intT = reflect.TypeOf(0) - intPtrT = reflect.TypeOf(new(int)) - - stringV = reflect.ValueOf("abc") - i = 1 - intV = reflect.ValueOf(i) - intPtrV = reflect.ValueOf(&i) - ) - - var validTests = []struct { - input string - argTypes []reflect.Type - expected []reflect.Value - }{ - {`[]`, []reflect.Type{}, []reflect.Value{}}, - {`[]`, []reflect.Type{intPtrT}, []reflect.Value{intPtrV}}, - {`[1]`, []reflect.Type{intT}, []reflect.Value{intV}}, - {`[1,"abc"]`, []reflect.Type{intT, stringT}, []reflect.Value{intV, stringV}}, - {`[null]`, []reflect.Type{intPtrT}, []reflect.Value{intPtrV}}, - {`[null,"abc"]`, []reflect.Type{intPtrT, stringT, intPtrT}, []reflect.Value{intPtrV, stringV, intPtrV}}, - {`[null,"abc",null]`, []reflect.Type{intPtrT, stringT, intPtrT}, []reflect.Value{intPtrV, stringV, intPtrV}}, - } - - codec := jsonCodec{} - - for _, test := range validTests { - params := (json.RawMessage)([]byte(test.input)) - args, err := codec.ParseRequestArguments(test.argTypes, params) - - if err != nil { - t.Fatal(err) - } - - var match []interface{} - json.Unmarshal([]byte(test.input), &match) - - if len(args) != len(test.argTypes) { - t.Fatalf("expected %d parsed args, got %d", len(test.argTypes), len(args)) - } - - for i, arg := range args { - expected := test.expected[i] - - if arg.Kind() != expected.Kind() { - t.Errorf("expected type for param %d in %s", i, test.input) - } - - if arg.Kind() == reflect.Int && arg.Int() != expected.Int() { - t.Errorf("expected int(%d), got int(%d) in %s", expected.Int(), arg.Int(), test.input) - } - - if arg.Kind() == reflect.String && arg.String() != expected.String() { - t.Errorf("expected string(%s), got string(%s) in %s", expected.String(), arg.String(), test.input) - } - } - } - - var invalidTests = []struct { - input string - argTypes []reflect.Type - }{ - {`[]`, []reflect.Type{intT}}, - {`[null]`, []reflect.Type{intT}}, - {`[1]`, []reflect.Type{stringT}}, - {`[1,2]`, []reflect.Type{stringT}}, - {`["abc", null]`, []reflect.Type{stringT, intT}}, - } - - for i, test := range invalidTests { - if _, err := codec.ParseRequestArguments(test.argTypes, test.input); err == nil { - t.Errorf("expected test %d - %s to fail", i, test.input) - } - } -} diff --git a/rpc/server.go b/rpc/server.go index 43fe091028ea..bf8da478319f 100644 --- a/rpc/server.go +++ b/rpc/server.go @@ -18,11 +18,7 @@ package rpc import ( "context" - "fmt" - "reflect" - "runtime" - "strings" - "sync" + "io" "sync/atomic" mapset "github.com/deckarep/golang-set" @@ -31,7 +27,9 @@ import ( const MetadataApi = "rpc" -// CodecOption specifies which type of messages this codec supports +// CodecOption specifies which type of messages a codec supports. +// +// Deprecated: this option is no longer honored by Server. type CodecOption int const ( @@ -42,194 +40,87 @@ const ( OptionSubscriptions = 1 << iota // support pub sub ) -// NewServer will create a new server instance with no registered handlers. -func NewServer() *Server { - server := &Server{ - services: make(serviceRegistry), - codecs: mapset.NewSet(), - run: 1, - } +// Server is an RPC server. +type Server struct { + services serviceRegistry + idgen func() ID + run int32 + codecs mapset.Set +} - // register a default service which will provide meta information about the RPC service such as the services and - // methods it offers. +// NewServer creates a new server instance with no registered handlers. +func NewServer() *Server { + server := &Server{idgen: randomIDGenerator(), codecs: mapset.NewSet(), run: 1} + // Register the default service providing meta information about the RPC service such + // as the services and methods it offers. rpcService := &RPCService{server} server.RegisterName(MetadataApi, rpcService) - return server } -// RPCService gives meta information about the server. -// e.g. gives information about the loaded modules. -type RPCService struct { - server *Server -} - -// Modules returns the list of RPC services with their version number -func (s *RPCService) Modules() map[string]string { - modules := make(map[string]string) - for name := range s.server.services { - modules[name] = "1.0" - } - return modules +// RegisterName creates a service for the given receiver type under the given name. When no +// methods on the given receiver match the criteria to be either a RPC method or a +// subscription an error is returned. Otherwise a new service is created and added to the +// service collection this server provides to clients. +func (s *Server) RegisterName(name string, receiver interface{}) error { + return s.services.registerName(name, receiver) } -// RegisterName will create a service for the given rcvr type under the given name. When no methods on the given rcvr -// match the criteria to be either a RPC method or a subscription an error is returned. Otherwise a new service is -// created and added to the service collection this server instance serves. -func (s *Server) RegisterName(name string, rcvr interface{}) error { - if s.services == nil { - s.services = make(serviceRegistry) - } - - svc := new(service) - svc.typ = reflect.TypeOf(rcvr) - rcvrVal := reflect.ValueOf(rcvr) - - if name == "" { - return fmt.Errorf("no service name for type %s", svc.typ.String()) - } - if !isExported(reflect.Indirect(rcvrVal).Type().Name()) { - return fmt.Errorf("%s is not exported", reflect.Indirect(rcvrVal).Type().Name()) - } - - methods, subscriptions := suitableCallbacks(rcvrVal, svc.typ) +// ServeCodec reads incoming requests from codec, calls the appropriate callback and writes +// the response back using the given codec. It will block until the codec is closed or the +// server is stopped. In either case the codec is closed. +// +// Note that codec options are no longer supported. +func (s *Server) ServeCodec(codec ServerCodec, options CodecOption) { + defer codec.Close() - if len(methods) == 0 && len(subscriptions) == 0 { - return fmt.Errorf("Service %T doesn't have any suitable methods/subscriptions to expose", rcvr) + // Don't serve if server is stopped. + if atomic.LoadInt32(&s.run) == 0 { + return } - // already a previous service register under given name, merge methods/subscriptions - if regsvc, present := s.services[name]; present { - for _, m := range methods { - regsvc.callbacks[formatName(m.method.Name)] = m - } - for _, s := range subscriptions { - regsvc.subscriptions[formatName(s.method.Name)] = s - } - return nil - } - - svc.name = name - svc.callbacks, svc.subscriptions = methods, subscriptions + // Add the codec to the set so it can be closed by Stop. + s.codecs.Add(codec) + defer s.codecs.Remove(codec) - s.services[svc.name] = svc - return nil + c := initClient(codec, s.idgen, &s.services) + <-codec.Closed() + c.Close() } -// serveRequest will reads requests from the codec, calls the RPC callback and -// writes the response to the given codec. -// -// If singleShot is true it will process a single request, otherwise it will handle -// requests until the codec returns an error when reading a request (in most cases -// an EOF). It executes requests in parallel when singleShot is false. -func (s *Server) serveRequest(ctx context.Context, codec ServerCodec, singleShot bool, options CodecOption) error { - var pend sync.WaitGroup - - defer func() { - if err := recover(); err != nil { - const size = 64 << 10 - buf := make([]byte, size) - buf = buf[:runtime.Stack(buf, false)] - log.Error(string(buf)) - } - s.codecsMu.Lock() - s.codecs.Remove(codec) - s.codecsMu.Unlock() - }() - - // ctx, cancel := context.WithCancel(context.Background()) - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - // if the codec supports notification include a notifier that callbacks can use - // to send notification to clients. It is tied to the codec/connection. If the - // connection is closed the notifier will stop and cancels all active subscriptions. - if options&OptionSubscriptions == OptionSubscriptions { - ctx = context.WithValue(ctx, notifierKey{}, newNotifier(codec)) - } - s.codecsMu.Lock() - if atomic.LoadInt32(&s.run) != 1 { // server stopped - s.codecsMu.Unlock() - return &shutdownError{} +// serveSingleRequest reads and processes a single RPC request from the given codec. This +// is used to serve HTTP connections. Subscriptions and reverse calls are not allowed in +// this mode. +func (s *Server) serveSingleRequest(ctx context.Context, codec ServerCodec) { + // Don't serve if server is stopped. + if atomic.LoadInt32(&s.run) == 0 { + return } - s.codecs.Add(codec) - s.codecsMu.Unlock() - // test if the server is ordered to stop - for atomic.LoadInt32(&s.run) == 1 { - reqs, batch, err := s.readRequest(codec) - if err != nil { - // If a parsing error occurred, send an error - if err.Error() != "EOF" { - log.Debug(fmt.Sprintf("read error %v\n", err)) - codec.Write(codec.CreateErrorResponse(nil, err)) - } - // Error or end of stream, wait for requests and tear down - pend.Wait() - return nil - } + h := newHandler(ctx, codec, s.idgen, &s.services) + h.allowSubscribe = false + defer h.close(io.EOF, nil) - // check if server is ordered to shutdown and return an error - // telling the client that his request failed. - if atomic.LoadInt32(&s.run) != 1 { - err = &shutdownError{} - if batch { - resps := make([]interface{}, len(reqs)) - for i, r := range reqs { - resps[i] = codec.CreateErrorResponse(&r.id, err) - } - codec.Write(resps) - } else { - codec.Write(codec.CreateErrorResponse(&reqs[0].id, err)) - } - return nil - } - // If a single shot request is executing, run and return immediately - if singleShot { - if batch { - s.execBatch(ctx, codec, reqs) - } else { - s.exec(ctx, codec, reqs[0]) - } - return nil + reqs, batch, err := codec.Read() + if err != nil { + if err != io.EOF { + codec.Write(ctx, errorMessage(&invalidMessageError{"parse error"})) } - // For multi-shot connections, start a goroutine to serve and loop back - pend.Add(1) - - go func(reqs []*serverRequest, batch bool) { - defer pend.Done() - if batch { - s.execBatch(ctx, codec, reqs) - } else { - s.exec(ctx, codec, reqs[0]) - } - }(reqs, batch) + return + } + if batch { + h.handleBatch(reqs) + } else { + h.handleMsg(reqs[0]) } - return nil -} - -// ServeCodec reads incoming requests from codec, calls the appropriate callback and writes the -// response back using the given codec. It will block until the codec is closed or the server is -// stopped. In either case the codec is closed. -func (s *Server) ServeCodec(codec ServerCodec, options CodecOption) { - defer codec.Close() - s.serveRequest(context.Background(), codec, false, options) -} - -// ServeSingleRequest reads and processes a single RPC request from the given codec. It will not -// close the codec unless a non-recoverable error has occurred. Note, this method will return after -// a single request has been processed! -func (s *Server) ServeSingleRequest(ctx context.Context, codec ServerCodec, options CodecOption) { - s.serveRequest(ctx, codec, true, options) } -// Stop will stop reading new requests, wait for stopPendingRequestTimeout to allow pending requests to finish, -// close all codecs which will cancel pending requests/subscriptions. +// Stop stops reading new requests, waits for stopPendingRequestTimeout to allow pending +// requests to finish, then closes all codecs which will cancel pending requests and +// subscriptions. func (s *Server) Stop() { if atomic.CompareAndSwapInt32(&s.run, 1, 0) { - log.Debug("RPC Server shutdown initiatied") - s.codecsMu.Lock() - defer s.codecsMu.Unlock() + log.Debug("RPC server shutting down") s.codecs.Each(func(c interface{}) bool { c.(ServerCodec).Close() return true @@ -237,206 +128,20 @@ func (s *Server) Stop() { } } -// createSubscription will call the subscription callback and returns the subscription id or error. -func (s *Server) createSubscription(ctx context.Context, c ServerCodec, req *serverRequest) (ID, error) { - // subscription have as first argument the context following optional arguments - args := []reflect.Value{req.callb.rcvr, reflect.ValueOf(ctx)} - args = append(args, req.args...) - reply := req.callb.method.Func.Call(args) - - if !reply[1].IsNil() { // subscription creation failed - return "", reply[1].Interface().(error) - } - - return reply[0].Interface().(*Subscription).ID, nil -} - -// handle executes a request and returns the response from the callback. -func (s *Server) handle(ctx context.Context, codec ServerCodec, req *serverRequest) (interface{}, func()) { - if req.err != nil { - return codec.CreateErrorResponse(&req.id, req.err), nil - } - - if req.isUnsubscribe { // cancel subscription, first param must be the subscription id - if len(req.args) >= 1 && req.args[0].Kind() == reflect.String { - notifier, supported := NotifierFromContext(ctx) - if !supported { // interface doesn't support subscriptions (e.g. http) - return codec.CreateErrorResponse(&req.id, &callbackError{ErrNotificationsUnsupported.Error()}), nil - } - - subid := ID(req.args[0].String()) - if err := notifier.unsubscribe(subid); err != nil { - return codec.CreateErrorResponse(&req.id, &callbackError{err.Error()}), nil - } - - return codec.CreateResponse(req.id, true), nil - } - return codec.CreateErrorResponse(&req.id, &invalidParamsError{"Expected subscription id as first argument"}), nil - } - - if req.callb.isSubscribe { - subid, err := s.createSubscription(ctx, codec, req) - if err != nil { - return codec.CreateErrorResponse(&req.id, &callbackError{err.Error()}), nil - } - - // active the subscription after the sub id was successfully sent to the client - activateSub := func() { - notifier, _ := NotifierFromContext(ctx) - notifier.activate(subid, req.svcname) - } - - return codec.CreateResponse(req.id, subid), activateSub - } - - // regular RPC call, prepare arguments - if len(req.args) != len(req.callb.argTypes) { - rpcErr := &invalidParamsError{fmt.Sprintf("%s%s%s expects %d parameters, got %d", - req.svcname, serviceMethodSeparator, req.callb.method.Name, - len(req.callb.argTypes), len(req.args))} - return codec.CreateErrorResponse(&req.id, rpcErr), nil - } - - arguments := []reflect.Value{req.callb.rcvr} - if req.callb.hasCtx { - arguments = append(arguments, reflect.ValueOf(ctx)) - } - if len(req.args) > 0 { - arguments = append(arguments, req.args...) - } - - // execute RPC method and return result - reply := req.callb.method.Func.Call(arguments) - if len(reply) == 0 { - return codec.CreateResponse(req.id, nil), nil - } - if req.callb.errPos >= 0 { // test if method returned an error - if !reply[req.callb.errPos].IsNil() { - e := reply[req.callb.errPos].Interface().(error) - res := codec.CreateErrorResponse(&req.id, &callbackError{e.Error()}) - return res, nil - } - } - return codec.CreateResponse(req.id, reply[0].Interface()), nil -} - -// exec executes the given request and writes the result back using the codec. -func (s *Server) exec(ctx context.Context, codec ServerCodec, req *serverRequest) { - var response interface{} - var callback func() - if req.err != nil { - response = codec.CreateErrorResponse(&req.id, req.err) - } else { - response, callback = s.handle(ctx, codec, req) - } - - if err := codec.Write(response); err != nil { - log.Error(fmt.Sprintf("%v\n", err)) - codec.Close() - } - - // when request was a subscribe request this allows these subscriptions to be actived - if callback != nil { - callback() - } -} - -// execBatch executes the given requests and writes the result back using the codec. -// It will only write the response back when the last request is processed. -func (s *Server) execBatch(ctx context.Context, codec ServerCodec, requests []*serverRequest) { - responses := make([]interface{}, len(requests)) - var callbacks []func() - for i, req := range requests { - if req.err != nil { - responses[i] = codec.CreateErrorResponse(&req.id, req.err) - } else { - var callback func() - if responses[i], callback = s.handle(ctx, codec, req); callback != nil { - callbacks = append(callbacks, callback) - } - } - } - - if err := codec.Write(responses); err != nil { - log.Error(fmt.Sprintf("%v\n", err)) - codec.Close() - } - - // when request holds one of more subscribe requests this allows these subscriptions to be activated - for _, c := range callbacks { - c() - } +// RPCService gives meta information about the server. +// e.g. gives information about the loaded modules. +type RPCService struct { + server *Server } -// readRequest requests the next (batch) request from the codec. It will return the collection -// of requests, an indication if the request was a batch, the invalid request identifier and an -// error when the request could not be read/parsed. -func (s *Server) readRequest(codec ServerCodec) ([]*serverRequest, bool, Error) { - reqs, batch, err := codec.ReadRequestHeaders() - if err != nil { - return nil, batch, err - } - - requests := make([]*serverRequest, len(reqs)) - - // verify requests - for i, r := range reqs { - var ok bool - var svc *service - - if r.err != nil { - requests[i] = &serverRequest{id: r.id, err: r.err} - continue - } - - if r.isPubSub && strings.HasSuffix(r.method, unsubscribeMethodSuffix) { - requests[i] = &serverRequest{id: r.id, isUnsubscribe: true} - argTypes := []reflect.Type{reflect.TypeOf("")} // expect subscription id as first arg - if args, err := codec.ParseRequestArguments(argTypes, r.params); err == nil { - requests[i].args = args - } else { - requests[i].err = &invalidParamsError{err.Error()} - } - continue - } - - if svc, ok = s.services[r.service]; !ok { // rpc method isn't available - requests[i] = &serverRequest{id: r.id, err: &methodNotFoundError{r.service, r.method}} - continue - } - - if r.isPubSub { // eth_subscribe, r.method contains the subscription method name - if callb, ok := svc.subscriptions[r.method]; ok { - requests[i] = &serverRequest{id: r.id, svcname: svc.name, callb: callb} - if r.params != nil && len(callb.argTypes) > 0 { - argTypes := []reflect.Type{reflect.TypeOf("")} - argTypes = append(argTypes, callb.argTypes...) - if args, err := codec.ParseRequestArguments(argTypes, r.params); err == nil { - requests[i].args = args[1:] // first one is service.method name which isn't an actual argument - } else { - requests[i].err = &invalidParamsError{err.Error()} - } - } - } else { - requests[i] = &serverRequest{id: r.id, err: &methodNotFoundError{r.service, r.method}} - } - continue - } - - if callb, ok := svc.callbacks[r.method]; ok { // lookup RPC method - requests[i] = &serverRequest{id: r.id, svcname: svc.name, callb: callb} - if r.params != nil && len(callb.argTypes) > 0 { - if args, err := codec.ParseRequestArguments(callb.argTypes, r.params); err == nil { - requests[i].args = args - } else { - requests[i].err = &invalidParamsError{err.Error()} - } - } - continue - } +// Modules returns the list of RPC services with their version number +func (s *RPCService) Modules() map[string]string { + s.server.services.mu.Lock() + defer s.server.services.mu.Unlock() - requests[i] = &serverRequest{id: r.id, err: &methodNotFoundError{r.service, r.method}} + modules := make(map[string]string) + for name := range s.server.services.services { + modules[name] = "1.0" } - - return requests, batch, nil + return modules } diff --git a/rpc/server_test.go b/rpc/server_test.go index 90d62f26d8f6..39099546bbe0 100644 --- a/rpc/server_test.go +++ b/rpc/server_test.go @@ -17,146 +17,136 @@ package rpc import ( - "context" - "encoding/json" + "bufio" + "bytes" + "io" + "io/ioutil" "net" - "reflect" + "path/filepath" + "strings" "testing" "time" ) -type Service struct{} - -type Args struct { - S string -} - -func (s *Service) NoArgsRets() { -} - -type Result struct { - String string - Int int - Args *Args -} - -func (s *Service) Echo(str string, i int, args *Args) Result { - return Result{str, i, args} -} - -func (s *Service) EchoWithCtx(ctx context.Context, str string, i int, args *Args) Result { - return Result{str, i, args} -} - -func (s *Service) Sleep(ctx context.Context, duration time.Duration) { - select { - case <-time.After(duration): - case <-ctx.Done(): - } -} - -func (s *Service) Rets() (string, error) { - return "", nil -} - -func (s *Service) InvalidRets1() (error, string) { - return nil, "" -} - -func (s *Service) InvalidRets2() (string, string) { - return "", "" -} - -func (s *Service) InvalidRets3() (string, string, error) { - return "", "", nil -} - -func (s *Service) Subscription(ctx context.Context) (*Subscription, error) { - return nil, nil -} - func TestServerRegisterName(t *testing.T) { server := NewServer() - service := new(Service) + service := new(testService) - if err := server.RegisterName("calc", service); err != nil { + if err := server.RegisterName("test", service); err != nil { t.Fatalf("%v", err) } - if len(server.services) != 2 { - t.Fatalf("Expected 2 service entries, got %d", len(server.services)) + if len(server.services.services) != 2 { + t.Fatalf("Expected 2 service entries, got %d", len(server.services.services)) } - svc, ok := server.services["calc"] + svc, ok := server.services.services["test"] if !ok { t.Fatalf("Expected service calc to be registered") } - if len(svc.callbacks) != 5 { - t.Errorf("Expected 5 callbacks for service 'calc', got %d", len(svc.callbacks)) - } - - if len(svc.subscriptions) != 1 { - t.Errorf("Expected 1 subscription for service 'calc', got %d", len(svc.subscriptions)) + wantCallbacks := 7 + if len(svc.callbacks) != wantCallbacks { + t.Errorf("Expected %d callbacks for service 'service', got %d", wantCallbacks, len(svc.callbacks)) } } -func testServerMethodExecution(t *testing.T, method string) { - server := NewServer() - service := new(Service) - - if err := server.RegisterName("test", service); err != nil { - t.Fatalf("%v", err) +func TestServer(t *testing.T) { + files, err := ioutil.ReadDir("testdata") + if err != nil { + t.Fatal("where'd my testdata go?") } + for _, f := range files { + if f.IsDir() || strings.HasPrefix(f.Name(), ".") { + continue + } + path := filepath.Join("testdata", f.Name()) + name := strings.TrimSuffix(f.Name(), filepath.Ext(f.Name())) + t.Run(name, func(t *testing.T) { + runTestScript(t, path) + }) + } +} - stringArg := "string arg" - intArg := 1122 - argsArg := &Args{"abcde"} - params := []interface{}{stringArg, intArg, argsArg} - - request := map[string]interface{}{ - "id": 12345, - "method": "test_" + method, - "version": "2.0", - "params": params, +func runTestScript(t *testing.T, file string) { + server := newTestServer() + content, err := ioutil.ReadFile(file) + if err != nil { + t.Fatal(err) } clientConn, serverConn := net.Pipe() defer clientConn.Close() - - go server.ServeCodec(NewJSONCodec(serverConn), OptionMethodInvocation) - - out := json.NewEncoder(clientConn) - in := json.NewDecoder(clientConn) - - if err := out.Encode(request); err != nil { - t.Fatal(err) + go server.ServeCodec(NewJSONCodec(serverConn), OptionMethodInvocation|OptionSubscriptions) + readbuf := bufio.NewReader(clientConn) + for _, line := range strings.Split(string(content), "\n") { + line = strings.TrimSpace(line) + switch { + case len(line) == 0 || strings.HasPrefix(line, "//"): + // skip comments, blank lines + continue + case strings.HasPrefix(line, "--> "): + t.Log(line) + // write to connection + clientConn.SetWriteDeadline(time.Now().Add(5 * time.Second)) + if _, err := io.WriteString(clientConn, line[4:]+"\n"); err != nil { + t.Fatalf("write error: %v", err) + } + case strings.HasPrefix(line, "<-- "): + t.Log(line) + want := line[4:] + // read line from connection and compare text + clientConn.SetReadDeadline(time.Now().Add(5 * time.Second)) + sent, err := readbuf.ReadString('\n') + if err != nil { + t.Fatalf("read error: %v", err) + } + sent = strings.TrimRight(sent, "\r\n") + if sent != want { + t.Errorf("wrong line from server\ngot: %s\nwant: %s", sent, want) + } + default: + panic("invalid line in test script: " + line) + } } +} - response := jsonSuccessResponse{Result: &Result{}} - if err := in.Decode(&response); err != nil { - t.Fatal(err) - } +// This test checks that responses are delivered for very short-lived connections that +// only carry a single request. +func TestServerShortLivedConn(t *testing.T) { + server := newTestServer() + defer server.Stop() - if result, ok := response.Result.(*Result); ok { - if result.String != stringArg { - t.Errorf("expected %s, got : %s\n", stringArg, result.String) + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + t.Fatal("can't listen:", err) + } + defer listener.Close() + go server.ServeListener(listener) + + var ( + request = `{"jsonrpc":"2.0","id":1,"method":"rpc_modules"}` + "\n" + wantResp = `{"jsonrpc":"2.0","id":1,"result":{"nftest":"1.0","rpc":"1.0","test":"1.0"}}` + "\n" + deadline = time.Now().Add(10 * time.Second) + ) + for i := 0; i < 20; i++ { + conn, err := net.Dial("tcp", listener.Addr().String()) + if err != nil { + t.Fatal("can't dial:", err) } - if result.Int != intArg { - t.Errorf("expected %d, got %d\n", intArg, result.Int) + defer conn.Close() + conn.SetDeadline(deadline) + // Write the request, then half-close the connection so the server stops reading. + conn.Write([]byte(request)) + conn.(*net.TCPConn).CloseWrite() + // Now try to get the response. + buf := make([]byte, 2000) + n, err := conn.Read(buf) + if err != nil { + t.Fatal("read error:", err) } - if !reflect.DeepEqual(result.Args, argsArg) { - t.Errorf("expected %v, got %v\n", argsArg, result) + if !bytes.Equal(buf[:n], []byte(wantResp)) { + t.Fatalf("wrong response: %s", buf[:n]) } - } else { - t.Fatalf("invalid response: expected *Result - got: %T", response.Result) } } - -func TestServerMethodExecution(t *testing.T) { - testServerMethodExecution(t, "echo") -} - -func TestServerMethodWithCtx(t *testing.T) { - testServerMethodExecution(t, "echoWithCtx") -} diff --git a/rpc/service.go b/rpc/service.go new file mode 100644 index 000000000000..26700fe7a8a0 --- /dev/null +++ b/rpc/service.go @@ -0,0 +1,285 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rpc + +import ( + "context" + "errors" + "fmt" + "reflect" + "runtime" + "strings" + "sync" + "unicode" + "unicode/utf8" + + "github.com/ubiq/go-ubiq/log" +) + +var ( + contextType = reflect.TypeOf((*context.Context)(nil)).Elem() + errorType = reflect.TypeOf((*error)(nil)).Elem() + subscriptionType = reflect.TypeOf(Subscription{}) + stringType = reflect.TypeOf("") +) + +type serviceRegistry struct { + mu sync.Mutex + services map[string]service +} + +// service represents a registered object. +type service struct { + name string // name for service + callbacks map[string]*callback // registered handlers + subscriptions map[string]*callback // available subscriptions/notifications +} + +// callback is a method callback which was registered in the server +type callback struct { + fn reflect.Value // the function + rcvr reflect.Value // receiver object of method, set if fn is method + argTypes []reflect.Type // input argument types + hasCtx bool // method's first argument is a context (not included in argTypes) + errPos int // err return idx, of -1 when method cannot return error + isSubscribe bool // true if this is a subscription callback +} + +func (r *serviceRegistry) registerName(name string, rcvr interface{}) error { + rcvrVal := reflect.ValueOf(rcvr) + if name == "" { + return fmt.Errorf("no service name for type %s", rcvrVal.Type().String()) + } + callbacks := suitableCallbacks(rcvrVal) + if len(callbacks) == 0 { + return fmt.Errorf("service %T doesn't have any suitable methods/subscriptions to expose", rcvr) + } + + r.mu.Lock() + defer r.mu.Unlock() + if r.services == nil { + r.services = make(map[string]service) + } + svc, ok := r.services[name] + if !ok { + svc = service{ + name: name, + callbacks: make(map[string]*callback), + subscriptions: make(map[string]*callback), + } + r.services[name] = svc + } + for name, cb := range callbacks { + if cb.isSubscribe { + svc.subscriptions[name] = cb + } else { + svc.callbacks[name] = cb + } + } + return nil +} + +// callback returns the callback corresponding to the given RPC method name. +func (r *serviceRegistry) callback(method string) *callback { + elem := strings.SplitN(method, serviceMethodSeparator, 2) + if len(elem) != 2 { + return nil + } + r.mu.Lock() + defer r.mu.Unlock() + return r.services[elem[0]].callbacks[elem[1]] +} + +// subscription returns a subscription callback in the given service. +func (r *serviceRegistry) subscription(service, name string) *callback { + r.mu.Lock() + defer r.mu.Unlock() + return r.services[service].subscriptions[name] +} + +// suitableCallbacks iterates over the methods of the given type. It determines if a method +// satisfies the criteria for a RPC callback or a subscription callback and adds it to the +// collection of callbacks. See server documentation for a summary of these criteria. +func suitableCallbacks(receiver reflect.Value) map[string]*callback { + typ := receiver.Type() + callbacks := make(map[string]*callback) + for m := 0; m < typ.NumMethod(); m++ { + method := typ.Method(m) + if method.PkgPath != "" { + continue // method not exported + } + cb := newCallback(receiver, method.Func) + if cb == nil { + continue // function invalid + } + name := formatName(method.Name) + callbacks[name] = cb + } + return callbacks +} + +// newCallback turns fn (a function) into a callback object. It returns nil if the function +// is unsuitable as an RPC callback. +func newCallback(receiver, fn reflect.Value) *callback { + fntype := fn.Type() + c := &callback{fn: fn, rcvr: receiver, errPos: -1, isSubscribe: isPubSub(fntype)} + // Determine parameter types. They must all be exported or builtin types. + c.makeArgTypes() + if !allExportedOrBuiltin(c.argTypes) { + return nil + } + // Verify return types. The function must return at most one error + // and/or one other non-error value. + outs := make([]reflect.Type, fntype.NumOut()) + for i := 0; i < fntype.NumOut(); i++ { + outs[i] = fntype.Out(i) + } + if len(outs) > 2 || !allExportedOrBuiltin(outs) { + return nil + } + // If an error is returned, it must be the last returned value. + switch { + case len(outs) == 1 && isErrorType(outs[0]): + c.errPos = 0 + case len(outs) == 2: + if isErrorType(outs[0]) || !isErrorType(outs[1]) { + return nil + } + c.errPos = 1 + } + return c +} + +// makeArgTypes composes the argTypes list. +func (c *callback) makeArgTypes() { + fntype := c.fn.Type() + // Skip receiver and context.Context parameter (if present). + firstArg := 0 + if c.rcvr.IsValid() { + firstArg++ + } + if fntype.NumIn() > firstArg && fntype.In(firstArg) == contextType { + c.hasCtx = true + firstArg++ + } + // Add all remaining parameters. + c.argTypes = make([]reflect.Type, fntype.NumIn()-firstArg) + for i := firstArg; i < fntype.NumIn(); i++ { + c.argTypes[i-firstArg] = fntype.In(i) + } +} + +// call invokes the callback. +func (c *callback) call(ctx context.Context, method string, args []reflect.Value) (res interface{}, errRes error) { + // Create the argument slice. + fullargs := make([]reflect.Value, 0, 2+len(args)) + if c.rcvr.IsValid() { + fullargs = append(fullargs, c.rcvr) + } + if c.hasCtx { + fullargs = append(fullargs, reflect.ValueOf(ctx)) + } + fullargs = append(fullargs, args...) + + // Catch panic while running the callback. + defer func() { + if err := recover(); err != nil { + const size = 64 << 10 + buf := make([]byte, size) + buf = buf[:runtime.Stack(buf, false)] + log.Error("RPC method " + method + " crashed: " + fmt.Sprintf("%v\n%s", err, buf)) + errRes = errors.New("method handler crashed") + } + }() + // Run the callback. + results := c.fn.Call(fullargs) + if len(results) == 0 { + return nil, nil + } + if c.errPos >= 0 && !results[c.errPos].IsNil() { + // Method has returned non-nil error value. + err := results[c.errPos].Interface().(error) + return reflect.Value{}, err + } + return results[0].Interface(), nil +} + +// Is this an exported - upper case - name? +func isExported(name string) bool { + rune, _ := utf8.DecodeRuneInString(name) + return unicode.IsUpper(rune) +} + +// Are all those types exported or built-in? +func allExportedOrBuiltin(types []reflect.Type) bool { + for _, typ := range types { + for typ.Kind() == reflect.Ptr { + typ = typ.Elem() + } + // PkgPath will be non-empty even for an exported type, + // so we need to check the type name as well. + if !isExported(typ.Name()) && typ.PkgPath() != "" { + return false + } + } + return true +} + +// Is t context.Context or *context.Context? +func isContextType(t reflect.Type) bool { + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + return t == contextType +} + +// Does t satisfy the error interface? +func isErrorType(t reflect.Type) bool { + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + return t.Implements(errorType) +} + +// Is t Subscription or *Subscription? +func isSubscriptionType(t reflect.Type) bool { + for t.Kind() == reflect.Ptr { + t = t.Elem() + } + return t == subscriptionType +} + +// isPubSub tests whether the given method has as as first argument a context.Context and +// returns the pair (Subscription, error). +func isPubSub(methodType reflect.Type) bool { + // numIn(0) is the receiver type + if methodType.NumIn() < 2 || methodType.NumOut() != 2 { + return false + } + return isContextType(methodType.In(1)) && + isSubscriptionType(methodType.Out(0)) && + isErrorType(methodType.Out(1)) +} + +// formatName converts to first character of name to lowercase. +func formatName(name string) string { + ret := []rune(name) + if len(ret) > 0 { + ret[0] = unicode.ToLower(ret[0]) + } + return string(ret) +} diff --git a/rpc/stdio.go b/rpc/stdio.go index ea552cca286b..8f6b7bd4bf6b 100644 --- a/rpc/stdio.go +++ b/rpc/stdio.go @@ -26,8 +26,8 @@ import ( // DialStdIO creates a client on stdin/stdout. func DialStdIO(ctx context.Context) (*Client, error) { - return newClient(ctx, func(_ context.Context) (net.Conn, error) { - return stdioConn{}, nil + return newClient(ctx, func(_ context.Context) (ServerCodec, error) { + return NewJSONCodec(stdioConn{}), nil }) } @@ -45,20 +45,8 @@ func (io stdioConn) Close() error { return nil } -func (io stdioConn) LocalAddr() net.Addr { - return &net.UnixAddr{Name: "stdio", Net: "stdio"} -} - -func (io stdioConn) RemoteAddr() net.Addr { - return &net.UnixAddr{Name: "stdio", Net: "stdio"} -} - -func (io stdioConn) SetDeadline(t time.Time) error { - return &net.OpError{Op: "set", Net: "stdio", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} -} - -func (io stdioConn) SetReadDeadline(t time.Time) error { - return &net.OpError{Op: "set", Net: "stdio", Source: nil, Addr: nil, Err: errors.New("deadline not supported")} +func (io stdioConn) RemoteAddr() string { + return "/dev/stdin" } func (io stdioConn) SetWriteDeadline(t time.Time) error { diff --git a/rpc/subscription.go b/rpc/subscription.go index 6bbb6f75d293..c1e869b8a3a4 100644 --- a/rpc/subscription.go +++ b/rpc/subscription.go @@ -17,9 +17,19 @@ package rpc import ( + "bufio" + "container/list" "context" + crand "crypto/rand" + "encoding/binary" + "encoding/hex" + "encoding/json" "errors" + "math/rand" + "reflect" + "strings" "sync" + "time" ) var ( @@ -29,121 +39,289 @@ var ( ErrSubscriptionNotFound = errors.New("subscription not found") ) +var globalGen = randomIDGenerator() + // ID defines a pseudo random number that is used to identify RPC subscriptions. type ID string -// a Subscription is created by a notifier and tight to that notifier. The client can use -// this subscription to wait for an unsubscribe request for the client, see Err(). -type Subscription struct { - ID ID - namespace string - err chan error // closed on unsubscribe +// NewID returns a new, random ID. +func NewID() ID { + return globalGen() } -// Err returns a channel that is closed when the client send an unsubscribe request. -func (s *Subscription) Err() <-chan error { - return s.err +// randomIDGenerator returns a function generates a random IDs. +func randomIDGenerator() func() ID { + seed, err := binary.ReadVarint(bufio.NewReader(crand.Reader)) + if err != nil { + seed = int64(time.Now().Nanosecond()) + } + var ( + mu sync.Mutex + rng = rand.New(rand.NewSource(seed)) + ) + return func() ID { + mu.Lock() + defer mu.Unlock() + id := make([]byte, 16) + rng.Read(id) + return encodeID(id) + } } -// notifierKey is used to store a notifier within the connection context. -type notifierKey struct{} - -// Notifier is tight to a RPC connection that supports subscriptions. -// Server callbacks use the notifier to send notifications. -type Notifier struct { - codec ServerCodec - subMu sync.Mutex - active map[ID]*Subscription - inactive map[ID]*Subscription - buffer map[ID][]interface{} // unsent notifications of inactive subscriptions -} - -// newNotifier creates a new notifier that can be used to send subscription -// notifications to the client. -func newNotifier(codec ServerCodec) *Notifier { - return &Notifier{ - codec: codec, - active: make(map[ID]*Subscription), - inactive: make(map[ID]*Subscription), - buffer: make(map[ID][]interface{}), +func encodeID(b []byte) ID { + id := hex.EncodeToString(b) + id = strings.TrimLeft(id, "0") + if id == "" { + id = "0" // ID's are RPC quantities, no leading zero's and 0 is 0x0. } + return ID("0x" + id) } +type notifierKey struct{} + // NotifierFromContext returns the Notifier value stored in ctx, if any. func NotifierFromContext(ctx context.Context) (*Notifier, bool) { n, ok := ctx.Value(notifierKey{}).(*Notifier) return n, ok } +// Notifier is tied to a RPC connection that supports subscriptions. +// Server callbacks use the notifier to send notifications. +type Notifier struct { + h *handler + namespace string + + mu sync.Mutex + sub *Subscription + buffer []json.RawMessage + callReturned bool + activated bool +} + // CreateSubscription returns a new subscription that is coupled to the // RPC connection. By default subscriptions are inactive and notifications // are dropped until the subscription is marked as active. This is done // by the RPC server after the subscription ID is send to the client. func (n *Notifier) CreateSubscription() *Subscription { - s := &Subscription{ID: NewID(), err: make(chan error)} - n.subMu.Lock() - n.inactive[s.ID] = s - n.subMu.Unlock() - return s + n.mu.Lock() + defer n.mu.Unlock() + + if n.sub != nil { + panic("can't create multiple subscriptions with Notifier") + } else if n.callReturned { + panic("can't create subscription after subscribe call has returned") + } + n.sub = &Subscription{ID: n.h.idgen(), namespace: n.namespace, err: make(chan error, 1)} + return n.sub } // Notify sends a notification to the client with the given data as payload. // If an error occurs the RPC connection is closed and the error is returned. func (n *Notifier) Notify(id ID, data interface{}) error { - n.subMu.Lock() - defer n.subMu.Unlock() + enc, err := json.Marshal(data) + if err != nil { + return err + } - if sub, active := n.active[id]; active { - n.send(sub, data) - } else { - n.buffer[id] = append(n.buffer[id], data) + n.mu.Lock() + defer n.mu.Unlock() + + if n.sub == nil { + panic("can't Notify before subscription is created") + } else if n.sub.ID != id { + panic("Notify with wrong ID") + } + if n.activated { + return n.send(n.sub, enc) } + n.buffer = append(n.buffer, enc) return nil } -func (n *Notifier) send(sub *Subscription, data interface{}) error { - notification := n.codec.CreateNotification(string(sub.ID), sub.namespace, data) - err := n.codec.Write(notification) - if err != nil { - n.codec.Close() +// Closed returns a channel that is closed when the RPC connection is closed. +// Deprecated: use subscription error channel +func (n *Notifier) Closed() <-chan interface{} { + return n.h.conn.Closed() +} + +// takeSubscription returns the subscription (if one has been created). No subscription can +// be created after this call. +func (n *Notifier) takeSubscription() *Subscription { + n.mu.Lock() + defer n.mu.Unlock() + n.callReturned = true + return n.sub +} + +// acticate is called after the subscription ID was sent to client. Notifications are +// buffered before activation. This prevents notifications being sent to the client before +// the subscription ID is sent to the client. +func (n *Notifier) activate() error { + n.mu.Lock() + defer n.mu.Unlock() + + for _, data := range n.buffer { + if err := n.send(n.sub, data); err != nil { + return err + } } - return err + n.activated = true + return nil } -// Closed returns a channel that is closed when the RPC connection is closed. -func (n *Notifier) Closed() <-chan interface{} { - return n.codec.Closed() -} - -// unsubscribe a subscription. -// If the subscription could not be found ErrSubscriptionNotFound is returned. -func (n *Notifier) unsubscribe(id ID) error { - n.subMu.Lock() - defer n.subMu.Unlock() - if s, found := n.active[id]; found { - close(s.err) - delete(n.active, id) - return nil +func (n *Notifier) send(sub *Subscription, data json.RawMessage) error { + params, _ := json.Marshal(&subscriptionResult{ID: string(sub.ID), Result: data}) + ctx := context.Background() + return n.h.conn.Write(ctx, &jsonrpcMessage{ + Version: vsn, + Method: n.namespace + notificationMethodSuffix, + Params: params, + }) +} + +// A Subscription is created by a notifier and tight to that notifier. The client can use +// this subscription to wait for an unsubscribe request for the client, see Err(). +type Subscription struct { + ID ID + namespace string + err chan error // closed on unsubscribe +} + +// Err returns a channel that is closed when the client send an unsubscribe request. +func (s *Subscription) Err() <-chan error { + return s.err +} + +// MarshalJSON marshals a subscription as its ID. +func (s *Subscription) MarshalJSON() ([]byte, error) { + return json.Marshal(s.ID) +} + +// ClientSubscription is a subscription established through the Client's Subscribe or +// EthSubscribe methods. +type ClientSubscription struct { + client *Client + etype reflect.Type + channel reflect.Value + namespace string + subid string + in chan json.RawMessage + + quitOnce sync.Once // ensures quit is closed once + quit chan struct{} // quit is closed when the subscription exits + errOnce sync.Once // ensures err is closed once + err chan error +} + +func newClientSubscription(c *Client, namespace string, channel reflect.Value) *ClientSubscription { + sub := &ClientSubscription{ + client: c, + namespace: namespace, + etype: channel.Type().Elem(), + channel: channel, + quit: make(chan struct{}), + err: make(chan error, 1), + in: make(chan json.RawMessage), } - return ErrSubscriptionNotFound -} - -// activate enables a subscription. Until a subscription is enabled all -// notifications are dropped. This method is called by the RPC server after -// the subscription ID was sent to client. This prevents notifications being -// send to the client before the subscription ID is send to the client. -func (n *Notifier) activate(id ID, namespace string) { - n.subMu.Lock() - defer n.subMu.Unlock() - - if sub, found := n.inactive[id]; found { - sub.namespace = namespace - n.active[id] = sub - delete(n.inactive, id) - // Send buffered notifications. - for _, data := range n.buffer[id] { - n.send(sub, data) + return sub +} + +// Err returns the subscription error channel. The intended use of Err is to schedule +// resubscription when the client connection is closed unexpectedly. +// +// The error channel receives a value when the subscription has ended due +// to an error. The received error is nil if Close has been called +// on the underlying client and no other error has occurred. +// +// The error channel is closed when Unsubscribe is called on the subscription. +func (sub *ClientSubscription) Err() <-chan error { + return sub.err +} + +// Unsubscribe unsubscribes the notification and closes the error channel. +// It can safely be called more than once. +func (sub *ClientSubscription) Unsubscribe() { + sub.quitWithError(nil, true) + sub.errOnce.Do(func() { close(sub.err) }) +} + +func (sub *ClientSubscription) quitWithError(err error, unsubscribeServer bool) { + sub.quitOnce.Do(func() { + // The dispatch loop won't be able to execute the unsubscribe call + // if it is blocked on deliver. Close sub.quit first because it + // unblocks deliver. + close(sub.quit) + if unsubscribeServer { + sub.requestUnsubscribe() + } + if err != nil { + if err == ErrClientQuit { + err = nil // Adhere to subscription semantics. + } + sub.err <- err } - delete(n.buffer, id) + }) +} + +func (sub *ClientSubscription) deliver(result json.RawMessage) (ok bool) { + select { + case sub.in <- result: + return true + case <-sub.quit: + return false } } + +func (sub *ClientSubscription) start() { + sub.quitWithError(sub.forward()) +} + +func (sub *ClientSubscription) forward() (err error, unsubscribeServer bool) { + cases := []reflect.SelectCase{ + {Dir: reflect.SelectRecv, Chan: reflect.ValueOf(sub.quit)}, + {Dir: reflect.SelectRecv, Chan: reflect.ValueOf(sub.in)}, + {Dir: reflect.SelectSend, Chan: sub.channel}, + } + buffer := list.New() + defer buffer.Init() + for { + var chosen int + var recv reflect.Value + if buffer.Len() == 0 { + // Idle, omit send case. + chosen, recv, _ = reflect.Select(cases[:2]) + } else { + // Non-empty buffer, send the first queued item. + cases[2].Send = reflect.ValueOf(buffer.Front().Value) + chosen, recv, _ = reflect.Select(cases) + } + + switch chosen { + case 0: // <-sub.quit + return nil, false + case 1: // <-sub.in + val, err := sub.unmarshal(recv.Interface().(json.RawMessage)) + if err != nil { + return err, true + } + if buffer.Len() == maxClientSubscriptionBuffer { + return ErrSubscriptionQueueOverflow, true + } + buffer.PushBack(val) + case 2: // sub.channel<- + cases[2].Send = reflect.Value{} // Don't hold onto the value. + buffer.Remove(buffer.Front()) + } + } +} + +func (sub *ClientSubscription) unmarshal(result json.RawMessage) (interface{}, error) { + val := reflect.New(sub.etype) + err := json.Unmarshal(result, val.Interface()) + return val.Elem().Interface(), err +} + +func (sub *ClientSubscription) requestUnsubscribe() error { + var result interface{} + return sub.client.Call(&result, sub.namespace+unsubscribeMethodSuffix, sub.subid) +} diff --git a/rpc/subscription_test.go b/rpc/subscription_test.go index 24febc91909d..eba192450db0 100644 --- a/rpc/subscription_test.go +++ b/rpc/subscription_test.go @@ -17,232 +17,62 @@ package rpc import ( - "context" "encoding/json" "fmt" "net" - "sync" + "strings" "testing" "time" ) -type NotificationTestService struct { - mu sync.Mutex - unsubscribed chan string - gotHangSubscriptionReq chan struct{} - unblockHangSubscription chan struct{} -} - -func (s *NotificationTestService) Echo(i int) int { - return i -} - -func (s *NotificationTestService) Unsubscribe(subid string) { - if s.unsubscribed != nil { - s.unsubscribed <- subid - } -} - -func (s *NotificationTestService) SomeSubscription(ctx context.Context, n, val int) (*Subscription, error) { - notifier, supported := NotifierFromContext(ctx) - if !supported { - return nil, ErrNotificationsUnsupported - } - - // by explicitly creating an subscription we make sure that the subscription id is send back to the client - // before the first subscription.Notify is called. Otherwise the events might be send before the response - // for the eth_subscribe method. - subscription := notifier.CreateSubscription() - - go func() { - // test expects n events, if we begin sending event immediately some events - // will probably be dropped since the subscription ID might not be send to - // the client. - for i := 0; i < n; i++ { - if err := notifier.Notify(subscription.ID, val+i); err != nil { - return - } - } - - select { - case <-notifier.Closed(): - case <-subscription.Err(): - } - if s.unsubscribed != nil { - s.unsubscribed <- string(subscription.ID) - } - }() - - return subscription, nil -} - -// HangSubscription blocks on s.unblockHangSubscription before -// sending anything. -func (s *NotificationTestService) HangSubscription(ctx context.Context, val int) (*Subscription, error) { - notifier, supported := NotifierFromContext(ctx) - if !supported { - return nil, ErrNotificationsUnsupported - } - - s.gotHangSubscriptionReq <- struct{}{} - <-s.unblockHangSubscription - subscription := notifier.CreateSubscription() - - go func() { - notifier.Notify(subscription.ID, val) - }() - return subscription, nil -} - -func TestNotifications(t *testing.T) { - server := NewServer() - service := &NotificationTestService{unsubscribed: make(chan string)} - - if err := server.RegisterName("eth", service); err != nil { - t.Fatalf("unable to register test service %v", err) - } - - clientConn, serverConn := net.Pipe() - - go server.ServeCodec(NewJSONCodec(serverConn), OptionMethodInvocation|OptionSubscriptions) - - out := json.NewEncoder(clientConn) - in := json.NewDecoder(clientConn) - - n := 5 - val := 12345 - request := map[string]interface{}{ - "id": 1, - "method": "eth_subscribe", - "version": "2.0", - "params": []interface{}{"someSubscription", n, val}, - } - - // create subscription - if err := out.Encode(request); err != nil { - t.Fatal(err) - } - - var subid string - response := jsonSuccessResponse{Result: subid} - if err := in.Decode(&response); err != nil { - t.Fatal(err) - } - - var ok bool - if _, ok = response.Result.(string); !ok { - t.Fatalf("expected subscription id, got %T", response.Result) - } - - for i := 0; i < n; i++ { - var notification jsonNotification - if err := in.Decode(¬ification); err != nil { - t.Fatalf("%v", err) - } - - if int(notification.Params.Result.(float64)) != val+i { - t.Fatalf("expected %d, got %d", val+i, notification.Params.Result) - } - } - - clientConn.Close() // causes notification unsubscribe callback to be called - select { - case <-service.unsubscribed: - case <-time.After(1 * time.Second): - t.Fatal("Unsubscribe not called after one second") - } -} - -func waitForMessages(t *testing.T, in *json.Decoder, successes chan<- jsonSuccessResponse, - failures chan<- jsonErrResponse, notifications chan<- jsonNotification, errors chan<- error) { - - // read and parse server messages - for { - var rmsg json.RawMessage - if err := in.Decode(&rmsg); err != nil { - return +func TestNewID(t *testing.T) { + hexchars := "0123456789ABCDEFabcdef" + for i := 0; i < 100; i++ { + id := string(NewID()) + if !strings.HasPrefix(id, "0x") { + t.Fatalf("invalid ID prefix, want '0x...', got %s", id) } - var responses []map[string]interface{} - if rmsg[0] == '[' { - if err := json.Unmarshal(rmsg, &responses); err != nil { - errors <- fmt.Errorf("Received invalid message: %s", rmsg) - return - } - } else { - var msg map[string]interface{} - if err := json.Unmarshal(rmsg, &msg); err != nil { - errors <- fmt.Errorf("Received invalid message: %s", rmsg) - return - } - responses = append(responses, msg) + id = id[2:] + if len(id) == 0 || len(id) > 32 { + t.Fatalf("invalid ID length, want len(id) > 0 && len(id) <= 32), got %d", len(id)) } - for _, msg := range responses { - // determine what kind of msg was received and broadcast - // it to over the corresponding channel - if _, found := msg["result"]; found { - successes <- jsonSuccessResponse{ - Version: msg["jsonrpc"].(string), - Id: msg["id"], - Result: msg["result"], - } - continue - } - if _, found := msg["error"]; found { - params := msg["params"].(map[string]interface{}) - failures <- jsonErrResponse{ - Version: msg["jsonrpc"].(string), - Id: msg["id"], - Error: jsonError{int(params["subscription"].(float64)), params["message"].(string), params["data"]}, - } - continue + for i := 0; i < len(id); i++ { + if strings.IndexByte(hexchars, id[i]) == -1 { + t.Fatalf("unexpected byte, want any valid hex char, got %c", id[i]) } - if _, found := msg["params"]; found { - params := msg["params"].(map[string]interface{}) - notifications <- jsonNotification{ - Version: msg["jsonrpc"].(string), - Method: msg["method"].(string), - Params: jsonSubscription{params["subscription"].(string), params["result"]}, - } - continue - } - errors <- fmt.Errorf("Received invalid message: %s", msg) } } } -// TestSubscriptionMultipleNamespaces ensures that subscriptions can exists -// for multiple different namespaces. -func TestSubscriptionMultipleNamespaces(t *testing.T) { +func TestSubscriptions(t *testing.T) { var ( namespaces = []string{"eth", "shh", "bzz"} - service = NotificationTestService{} - subCount = len(namespaces) * 2 + service = ¬ificationTestService{} + subCount = len(namespaces) notificationCount = 3 server = NewServer() clientConn, serverConn = net.Pipe() out = json.NewEncoder(clientConn) in = json.NewDecoder(clientConn) - successes = make(chan jsonSuccessResponse) - failures = make(chan jsonErrResponse) - notifications = make(chan jsonNotification) - errors = make(chan error, 10) + successes = make(chan subConfirmation) + notifications = make(chan subscriptionResult) + errors = make(chan error, subCount*notificationCount+1) ) // setup and start server for _, namespace := range namespaces { - if err := server.RegisterName(namespace, &service); err != nil { + if err := server.RegisterName(namespace, service); err != nil { t.Fatalf("unable to register test service %v", err) } } - go server.ServeCodec(NewJSONCodec(serverConn), OptionMethodInvocation|OptionSubscriptions) defer server.Stop() // wait for message and write them to the given channels - go waitForMessages(t, in, successes, failures, notifications, errors) + go waitForMessages(in, successes, notifications, errors) // create subscriptions one by one for i, namespace := range namespaces { @@ -252,27 +82,11 @@ func TestSubscriptionMultipleNamespaces(t *testing.T) { "version": "2.0", "params": []interface{}{"someSubscription", notificationCount, i}, } - if err := out.Encode(&request); err != nil { t.Fatalf("Could not create subscription: %v", err) } } - // create all subscriptions in 1 batch - var requests []interface{} - for i, namespace := range namespaces { - requests = append(requests, map[string]interface{}{ - "id": i, - "method": fmt.Sprintf("%s_subscribe", namespace), - "version": "2.0", - "params": []interface{}{"someSubscription", notificationCount, i}, - }) - } - - if err := out.Encode(&requests); err != nil { - t.Fatalf("Could not create subscription in batch form: %v", err) - } - timeout := time.After(30 * time.Second) subids := make(map[string]string, subCount) count := make(map[string]int, subCount) @@ -285,17 +99,14 @@ func TestSubscriptionMultipleNamespaces(t *testing.T) { } return done } - for !allReceived() { select { - case suc := <-successes: // subscription created - subids[namespaces[int(suc.Id.(float64))]] = suc.Result.(string) + case confirmation := <-successes: // subscription created + subids[namespaces[confirmation.reqid]] = string(confirmation.subid) case notification := <-notifications: - count[notification.Params.Subscription]++ + count[notification.ID]++ case err := <-errors: t.Fatal(err) - case failure := <-failures: - t.Errorf("received error: %v", failure.Error) case <-timeout: for _, namespace := range namespaces { subid, found := subids[namespace] @@ -311,3 +122,85 @@ func TestSubscriptionMultipleNamespaces(t *testing.T) { } } } + +// This test checks that unsubscribing works. +func TestServerUnsubscribe(t *testing.T) { + // Start the server. + server := newTestServer() + service := ¬ificationTestService{unsubscribed: make(chan string)} + server.RegisterName("nftest2", service) + p1, p2 := net.Pipe() + go server.ServeCodec(NewJSONCodec(p1), OptionMethodInvocation|OptionSubscriptions) + + p2.SetDeadline(time.Now().Add(10 * time.Second)) + + // Subscribe. + p2.Write([]byte(`{"jsonrpc":"2.0","id":1,"method":"nftest2_subscribe","params":["someSubscription",0,10]}`)) + + // Handle received messages. + resps := make(chan subConfirmation) + notifications := make(chan subscriptionResult) + errors := make(chan error) + go waitForMessages(json.NewDecoder(p2), resps, notifications, errors) + + // Receive the subscription ID. + var sub subConfirmation + select { + case sub = <-resps: + case err := <-errors: + t.Fatal(err) + } + + // Unsubscribe and check that it is handled on the server side. + p2.Write([]byte(`{"jsonrpc":"2.0","method":"nftest2_unsubscribe","params":["` + sub.subid + `"]}`)) + for { + select { + case id := <-service.unsubscribed: + if id != string(sub.subid) { + t.Errorf("wrong subscription ID unsubscribed") + } + return + case err := <-errors: + t.Fatal(err) + case <-notifications: + // drop notifications + } + } +} + +type subConfirmation struct { + reqid int + subid ID +} + +func waitForMessages(in *json.Decoder, successes chan subConfirmation, notifications chan subscriptionResult, errors chan error) { + for { + var msg jsonrpcMessage + if err := in.Decode(&msg); err != nil { + errors <- fmt.Errorf("decode error: %v", err) + return + } + switch { + case msg.isNotification(): + var res subscriptionResult + if err := json.Unmarshal(msg.Params, &res); err != nil { + errors <- fmt.Errorf("invalid subscription result: %v", err) + } else { + notifications <- res + } + case msg.isResponse(): + var c subConfirmation + if msg.Error != nil { + errors <- msg.Error + } else if err := json.Unmarshal(msg.Result, &c.subid); err != nil { + errors <- fmt.Errorf("invalid response: %v", err) + } else { + json.Unmarshal(msg.ID, &c.reqid) + successes <- c + } + default: + errors <- fmt.Errorf("unrecognized message: %v", msg) + return + } + } +} diff --git a/rpc/testdata/invalid-badid.js b/rpc/testdata/invalid-badid.js new file mode 100644 index 000000000000..2202b8ccd26e --- /dev/null +++ b/rpc/testdata/invalid-badid.js @@ -0,0 +1,7 @@ +// This test checks processing of messages with invalid ID. + +--> {"id":[],"method":"test_foo"} +<-- {"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"invalid request"}} + +--> {"id":{},"method":"test_foo"} +<-- {"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"invalid request"}} diff --git a/rpc/testdata/invalid-batch.js b/rpc/testdata/invalid-batch.js new file mode 100644 index 000000000000..f470574fb5b5 --- /dev/null +++ b/rpc/testdata/invalid-batch.js @@ -0,0 +1,14 @@ +// This test checks the behavior of batches with invalid elements. +// Empty batches are not allowed. Batches may contain junk. + +--> [] +<-- {"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"empty batch"}} + +--> [1] +<-- [{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"invalid request"}}] + +--> [1,2,3] +<-- [{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"invalid request"}},{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"invalid request"}},{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"invalid request"}}] + +--> [{"jsonrpc":"2.0","id":1,"method":"test_echo","params":["foo",1]},55,{"jsonrpc":"2.0","id":2,"method":"unknown_method"},{"foo":"bar"}] +<-- [{"jsonrpc":"2.0","id":1,"result":{"String":"foo","Int":1,"Args":null}},{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"invalid request"}},{"jsonrpc":"2.0","id":2,"error":{"code":-32601,"message":"the method unknown_method does not exist/is not available"}},{"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"invalid request"}}] diff --git a/rpc/testdata/invalid-idonly.js b/rpc/testdata/invalid-idonly.js new file mode 100644 index 000000000000..79997bee3060 --- /dev/null +++ b/rpc/testdata/invalid-idonly.js @@ -0,0 +1,7 @@ +// This test checks processing of messages that contain just the ID and nothing else. + +--> {"id":1} +<-- {"jsonrpc":"2.0","id":1,"error":{"code":-32600,"message":"invalid request"}} + +--> {"jsonrpc":"2.0","id":1} +<-- {"jsonrpc":"2.0","id":1,"error":{"code":-32600,"message":"invalid request"}} diff --git a/rpc/testdata/invalid-nonobj.js b/rpc/testdata/invalid-nonobj.js new file mode 100644 index 000000000000..4b9f4d994c13 --- /dev/null +++ b/rpc/testdata/invalid-nonobj.js @@ -0,0 +1,4 @@ +// This test checks behavior for invalid requests. + +--> 1 +<-- {"jsonrpc":"2.0","id":null,"error":{"code":-32600,"message":"invalid request"}} diff --git a/rpc/testdata/invalid-syntax.json b/rpc/testdata/invalid-syntax.json new file mode 100644 index 000000000000..b19429960309 --- /dev/null +++ b/rpc/testdata/invalid-syntax.json @@ -0,0 +1,5 @@ +// This test checks that an error is written for invalid JSON requests. + +--> 'f +<-- {"jsonrpc":"2.0","id":null,"error":{"code":-32700,"message":"invalid character '\\'' looking for beginning of value"}} + diff --git a/rpc/testdata/reqresp-batch.js b/rpc/testdata/reqresp-batch.js new file mode 100644 index 000000000000..977af7663099 --- /dev/null +++ b/rpc/testdata/reqresp-batch.js @@ -0,0 +1,8 @@ +// There is no response for all-notification batches. + +--> [{"jsonrpc":"2.0","method":"test_echo","params":["x",99]}] + +// This test checks regular batch calls. + +--> [{"jsonrpc":"2.0","id":2,"method":"test_echo","params":[]}, {"jsonrpc":"2.0","id": 3,"method":"test_echo","params":["x",3]}] +<-- [{"jsonrpc":"2.0","id":2,"error":{"code":-32602,"message":"missing value for required argument 0"}},{"jsonrpc":"2.0","id":3,"result":{"String":"x","Int":3,"Args":null}}] diff --git a/rpc/testdata/reqresp-echo.js b/rpc/testdata/reqresp-echo.js new file mode 100644 index 000000000000..7a9e90321c47 --- /dev/null +++ b/rpc/testdata/reqresp-echo.js @@ -0,0 +1,16 @@ +// This test calls the test_echo method. + +--> {"jsonrpc": "2.0", "id": 2, "method": "test_echo", "params": []} +<-- {"jsonrpc":"2.0","id":2,"error":{"code":-32602,"message":"missing value for required argument 0"}} + +--> {"jsonrpc": "2.0", "id": 2, "method": "test_echo", "params": ["x"]} +<-- {"jsonrpc":"2.0","id":2,"error":{"code":-32602,"message":"missing value for required argument 1"}} + +--> {"jsonrpc": "2.0", "id": 2, "method": "test_echo", "params": ["x", 3]} +<-- {"jsonrpc":"2.0","id":2,"result":{"String":"x","Int":3,"Args":null}} + +--> {"jsonrpc": "2.0", "id": 2, "method": "test_echo", "params": ["x", 3, {"S": "foo"}]} +<-- {"jsonrpc":"2.0","id":2,"result":{"String":"x","Int":3,"Args":{"S":"foo"}}} + +--> {"jsonrpc": "2.0", "id": 2, "method": "test_echoWithCtx", "params": ["x", 3, {"S": "foo"}]} +<-- {"jsonrpc":"2.0","id":2,"result":{"String":"x","Int":3,"Args":{"S":"foo"}}} diff --git a/rpc/testdata/reqresp-namedparam.js b/rpc/testdata/reqresp-namedparam.js new file mode 100644 index 000000000000..9a9372b0a711 --- /dev/null +++ b/rpc/testdata/reqresp-namedparam.js @@ -0,0 +1,5 @@ +// This test checks that an error response is sent for calls +// with named parameters. + +--> {"jsonrpc":"2.0","method":"test_echo","params":{"int":23},"id":3} +<-- {"jsonrpc":"2.0","id":3,"error":{"code":-32602,"message":"non-array args"}} diff --git a/rpc/testdata/reqresp-noargsrets.js b/rpc/testdata/reqresp-noargsrets.js new file mode 100644 index 000000000000..e61cc708ba33 --- /dev/null +++ b/rpc/testdata/reqresp-noargsrets.js @@ -0,0 +1,4 @@ +// This test calls the test_noArgsRets method. + +--> {"jsonrpc": "2.0", "id": "foo", "method": "test_noArgsRets", "params": []} +<-- {"jsonrpc":"2.0","id":"foo","result":null} diff --git a/rpc/testdata/reqresp-nomethod.js b/rpc/testdata/reqresp-nomethod.js new file mode 100644 index 000000000000..58ea6f3079b6 --- /dev/null +++ b/rpc/testdata/reqresp-nomethod.js @@ -0,0 +1,4 @@ +// This test calls a method that doesn't exist. + +--> {"jsonrpc": "2.0", "id": 2, "method": "invalid_method", "params": [2, 3]} +<-- {"jsonrpc":"2.0","id":2,"error":{"code":-32601,"message":"the method invalid_method does not exist/is not available"}} diff --git a/rpc/testdata/reqresp-noparam.js b/rpc/testdata/reqresp-noparam.js new file mode 100644 index 000000000000..2edf486d9f85 --- /dev/null +++ b/rpc/testdata/reqresp-noparam.js @@ -0,0 +1,4 @@ +// This test checks that calls with no parameters work. + +--> {"jsonrpc":"2.0","method":"test_noArgsRets","id":3} +<-- {"jsonrpc":"2.0","id":3,"result":null} diff --git a/rpc/testdata/reqresp-paramsnull.js b/rpc/testdata/reqresp-paramsnull.js new file mode 100644 index 000000000000..8a01bae1bbe7 --- /dev/null +++ b/rpc/testdata/reqresp-paramsnull.js @@ -0,0 +1,4 @@ +// This test checks that calls with "params":null work. + +--> {"jsonrpc":"2.0","method":"test_noArgsRets","params":null,"id":3} +<-- {"jsonrpc":"2.0","id":3,"result":null} diff --git a/rpc/testdata/revcall.js b/rpc/testdata/revcall.js new file mode 100644 index 000000000000..695d9858f87e --- /dev/null +++ b/rpc/testdata/revcall.js @@ -0,0 +1,6 @@ +// This test checks reverse calls. + +--> {"jsonrpc":"2.0","id":2,"method":"test_callMeBack","params":["foo",[1]]} +<-- {"jsonrpc":"2.0","id":1,"method":"foo","params":[1]} +--> {"jsonrpc":"2.0","id":1,"result":"my result"} +<-- {"jsonrpc":"2.0","id":2,"result":"my result"} diff --git a/rpc/testdata/revcall2.js b/rpc/testdata/revcall2.js new file mode 100644 index 000000000000..acab46551ec6 --- /dev/null +++ b/rpc/testdata/revcall2.js @@ -0,0 +1,7 @@ +// This test checks reverse calls. + +--> {"jsonrpc":"2.0","id":2,"method":"test_callMeBackLater","params":["foo",[1]]} +<-- {"jsonrpc":"2.0","id":2,"result":null} +<-- {"jsonrpc":"2.0","id":1,"method":"foo","params":[1]} +--> {"jsonrpc":"2.0","id":1,"result":"my result"} + diff --git a/rpc/testdata/subscription.js b/rpc/testdata/subscription.js new file mode 100644 index 000000000000..9f1007301080 --- /dev/null +++ b/rpc/testdata/subscription.js @@ -0,0 +1,12 @@ +// This test checks basic subscription support. + +--> {"jsonrpc":"2.0","id":1,"method":"nftest_subscribe","params":["someSubscription",5,1]} +<-- {"jsonrpc":"2.0","id":1,"result":"0x1"} +<-- {"jsonrpc":"2.0","method":"nftest_subscription","params":{"subscription":"0x1","result":1}} +<-- {"jsonrpc":"2.0","method":"nftest_subscription","params":{"subscription":"0x1","result":2}} +<-- {"jsonrpc":"2.0","method":"nftest_subscription","params":{"subscription":"0x1","result":3}} +<-- {"jsonrpc":"2.0","method":"nftest_subscription","params":{"subscription":"0x1","result":4}} +<-- {"jsonrpc":"2.0","method":"nftest_subscription","params":{"subscription":"0x1","result":5}} + +--> {"jsonrpc":"2.0","id":2,"method":"nftest_echo","params":[11]} +<-- {"jsonrpc":"2.0","id":2,"result":11} diff --git a/rpc/testservice_test.go b/rpc/testservice_test.go new file mode 100644 index 000000000000..470870bacf78 --- /dev/null +++ b/rpc/testservice_test.go @@ -0,0 +1,180 @@ +// Copyright 2018 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rpc + +import ( + "context" + "encoding/binary" + "errors" + "sync" + "time" +) + +func newTestServer() *Server { + server := NewServer() + server.idgen = sequentialIDGenerator() + if err := server.RegisterName("test", new(testService)); err != nil { + panic(err) + } + if err := server.RegisterName("nftest", new(notificationTestService)); err != nil { + panic(err) + } + return server +} + +func sequentialIDGenerator() func() ID { + var ( + mu sync.Mutex + counter uint64 + ) + return func() ID { + mu.Lock() + defer mu.Unlock() + counter++ + id := make([]byte, 8) + binary.BigEndian.PutUint64(id, counter) + return encodeID(id) + } +} + +type testService struct{} + +type Args struct { + S string +} + +type Result struct { + String string + Int int + Args *Args +} + +func (s *testService) NoArgsRets() {} + +func (s *testService) Echo(str string, i int, args *Args) Result { + return Result{str, i, args} +} + +func (s *testService) EchoWithCtx(ctx context.Context, str string, i int, args *Args) Result { + return Result{str, i, args} +} + +func (s *testService) Sleep(ctx context.Context, duration time.Duration) { + time.Sleep(duration) +} + +func (s *testService) Rets() (string, error) { + return "", nil +} + +func (s *testService) InvalidRets1() (error, string) { + return nil, "" +} + +func (s *testService) InvalidRets2() (string, string) { + return "", "" +} + +func (s *testService) InvalidRets3() (string, string, error) { + return "", "", nil +} + +func (s *testService) CallMeBack(ctx context.Context, method string, args []interface{}) (interface{}, error) { + c, ok := ClientFromContext(ctx) + if !ok { + return nil, errors.New("no client") + } + var result interface{} + err := c.Call(&result, method, args...) + return result, err +} + +func (s *testService) CallMeBackLater(ctx context.Context, method string, args []interface{}) error { + c, ok := ClientFromContext(ctx) + if !ok { + return errors.New("no client") + } + go func() { + <-ctx.Done() + var result interface{} + c.Call(&result, method, args...) + }() + return nil +} + +func (s *testService) Subscription(ctx context.Context) (*Subscription, error) { + return nil, nil +} + +type notificationTestService struct { + unsubscribed chan string + gotHangSubscriptionReq chan struct{} + unblockHangSubscription chan struct{} +} + +func (s *notificationTestService) Echo(i int) int { + return i +} + +func (s *notificationTestService) Unsubscribe(subid string) { + if s.unsubscribed != nil { + s.unsubscribed <- subid + } +} + +func (s *notificationTestService) SomeSubscription(ctx context.Context, n, val int) (*Subscription, error) { + notifier, supported := NotifierFromContext(ctx) + if !supported { + return nil, ErrNotificationsUnsupported + } + + // By explicitly creating an subscription we make sure that the subscription id is send + // back to the client before the first subscription.Notify is called. Otherwise the + // events might be send before the response for the *_subscribe method. + subscription := notifier.CreateSubscription() + go func() { + for i := 0; i < n; i++ { + if err := notifier.Notify(subscription.ID, val+i); err != nil { + return + } + } + select { + case <-notifier.Closed(): + case <-subscription.Err(): + } + if s.unsubscribed != nil { + s.unsubscribed <- string(subscription.ID) + } + }() + return subscription, nil +} + +// HangSubscription blocks on s.unblockHangSubscription before sending anything. +func (s *notificationTestService) HangSubscription(ctx context.Context, val int) (*Subscription, error) { + notifier, supported := NotifierFromContext(ctx) + if !supported { + return nil, ErrNotificationsUnsupported + } + s.gotHangSubscriptionReq <- struct{}{} + <-s.unblockHangSubscription + subscription := notifier.CreateSubscription() + + go func() { + notifier.Notify(subscription.ID, val) + }() + return subscription, nil +} diff --git a/rpc/types.go b/rpc/types.go index d42617497ef1..edc71cf2e655 100644 --- a/rpc/types.go +++ b/rpc/types.go @@ -17,13 +17,11 @@ package rpc import ( + "context" "fmt" "math" - "reflect" "strings" - "sync" - mapset "github.com/deckarep/golang-set" "github.com/ubiq/go-ubiq/common/hexutil" ) @@ -35,57 +33,6 @@ type API struct { Public bool // indication if the methods must be considered safe for public use } -// callback is a method callback which was registered in the server -type callback struct { - rcvr reflect.Value // receiver of method - method reflect.Method // callback - argTypes []reflect.Type // input argument types - hasCtx bool // method's first argument is a context (not included in argTypes) - errPos int // err return idx, of -1 when method cannot return error - isSubscribe bool // indication if the callback is a subscription -} - -// service represents a registered object -type service struct { - name string // name for service - typ reflect.Type // receiver type - callbacks callbacks // registered handlers - subscriptions subscriptions // available subscriptions/notifications -} - -// serverRequest is an incoming request -type serverRequest struct { - id interface{} - svcname string - callb *callback - args []reflect.Value - isUnsubscribe bool - err Error -} - -type serviceRegistry map[string]*service // collection of services -type callbacks map[string]*callback // collection of RPC callbacks -type subscriptions map[string]*callback // collection of subscription callbacks - -// Server represents a RPC server -type Server struct { - services serviceRegistry - - run int32 - codecsMu sync.Mutex - codecs mapset.Set -} - -// rpcRequest represents a raw incoming RPC request -type rpcRequest struct { - service string - method string - id interface{} - isPubSub bool - params interface{} - err Error // invalid batch element -} - // Error wraps RPC errors, which contain an error code in addition to the message. type Error interface { Error() string // returns the message @@ -96,24 +43,19 @@ type Error interface { // a RPC session. Implementations must be go-routine safe since the codec can be called in // multiple go-routines concurrently. type ServerCodec interface { - // Read next request - ReadRequestHeaders() ([]rpcRequest, bool, Error) - // Parse request argument to the given types - ParseRequestArguments(argTypes []reflect.Type, params interface{}) ([]reflect.Value, Error) - // Assemble success response, expects response id and payload - CreateResponse(id interface{}, reply interface{}) interface{} - // Assemble error response, expects response id and error - CreateErrorResponse(id interface{}, err Error) interface{} - // Assemble error response with extra information about the error through info - CreateErrorResponseWithInfo(id interface{}, err Error, info interface{}) interface{} - // Create notification response - CreateNotification(id, namespace string, event interface{}) interface{} - // Write msg to client. - Write(msg interface{}) error - // Close underlying data stream + Read() (msgs []*jsonrpcMessage, isBatch bool, err error) Close() - // Closed when underlying connection is closed + jsonWriter +} + +// jsonWriter can write JSON messages to its underlying connection. +// Implementations must be safe for concurrent use. +type jsonWriter interface { + Write(context.Context, interface{}) error + // Closed returns a channel which is closed when the connection is closed. Closed() <-chan interface{} + // RemoteAddr returns the peer address of the connection. + RemoteAddr() string } type BlockNumber int64 diff --git a/rpc/utils.go b/rpc/utils.go deleted file mode 100644 index 7f7ac4520bc5..000000000000 --- a/rpc/utils.go +++ /dev/null @@ -1,226 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package rpc - -import ( - "bufio" - "context" - crand "crypto/rand" - "encoding/binary" - "encoding/hex" - "math/rand" - "reflect" - "strings" - "sync" - "time" - "unicode" - "unicode/utf8" -) - -var ( - subscriptionIDGenMu sync.Mutex - subscriptionIDGen = idGenerator() -) - -// Is this an exported - upper case - name? -func isExported(name string) bool { - rune, _ := utf8.DecodeRuneInString(name) - return unicode.IsUpper(rune) -} - -// Is this type exported or a builtin? -func isExportedOrBuiltinType(t reflect.Type) bool { - for t.Kind() == reflect.Ptr { - t = t.Elem() - } - // PkgPath will be non-empty even for an exported type, - // so we need to check the type name as well. - return isExported(t.Name()) || t.PkgPath() == "" -} - -var contextType = reflect.TypeOf((*context.Context)(nil)).Elem() - -// isContextType returns an indication if the given t is of context.Context or *context.Context type -func isContextType(t reflect.Type) bool { - for t.Kind() == reflect.Ptr { - t = t.Elem() - } - return t == contextType -} - -var errorType = reflect.TypeOf((*error)(nil)).Elem() - -// Implements this type the error interface -func isErrorType(t reflect.Type) bool { - for t.Kind() == reflect.Ptr { - t = t.Elem() - } - return t.Implements(errorType) -} - -var subscriptionType = reflect.TypeOf((*Subscription)(nil)).Elem() - -// isSubscriptionType returns an indication if the given t is of Subscription or *Subscription type -func isSubscriptionType(t reflect.Type) bool { - for t.Kind() == reflect.Ptr { - t = t.Elem() - } - return t == subscriptionType -} - -// isPubSub tests whether the given method has as as first argument a context.Context -// and returns the pair (Subscription, error) -func isPubSub(methodType reflect.Type) bool { - // numIn(0) is the receiver type - if methodType.NumIn() < 2 || methodType.NumOut() != 2 { - return false - } - - return isContextType(methodType.In(1)) && - isSubscriptionType(methodType.Out(0)) && - isErrorType(methodType.Out(1)) -} - -// formatName will convert to first character to lower case -func formatName(name string) string { - ret := []rune(name) - if len(ret) > 0 { - ret[0] = unicode.ToLower(ret[0]) - } - return string(ret) -} - -// suitableCallbacks iterates over the methods of the given type. It will determine if a method satisfies the criteria -// for a RPC callback or a subscription callback and adds it to the collection of callbacks or subscriptions. See server -// documentation for a summary of these criteria. -func suitableCallbacks(rcvr reflect.Value, typ reflect.Type) (callbacks, subscriptions) { - callbacks := make(callbacks) - subscriptions := make(subscriptions) - -METHODS: - for m := 0; m < typ.NumMethod(); m++ { - method := typ.Method(m) - mtype := method.Type - mname := formatName(method.Name) - if method.PkgPath != "" { // method must be exported - continue - } - - var h callback - h.isSubscribe = isPubSub(mtype) - h.rcvr = rcvr - h.method = method - h.errPos = -1 - - firstArg := 1 - numIn := mtype.NumIn() - if numIn >= 2 && mtype.In(1) == contextType { - h.hasCtx = true - firstArg = 2 - } - - if h.isSubscribe { - h.argTypes = make([]reflect.Type, numIn-firstArg) // skip rcvr type - for i := firstArg; i < numIn; i++ { - argType := mtype.In(i) - if isExportedOrBuiltinType(argType) { - h.argTypes[i-firstArg] = argType - } else { - continue METHODS - } - } - - subscriptions[mname] = &h - continue METHODS - } - - // determine method arguments, ignore first arg since it's the receiver type - // Arguments must be exported or builtin types - h.argTypes = make([]reflect.Type, numIn-firstArg) - for i := firstArg; i < numIn; i++ { - argType := mtype.In(i) - if !isExportedOrBuiltinType(argType) { - continue METHODS - } - h.argTypes[i-firstArg] = argType - } - - // check that all returned values are exported or builtin types - for i := 0; i < mtype.NumOut(); i++ { - if !isExportedOrBuiltinType(mtype.Out(i)) { - continue METHODS - } - } - - // when a method returns an error it must be the last returned value - h.errPos = -1 - for i := 0; i < mtype.NumOut(); i++ { - if isErrorType(mtype.Out(i)) { - h.errPos = i - break - } - } - - if h.errPos >= 0 && h.errPos != mtype.NumOut()-1 { - continue METHODS - } - - switch mtype.NumOut() { - case 0, 1, 2: - if mtype.NumOut() == 2 && h.errPos == -1 { // method must one return value and 1 error - continue METHODS - } - callbacks[mname] = &h - } - } - - return callbacks, subscriptions -} - -// idGenerator helper utility that generates a (pseudo) random sequence of -// bytes that are used to generate identifiers. -func idGenerator() *rand.Rand { - if seed, err := binary.ReadVarint(bufio.NewReader(crand.Reader)); err == nil { - return rand.New(rand.NewSource(seed)) - } - return rand.New(rand.NewSource(int64(time.Now().Nanosecond()))) -} - -// NewID generates a identifier that can be used as an identifier in the RPC interface. -// e.g. filter and subscription identifier. -func NewID() ID { - subscriptionIDGenMu.Lock() - defer subscriptionIDGenMu.Unlock() - - id := make([]byte, 16) - for i := 0; i < len(id); i += 7 { - val := subscriptionIDGen.Int63() - for j := 0; i+j < len(id) && j < 7; j++ { - id[i+j] = byte(val) - val >>= 8 - } - } - - rpcId := hex.EncodeToString(id) - // rpc ID's are RPC quantities, no leading zero's and 0 is 0x0 - rpcId = strings.TrimLeft(rpcId, "0") - if rpcId == "" { - rpcId = "0" - } - - return ID("0x" + rpcId) -} diff --git a/rpc/utils_test.go b/rpc/utils_test.go deleted file mode 100644 index e0e063f607f6..000000000000 --- a/rpc/utils_test.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package rpc - -import ( - "strings" - "testing" -) - -func TestNewID(t *testing.T) { - hexchars := "0123456789ABCDEFabcdef" - for i := 0; i < 100; i++ { - id := string(NewID()) - if !strings.HasPrefix(id, "0x") { - t.Fatalf("invalid ID prefix, want '0x...', got %s", id) - } - - id = id[2:] - if len(id) == 0 || len(id) > 32 { - t.Fatalf("invalid ID length, want len(id) > 0 && len(id) <= 32), got %d", len(id)) - } - - for i := 0; i < len(id); i++ { - if strings.IndexByte(hexchars, id[i]) == -1 { - t.Fatalf("unexpected byte, want any valid hex char, got %c", id[i]) - } - } - } -} diff --git a/rpc/websocket.go b/rpc/websocket.go index 2a29fcf3d870..307a74590eee 100644 --- a/rpc/websocket.go +++ b/rpc/websocket.go @@ -22,6 +22,7 @@ import ( "crypto/tls" "encoding/base64" "encoding/json" + "errors" "fmt" "net" "net/http" @@ -56,24 +57,39 @@ var websocketJSONCodec = websocket.Codec{ // // allowedOrigins should be a comma-separated list of allowed origin URLs. // To allow connections with any origin, pass "*". -func (srv *Server) WebsocketHandler(allowedOrigins []string) http.Handler { +func (s *Server) WebsocketHandler(allowedOrigins []string) http.Handler { return websocket.Server{ Handshake: wsHandshakeValidator(allowedOrigins), Handler: func(conn *websocket.Conn) { - // Create a custom encode/decode pair to enforce payload size and number encoding - conn.MaxPayloadBytes = maxRequestContentLength - - encoder := func(v interface{}) error { - return websocketJSONCodec.Send(conn, v) - } - decoder := func(v interface{}) error { - return websocketJSONCodec.Receive(conn, v) - } - srv.ServeCodec(NewCodec(conn, encoder, decoder), OptionMethodInvocation|OptionSubscriptions) + codec := newWebsocketCodec(conn) + s.ServeCodec(codec, OptionMethodInvocation|OptionSubscriptions) }, } } +func newWebsocketCodec(conn *websocket.Conn) ServerCodec { + // Create a custom encode/decode pair to enforce payload size and number encoding + conn.MaxPayloadBytes = maxRequestContentLength + encoder := func(v interface{}) error { + return websocketJSONCodec.Send(conn, v) + } + decoder := func(v interface{}) error { + return websocketJSONCodec.Receive(conn, v) + } + rpcconn := Conn(conn) + if conn.IsServerConn() { + // Override remote address with the actual socket address because + // package websocket crashes if there is no request origin. + addr := conn.Request().RemoteAddr + if wsaddr := conn.RemoteAddr().(*websocket.Addr); wsaddr.URL != nil { + // Add origin if present. + addr += "(" + wsaddr.URL.String() + ")" + } + rpcconn = connWithRemoteAddr{conn, addr} + } + return NewCodec(rpcconn, encoder, decoder) +} + // NewWSServer creates a new websocket RPC server around an API provider. // // Deprecated: use Server.WebsocketHandler @@ -105,15 +121,16 @@ func wsHandshakeValidator(allowedOrigins []string) func(*websocket.Config, *http } } - log.Debug(fmt.Sprintf("Allowed origin(s) for WS RPC interface %v\n", origins.ToSlice())) + log.Debug(fmt.Sprintf("Allowed origin(s) for WS RPC interface %v", origins.ToSlice())) f := func(cfg *websocket.Config, req *http.Request) error { + // Verify origin against whitelist. origin := strings.ToLower(req.Header.Get("Origin")) if allowAllOrigins || origins.Contains(origin) { return nil } - log.Warn(fmt.Sprintf("origin '%s' not allowed on WS-RPC interface\n", origin)) - return fmt.Errorf("origin %s not allowed", origin) + log.Warn("Rejected WebSocket connection", "origin", origin) + return errors.New("origin not allowed") } return f @@ -155,8 +172,12 @@ func DialWebsocket(ctx context.Context, endpoint, origin string) (*Client, error return nil, err } - return newClient(ctx, func(ctx context.Context) (net.Conn, error) { - return wsDialContext(ctx, config) + return newClient(ctx, func(ctx context.Context) (ServerCodec, error) { + conn, err := wsDialContext(ctx, config) + if err != nil { + return nil, err + } + return newWebsocketCodec(conn), nil }) } From 2a31f979cb9ff9660b77d215120ac820fd523a41 Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Sat, 14 Sep 2019 19:39:24 +0200 Subject: [PATCH 03/29] add rpc.discover (openrpc) --- cmd/gubiq/main.go | 6 + internal/openrpc/openrpc_schema.go | 1838 +++++++++++++++++++++++ internal/openrpc/openrpc_schema_test.go | 14 + internal/web3ext/web3ext.go | 10 +- rpc/json.go | 20 +- rpc/openrpc.go | 25 + rpc/server.go | 121 +- rpc/service.go | 7 +- 8 files changed, 2026 insertions(+), 15 deletions(-) create mode 100644 internal/openrpc/openrpc_schema.go create mode 100644 internal/openrpc/openrpc_schema_test.go create mode 100644 rpc/openrpc.go diff --git a/cmd/gubiq/main.go b/cmd/gubiq/main.go index ce740f425967..53302354acd7 100644 --- a/cmd/gubiq/main.go +++ b/cmd/gubiq/main.go @@ -35,9 +35,11 @@ import ( "github.com/ubiq/go-ubiq/eth" "github.com/ubiq/go-ubiq/ethclient" "github.com/ubiq/go-ubiq/internal/debug" + "github.com/ubiq/go-ubiq/internal/openrpc" "github.com/ubiq/go-ubiq/log" "github.com/ubiq/go-ubiq/metrics" "github.com/ubiq/go-ubiq/node" + "github.com/ubiq/go-ubiq/rpc" cli "gopkg.in/urfave/cli.v1" ) @@ -248,6 +250,10 @@ func init() { console.Stdin.Close() // Resets terminal mode. return nil } + + if err := rpc.SetDefaultOpenRPCSchemaRaw(openrpc.OpenRPCSchema); err != nil { + log.Crit("Setting OpenRPC default", "error", err) + } } func main() { diff --git a/internal/openrpc/openrpc_schema.go b/internal/openrpc/openrpc_schema.go new file mode 100644 index 000000000000..414f830ef536 --- /dev/null +++ b/internal/openrpc/openrpc_schema.go @@ -0,0 +1,1838 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package openrpc + +// This file contains a string constant containing the JSON schema data for OpenRPC. + +// OpenRPCSchema defines the default full suite of possibly available go-ethereum RPC +// methods. +const OpenRPCSchema = ` +{ + "openrpc": "1.0.0", + "info": { + "version": "1.0.10", + "title": "Ubiq JSON-RPC", + "description": "This API lets you interact with an EVM-based client via JSON-RPC", + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "methods": [ + { + "name": "web3_clientVersion", + "description": "Returns the version of the current client", + "summary": "current client version", + "params": [], + "result": { + "name": "clientVersion", + "description": "client version", + "schema": { + "title": "clientVersion", + "type": "string" + } + } + }, + { + "name": "web3_sha3", + "summary": "Hashes data", + "description": "Hashes data using the Keccak-256 algorithm", + "params": [ + { + "name": "data", + "description": "data to hash using the Keccak-256 algorithm", + "summary": "data to hash", + "schema": { + "title": "datahash", + "type": "string", + "pattern": "^0x[a-fA-F\\d]+$" + } + } + ], + "result": { + "name": "hashedData", + "description": "Keccak-256 hash of the given data", + "schema": { + "$ref": "#/components/schemas/Keccak" + } + }, + "examples": [ + { + "name": "sha3Example", + "params": [ + { + "name": "sha3ParamExample", + "value": "0x68656c6c6f20776f726c64" + } + ], + "result": { + "name": "sha3ResultExample", + "value": "0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad" + } + } + ] + }, + { + "name": "net_listening", + "summary": "returns listening status", + "description": "Determines if this client is listening for new network connections.", + "params": [], + "result": { + "name": "netListeningResult", + "description": "` + "`" + `true` + "`" + ` if listening is active or ` + "`" + `false` + "`" + ` if listening is not active", + "schema": { + "title": "isNetListening", + "type": "boolean" + } + }, + "examples": [ + { + "name": "netListeningTrueExample", + "description": "example of true result for net_listening", + "params": [], + "result": { + "name": "netListeningExampleFalseResult", + "value": true + } + } + ] + }, + { + "name": "net_peerCount", + "summary": "number of peers", + "description": "Returns the number of peers currently connected to this client.", + "params": [], + "result": { + "name": "quantity", + "description": "number of connected peers.", + "schema": { + "title": "numConnectedPeers", + "description": "Hex representation of number of connected peers", + "type": "string" + } + } + }, + { + "name": "net_version", + "summary": "chain ID associated with network", + "description": "Returns the chain ID associated with the current network.", + "params": [], + "result": { + "name": "chainID", + "description": "chain ID associated with the current network", + "schema": { + "title": "chainID", + "type": "string", + "pattern": "^0x[a-fA-F\\d]+$" + } + } + }, + { + "name": "eth_blockNumber", + "summary": "Returns the number of most recent block.", + "params": [], + "result": { + "$ref": "#/components/contentDescriptors/BlockNumber" + } + }, + { + "name": "eth_call", + "summary": "Executes a new message call (locally) immediately without creating a transaction on the block chain.", + "params": [ + { + "$ref": "#/components/contentDescriptors/Transaction" + }, + { + "$ref": "#/components/contentDescriptors/BlockNumber" + } + ], + "result": { + "name": "returnValue", + "description": "The return value of the executed contract", + "schema": { + "$ref": "#/components/schemas/Bytes" + } + } + }, + { + "name": "eth_chainId", + "summary": "Returns the currently configured chain id", + "description": "Returns the currently configured chain id, a value used in replay-protected transaction signing as introduced by [EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md).", + "params": [], + "result": { + "name": "chainId", + "description": "hex format integer of the current chain id. Defaults are mainnet=8", + "schema": { + "title": "chainId", + "type": "string", + "pattern": "^0x[a-fA-F\\d]+$" + } + } + }, + { + "name": "eth_coinbase", + "summary": "Returns the client coinbase address.", + "params": [], + "result": { + "name": "address", + "description": "The address owned by the client that is used as default for things like the mining reward", + "schema": { + "$ref": "#/components/schemas/Address" + } + } + }, + { + "name": "eth_estimateGas", + "summary": "Generates and returns an estimate of how much gas is necessary to allow the transaction to complete. The transaction will not be added to the blockchain. Note that the estimate may be significantly more than the amount of gas actually used by the transaction, for a variety of reasons including EVM mechanics and node performance.", + "params": [ + { + "$ref": "#/components/contentDescriptors/Transaction" + } + ], + "result": { + "name": "gasUsed", + "description": "The amount of gas used", + "schema": { + "$ref": "#/components/schemas/Integer" + } + } + }, + { + "name": "eth_gasPrice", + "summary": "Returns the current price per gas in wei", + "params": [], + "result": { + "$ref": "#/components/contentDescriptors/GasPrice" + } + }, + { + "name": "eth_getBalance", + "summary": "Returns balance of a given account or contract", + "params": [ + { + "name": "address", + "required": true, + "description": "The address of the acccount or contract", + "schema": { + "$ref": "#/components/schemas/Address" + } + }, + { + "name": "blockNumber", + "description": "A BlockNumber at which to request the balance", + "schema": { + "$ref": "#/components/schemas/BlockNumber" + } + } + ], + "result": { + "name": "getBalanceResult", + "schema": { + "title": "getBalanceResult", + "oneOf": [ + { + "$ref": "#/components/schemas/Integer" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + } + } + }, + { + "name": "eth_getBlockByHash", + "summary": "Gets a block for a given hash", + "params": [ + { + "name": "blockHash", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlockHash" + } + }, + { + "name": "includeTransactions", + "description": "If ` + "`" + `true` + "`" + ` it returns the full transaction objects, if ` + "`" + `false` + "`" + ` only the hashes of the transactions.", + "required": true, + "schema": { + "title": "isTransactionsIncluded", + "type": "boolean" + } + } + ], + "result": { + "name": "getBlockByHashResult", + "schema": { + "title": "getBlockByHashResult", + "oneOf": [ + { + "$ref": "#/components/schemas/Block" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + } + } + }, + { + "name": "eth_getBlockByNumber", + "summary": "Gets a block for a given number salad", + "params": [ + { + "$ref": "#/components/contentDescriptors/BlockNumber" + }, + { + "name": "includeTransactions", + "description": "If ` + "`" + `true` + "`" + ` it returns the full transaction objects, if ` + "`" + `false` + "`" + ` only the hashes of the transactions.", + "required": true, + "schema": { + "title": "isTransactionsIncluded", + "type": "boolean" + } + } + ], + "result": { + "name": "getBlockByNumberResult", + "schema": { + "title": "getBlockByNumberResult", + "oneOf": [ + { + "$ref": "#/components/schemas/Block" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + } + } + }, + { + "name": "eth_getBlockTransactionCountByHash", + "summary": "Returns the number of transactions in a block from a block matching the given block hash.", + "params": [ + { + "$ref": "#/components/contentDescriptors/BlockHash" + } + ], + "result": { + "name": "blockTransactionCountByHash", + "description": "The Number of total transactions in the given block", + "schema": { + "title": "blockTransactionCountByHash", + "oneOf": [ + { + "$ref": "#/components/schemas/Integer" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + } + } + }, + { + "name": "eth_getBlockTransactionCountByNumber", + "summary": "Returns the number of transactions in a block from a block matching the given block number.", + "params": [ + { + "$ref": "#/components/contentDescriptors/BlockNumber" + } + ], + "result": { + "name": "blockTransactionCountByHash", + "description": "The Number of total transactions in the given block", + "schema": { + "title": "blockTransactionCountByHash", + "oneOf": [ + { + "$ref": "#/components/schemas/Integer" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + } + } + }, + { + "name": "eth_getCode", + "summary": "Returns code at a given contract address", + "params": [ + { + "name": "address", + "required": true, + "description": "The address of the contract", + "schema": { + "$ref": "#/components/schemas/Address" + } + }, + { + "name": "blockNumber", + "description": "A BlockNumber of which the code existed", + "schema": { + "$ref": "#/components/schemas/BlockNumber" + } + } + ], + "result": { + "name": "bytes", + "schema": { + "$ref": "#/components/schemas/Bytes" + } + } + }, + { + "name": "eth_getFilterChanges", + "summary": "Polling method for a filter, which returns an array of logs which occurred since last poll.", + "params": [ + { + "name": "filterId", + "required": true, + "schema": { + "$ref": "#/components/schemas/FilterId" + } + } + ], + "result": { + "name": "logResult", + "schema": { + "title": "logResult", + "type": "array", + "items": { + "$ref": "#/components/schemas/Log" + } + } + } + }, + { + "name": "eth_getFilterLogs", + "summary": "Returns an array of all logs matching filter with given id.", + "params": [ + { + "name": "filterId", + "required": true, + "schema": { + "$ref": "#/components/schemas/FilterId" + } + } + ], + "result": { + "$ref": "#/components/contentDescriptors/Logs" + } + }, + { + "name": "eth_getRawTransactionByHash", + "summary": "Returns raw transaction data of a transaction with the given hash.", + "params": [ + { + "$ref": "#/components/contentDescriptors/TransactionHash" + } + ], + "result": { + "name": "rawTransactionByHash", + "description": "The raw transaction data", + "schema": { + "$ref": "#/components/schemas/Bytes" + } + } + }, + { + "name": "eth_getRawTransactionByBlockHashAndIndex", + "summary": "Returns raw transaction data of a transaction with the given hash.", + "params": [ + { + "$ref": "#/components/contentDescriptors/BlockHash" + }, + { + "name": "index", + "description": "The ordering in which a transaction is mined within its block.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Integer" + } + } + ], + "result": { + "name": "rawTransaction", + "description": "The raw transaction data", + "schema": { + "$ref": "#/components/schemas/Bytes" + } + } + }, + { + "name": "eth_getRawTransactionByBlockNumberAndIndex", + "summary": "Returns raw transaction data of a transaction with the given hash.", + "params": [ + { + "$ref": "#/components/contentDescriptors/BlockNumber" + }, + { + "name": "index", + "description": "The ordering in which a transaction is mined within its block.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Integer" + } + } + ], + "result": { + "name": "rawTransaction", + "description": "The raw transaction data", + "schema": { + "$ref": "#/components/schemas/Bytes" + } + } + }, + { + "name": "eth_getLogs", + "summary": "Returns an array of all logs matching a given filter object.", + "params": [ + { + "$ref": "#/components/contentDescriptors/Filter" + } + ], + "result": { + "$ref": "#/components/contentDescriptors/Logs" + } + }, + { + "name": "eth_getStorageAt", + "summary": "Gets a storage value from a contract address, a position, and an optional blockNumber", + "params": [ + { + "$ref": "#/components/contentDescriptors/Address" + }, + { + "$ref": "#/components/contentDescriptors/Position" + }, + { + "$ref": "#/components/contentDescriptors/BlockNumber" + } + ], + "result": { + "name": "dataWord", + "schema": { + "$ref": "#/components/schemas/DataWord" + } + } + }, + { + "name": "eth_getTransactionByBlockHashAndIndex", + "summary": "Returns the information about a transaction requested by the block hash and index of which it was mined.", + "params": [ + { + "$ref": "#/components/contentDescriptors/BlockHash" + }, + { + "name": "index", + "description": "The ordering in which a transaction is mined within its block.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Integer" + } + } + ], + "result": { + "$ref": "#/components/contentDescriptors/TransactionResult" + }, + "examples": [ + { + "name": "nullExample", + "params": [ + { + "name": "blockHashExample", + "value": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + }, + { + "name": "indexExample", + "value": "0x0" + } + ], + "result": { + "name": "nullResultExample", + "value": null + } + } + ] + }, + { + "name": "eth_getTransactionByBlockNumberAndIndex", + "summary": "Returns the information about a transaction requested by the block hash and index of which it was mined.", + "params": [ + { + "$ref": "#/components/contentDescriptors/BlockNumber" + }, + { + "name": "index", + "description": "The ordering in which a transaction is mined within its block.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Integer" + } + } + ], + "result": { + "$ref": "#/components/contentDescriptors/TransactionResult" + } + }, + { + "name": "eth_getTransactionByHash", + "summary": "Returns the information about a transaction requested by transaction hash.", + "params": [ + { + "$ref": "#/components/contentDescriptors/TransactionHash" + } + ], + "result": { + "$ref": "#/components/contentDescriptors/TransactionResult" + } + }, + { + "name": "eth_getTransactionCount", + "summary": "Returns the number of transactions sent from an address", + "params": [ + { + "$ref": "#/components/contentDescriptors/Address" + }, + { + "$ref": "#/components/contentDescriptors/BlockNumber" + } + ], + "result": { + "name": "transactionCount", + "schema": { + "title": "nonceOrNull", + "oneOf": [ + { + "$ref": "#/components/schemas/Nonce" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + } + } + }, + { + "name": "eth_getTransactionReceipt", + "summary": "Returns the receipt information of a transaction by its hash.", + "params": [ + { + "$ref": "#/components/contentDescriptors/TransactionHash" + } + ], + "result": { + "name": "transactionReceiptResult", + "description": "returns either a receipt or null", + "schema": { + "title": "transactionReceiptOrNull", + "oneOf": [ + { + "$ref": "#/components/schemas/Receipt" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + } + } + }, + { + "name": "eth_getUncleByBlockHashAndIndex", + "summary": "Returns information about a uncle of a block by hash and uncle index position.", + "params": [ + { + "$ref": "#/components/contentDescriptors/BlockHash" + }, + { + "name": "index", + "description": "The ordering in which a uncle is included within its block.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Integer" + } + } + ], + "result": { + "name": "uncle", + "schema": { + "title": "uncleOrNull", + "oneOf": [ + { + "$ref": "#/components/schemas/Uncle" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + } + } + }, + { + "name": "eth_getUncleByBlockNumberAndIndex", + "summary": "Returns information about a uncle of a block by hash and uncle index position.", + "params": [ + { + "name": "uncleBlockNumber", + "description": "The block in which the uncle was included", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlockNumber" + } + }, + { + "name": "index", + "description": "The ordering in which a uncle is included within its block.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Integer" + } + } + ], + "result": { + "name": "uncleResult", + "description": "returns an uncle or null", + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/Uncle" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + } + }, + "examples": [ + { + "name": "nullResultExample", + "params": [ + { + "name": "uncleBlockNumberExample", + "value": "0x0" + }, + { + "name": "uncleBlockNumberIndexExample", + "value": "0x0" + } + ], + "result": { + "name": "nullResultExample", + "value": null + } + } + ] + }, + { + "name": "eth_getUncleCountByBlockHash", + "summary": "Returns the number of uncles in a block from a block matching the given block hash.", + "params": [ + { + "$ref": "#/components/contentDescriptors/BlockHash" + } + ], + "result": { + "name": "uncleCountResult", + "schema": { + "title": "uncleCountOrNull", + "oneOf": [ + { + "description": "The Number of total uncles in the given block", + "$ref": "#/components/schemas/Integer" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + } + } + }, + { + "name": "eth_getUncleCountByBlockNumber", + "summary": "Returns the number of uncles in a block from a block matching the given block number.", + "params": [ + { + "$ref": "#/components/contentDescriptors/BlockNumber" + } + ], + "result": { + "name": "uncleCountResult", + "schema": { + "title": "uncleCountOrNull", + "oneOf": [ + { + "description": "The Number of total uncles in the given block", + "$ref": "#/components/schemas/Integer" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + } + } + }, + { + "name": "eth_getProof", + "summary": "Returns the account- and storage-values of the specified account including the Merkle-proof.", + "params": [ + { + "name": "address", + "description": "The address of the account or contract", + "required": true, + "schema": { + "$ref": "#/components/schemas/Address" + } + }, + { + "name": "storageKeys", + "required": true, + "schema": { + "title": "storageKeys", + "description": "The storage keys of all the storage slots being requested", + "items": { + "description": "A storage key is indexed from the solidity compiler by the order it is declaired. For mappings it uses the keccak of the mapping key with its position (and recursively for X-dimensional mappings)", + "$ref": "#/components/schemas/Integer" + } + } + }, + { + "$ref": "#/components/contentDescriptors/BlockNumber" + } + ], + "result": { + "name": "account", + "schema": { + "title": "proofAccountOrNull", + "oneOf": [ + { + "title": "proofAccount", + "type": "object", + "description": "The merkle proofs of the specified account connecting them to the blockhash of the block specified", + "properties": { + "address": { + "description": "The address of the account or contract of the request", + "$ref": "#/components/schemas/Address" + }, + "accountProof": { + "$ref": "#/components/schemas/AccountProof" + }, + "balance": { + "description": "The balance of the account or contract of the request", + "$ref": "#/components/schemas/Integer" + }, + "codeHash": { + "description": "The code hash of the contract of the request (keccak(NULL) if external account)", + "$ref": "#/components/schemas/Keccak" + }, + "nonce": { + "description": "The transaction count of the account or contract of the request", + "$ref": "#/components/schemas/Nonce" + }, + "storageHash": { + "description": "The storage hash of the contract of the request (keccak(rlp(NULL)) if external account)", + "$ref": "#/components/schemas/Keccak" + }, + "storageProof": { + "$ref": "#/components/schemas/StorageProof" + } + } + }, + { + "$ref": "#/components/schemas/Null" + } + ] + } + } + }, + { + "name": "eth_getWork", + "summary": "Returns the hash of the current block, the seedHash, and the boundary condition to be met ('target').", + "params": [], + "result": { + "name": "work", + "schema": { + "type": "array", + "items": [ + { + "$ref": "#/components/schemas/PowHash" + }, + { + "$ref": "#/components/schemas/SeedHash" + }, + { + "$ref": "#/components/schemas/Difficulty" + } + ] + } + } + }, + { + "name": "eth_hashrate", + "summary": "Returns the number of hashes per second that the node is mining with.", + "params": [], + "result": { + "name": "hashesPerSecond", + "schema": { + "description": "Integer of the number of hashes per second", + "$ref": "#/components/schemas/Integer" + } + } + }, + { + "name": "eth_mining", + "summary": "Returns true if client is actively mining new blocks.", + "params": [], + "result": { + "name": "mining", + "schema": { + "description": "Whether of not the client is mining", + "type": "boolean" + } + } + }, + { + "name": "eth_newBlockFilter", + "summary": "Creates a filter in the node, to notify when a new block arrives. To check if the state has changed, call eth_getFilterChanges.", + "params": [], + "result": { + "$ref": "#/components/contentDescriptors/FilterId" + } + }, + { + "name": "eth_newFilter", + "summary": "Creates a filter object, based on filter options, to notify when the state changes (logs). To check if the state has changed, call eth_getFilterChanges.", + "params": [ + { + "$ref": "#/components/contentDescriptors/Filter" + } + ], + "result": { + "name": "filterId", + "schema": { + "description": "The filter ID for use in ` + "`" + `eth_getFilterChanges` + "`" + `", + "$ref": "#/components/schemas/Integer" + } + } + }, + { + "name": "eth_newPendingTransactionFilter", + "summary": "Creates a filter in the node, to notify when new pending transactions arrive. To check if the state has changed, call eth_getFilterChanges.", + "params": [], + "result": { + "$ref": "#/components/contentDescriptors/FilterId" + } + }, + { + "name": "eth_pendingTransactions", + "summary": "Returns the pending transactions list", + "params": [], + "result": { + "name": "pendingTransactions", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Transaction" + } + } + } + }, + { + "name": "eth_protocolVersion", + "summary": "Returns the current ubiq protocol version.", + "params": [], + "result": { + "name": "protocolVersion", + "schema": { + "description": "The current ubiq protocol version", + "$ref": "#/components/schemas/Integer" + } + } + }, + { + "name": "eth_sendRawTransaction", + "summary": "Creates new message call transaction or a contract creation for signed transactions.", + "params": [ + { + "name": "signedTransactionData", + "required": true, + "description": "The signed transaction data", + "schema": { + "$ref": "#/components/schemas/Bytes" + } + } + ], + "result": { + "name": "transactionHash", + "schema": { + "description": "The transaction hash, or the zero hash if the transaction is not yet available.", + "$ref": "#/components/schemas/Keccak" + } + } + }, + { + "name": "eth_submitHashrate", + "summary": "Returns an array of all logs matching a given filter object.", + "params": [ + { + "name": "hashRate", + "required": true, + "schema": { + "$ref": "#/components/schemas/DataWord" + } + }, + { + "name": "id", + "required": true, + "description": "String identifiying the client", + "schema": { + "$ref": "#/components/schemas/DataWord" + } + } + ], + "result": { + "name": "submitHashRateSuccess", + "schema": { + "type": "boolean", + "description": "whether of not submitting went through successfully" + } + } + }, + { + "name": "eth_submitWork", + "summary": "Used for submitting a proof-of-work solution.", + "params": [ + { + "$ref": "#/components/contentDescriptors/Nonce" + }, + { + "name": "powHash", + "required": true, + "schema": { + "$ref": "#/components/schemas/PowHash" + } + }, + { + "name": "mixHash", + "required": true, + "schema": { + "$ref": "#/components/schemas/MixHash" + } + } + ], + "result": { + "name": "solutionValid", + "description": "returns true if the provided solution is valid, otherwise false.", + "schema": { + "type": "boolean", + "description": "Whether or not the provided solution is valid" + } + }, + "examples": [ + { + "name": "submitWorkExample", + "params": [ + { + "name": "nonceExample", + "description": "example of a number only used once", + "value": "0x0000000000000001" + }, + { + "name": "powHashExample", + "description": "proof of work to submit", + "value": "0x6bf2cAE0dE3ec3ecA5E194a6C6e02cf42aADfe1C2c4Fff12E5D36C3Cf7297F22" + }, + { + "name": "mixHashExample", + "description": "the mix digest example", + "value": "0xD1FE5700000000000000000000000000D1FE5700000000000000000000000000" + } + ], + "result": { + "name": "solutionInvalidExample", + "description": "this example should return ` + "`" + `false` + "`" + ` as it is not a valid pow to submit", + "value": false + } + } + ] + }, + { + "name": "eth_syncing", + "summary": "Returns an object with data about the sync status or false.", + "params": [], + "result": { + "name": "syncing", + "schema": { + "oneOf": [ + { + "description": "An object with sync status data", + "type": "object", + "properties": { + "startingBlock": { + "description": "Block at which the import started (will only be reset, after the sync reached his head)", + "$ref": "#/components/schemas/Integer" + }, + "currentBlock": { + "description": "The current block, same as eth_blockNumber", + "$ref": "#/components/schemas/Integer" + }, + "highestBlock": { + "description": "The estimated highest block", + "$ref": "#/components/schemas/Integer" + }, + "knownStates": { + "description": "The known states", + "$ref": "#/components/schemas/Integer" + }, + "pulledStates": { + "description": "The pulled states", + "$ref": "#/components/schemas/Integer" + } + } + }, + { + "type": "boolean", + "description": "The value ` + "`" + `false` + "`" + ` indicating that syncing is complete" + } + ] + } + } + }, + { + "name": "eth_uninstallFilter", + "summary": "Uninstalls a filter with given id. Should always be called when watch is no longer needed. Additionally Filters timeout when they aren't requested with eth_getFilterChanges for a period of time.", + "params": [ + { + "name": "filterId", + "required": true, + "schema": { + "$ref": "#/components/schemas/FilterId" + } + } + ], + "result": { + "name": "filterUninstalledSuccess", + "schema": { + "type": "boolean", + "description": "Whether of not the filter was successfully uninstalled" + } + } + } + ], + "components": { + "schemas": { + "ProofNode": { + "type": "string", + "description": "An indiviual node used to prove a path down a merkle-patricia-tree", + "$ref": "#/components/schemas/Bytes" + }, + "AccountProof": { + "$ref": "#/components/schemas/ProofNodes" + }, + "StorageProof": { + "type": "array", + "description": "Current block header PoW hash.", + "items": { + "type": "object", + "description": "Object proving a relationship of a storage value to an account's storageHash.", + "properties": { + "key": { + "description": "The key used to get the storage slot in its account tree", + "$ref": "#/components/schemas/Integer" + }, + "value": { + "description": "The value of the storage slot in its account tree", + "$ref": "#/components/schemas/Integer" + }, + "proof": { + "$ref": "#/components/schemas/ProofNodes" + } + } + } + }, + "ProofNodes": { + "type": "array", + "description": "The set of node values needed to traverse a patricia merkle tree (from root to leaf) to retrieve a value", + "items": { + "$ref": "#/components/schemas/ProofNode" + } + }, + "PowHash": { + "description": "Current block header PoW hash.", + "$ref": "#/components/schemas/DataWord" + }, + "SeedHash": { + "description": "The seed hash used for the DAG.", + "$ref": "#/components/schemas/DataWord" + }, + "MixHash": { + "description": "The mix digest.", + "$ref": "#/components/schemas/DataWord" + }, + "Difficulty": { + "description": "The boundary condition ('target'), 2^256 / difficulty.", + "$ref": "#/components/schemas/DataWord" + }, + "FilterId": { + "type": "string", + "description": "An identifier used to reference the filter." + }, + "BlockHash": { + "type": "string", + "pattern": "^0x[a-fA-F\\d]{64}$", + "description": "The hex representation of the Keccak 256 of the RLP encoded block" + }, + "BlockNumber": { + "type": "string", + "pattern": "^0x[a-fA-F\\d]+$", + "description": "The hex representation of the block's height" + }, + "BlockNumberTag": { + "type": "string", + "description": "The optional block height description", + "enum": [ + "earliest", + "latest", + "pending" + ] + }, + "Receipt": { + "type": "object", + "description": "The receipt of a transaction", + "required": [ + "blockHash", + "blockNumber", + "contractAddress", + "cumulativeGasUsed", + "from", + "gasUsed", + "logs", + "logsBloom", + "to", + "transactionHash", + "transactionIndex" + ], + "properties": { + "blockHash": { + "description": "BlockHash of the block in which the transaction was mined", + "$ref": "#/components/schemas/BlockHash" + }, + "blockNumber": { + "description": "BlockNumber of the block in which the transaction was mined", + "$ref": "#/components/schemas/BlockNumber" + }, + "contractAddress": { + "description": "The contract address created, if the transaction was a contract creation, otherwise null", + "$ref": "#/components/schemas/Address" + }, + "cumulativeGasUsed": { + "description": "The gas units used by the transaction", + "$ref": "#/components/schemas/Integer" + }, + "from": { + "description": "The sender of the transaction", + "$ref": "#/components/schemas/Address" + }, + "gasUsed": { + "description": "The total gas used by the transaction", + "$ref": "#/components/schemas/Integer" + }, + "logs": { + "type": "array", + "description": "An array of all the logs triggered during the transaction", + "items": { + "$ref": "#/components/schemas/Log" + } + }, + "logsBloom": { + "$ref": "#/components/schemas/BloomFilter" + }, + "to": { + "description": "Destination address of the transaction", + "$ref": "#/components/schemas/Address" + }, + "transactionHash": { + "description": "Keccak 256 of the transaction", + "$ref": "#/components/schemas/Keccak" + }, + "transactionIndex": { + "description": "An array of all the logs triggered during the transaction", + "$ref": "#/components/schemas/BloomFilter" + }, + "postTransactionState": { + "description": "The intermediate stateRoot directly after transaction execution.", + "$ref": "#/components/schemas/Keccak" + }, + "status": { + "description": "Whether or not the transaction threw an error.", + "type": "boolean" + } + } + }, + "BloomFilter": { + "type": "string", + "description": "A 2048 bit bloom filter from the logs of the transaction. Each log sets 3 bits though taking the low-order 11 bits of each of the first three pairs of bytes in a Keccak 256 hash of the log's byte series" + }, + "Log": { + "type": "object", + "description": "An indexed event generated during a transaction", + "properties": { + "address": { + "description": "Sender of the transaction", + "$ref": "#/components/schemas/Address" + }, + "blockHash": { + "description": "BlockHash of the block in which the transaction was mined", + "$ref": "#/components/schemas/BlockHash" + }, + "blockNumber": { + "description": "BlockNumber of the block in which the transaction was mined", + "$ref": "#/components/schemas/BlockNumber" + }, + "data": { + "description": "The data/input string sent along with the transaction", + "$ref": "#/components/schemas/Bytes" + }, + "logIndex": { + "description": "The index of the event within its transaction, null when its pending", + "$ref": "#/components/schemas/Integer" + }, + "removed": { + "schema": { + "description": "Whether or not the log was orphaned off the main chain", + "type": "boolean" + } + }, + "topics": { + "type": "array", + "items": { + "topic": { + "description": "32 Bytes DATA of indexed log arguments. (In solidity: The first topic is the hash of the signature of the event (e.g. Deposit(address,bytes32,uint256))", + "$ref": "#/components/schemas/DataWord" + } + } + }, + "transactionHash": { + "description": "The hash of the transaction in which the log occurred", + "$ref": "#/components/schemas/Keccak" + }, + "transactionIndex": { + "description": "The index of the transaction in which the log occurred", + "$ref": "#/components/schemas/Integer" + } + } + }, + "Uncle": { + "type": "object", + "description": "Orphaned blocks that can be included in the chain but at a lower block reward. NOTE: An uncle doesn’t contain individual transactions.", + "properties": { + "number": { + "description": "The block number or null when its the pending block", + "$ref": "#/components/schemas/IntOrPending" + }, + "hash": { + "description": "The block hash or null when its the pending block", + "$ref": "#/components/schemas/KeccakOrPending" + }, + "parentHash": { + "description": "Hash of the parent block", + "$ref": "#/components/schemas/Keccak" + }, + "nonce": { + "description": "Randomly selected number to satisfy the proof-of-work or null when its the pending block", + "$ref": "#/components/schemas/IntOrPending" + }, + "sha3Uncles": { + "description": "Keccak hash of the uncles data in the block", + "$ref": "#/components/schemas/Keccak" + }, + "logsBloom": { + "type": "string", + "description": "The bloom filter for the logs of the block or null when its the pending block", + "pattern": "^0x[a-fA-F\\d]+$" + }, + "transactionsRoot": { + "description": "The root of the transactions trie of the block.", + "$ref": "#/components/schemas/Keccak" + }, + "stateRoot": { + "description": "The root of the final state trie of the block", + "$ref": "#/components/schemas/Keccak" + }, + "receiptsRoot": { + "description": "The root of the receipts trie of the block", + "$ref": "#/components/schemas/Keccak" + }, + "miner": { + "description": "The address of the beneficiary to whom the mining rewards were given or null when its the pending block", + "oneOf": [ + { + "$ref": "#/components/schemas/Address" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + }, + "difficulty": { + "type": "string", + "description": "Integer of the difficulty for this block" + }, + "totalDifficulty": { + "description": "Integer of the total difficulty of the chain until this block", + "$ref": "#/components/schemas/IntOrPending" + }, + "extraData": { + "type": "string", + "description": "The 'extra data' field of this block" + }, + "size": { + "type": "string", + "description": "Integer the size of this block in bytes" + }, + "gasLimit": { + "type": "string", + "description": "The maximum gas allowed in this block" + }, + "gasUsed": { + "type": "string", + "description": "The total used gas by all transactions in this block" + }, + "timestamp": { + "type": "string", + "description": "The unix timestamp for when the block was collated" + }, + "uncles": { + "description": "Array of uncle hashes", + "type": "array", + "items": { + "description": "Block hash of the RLP encoding of an uncle block", + "$ref": "#/components/schemas/Keccak" + } + } + } + }, + "Block": { + "type": "object", + "properties": { + "number": { + "description": "The block number or null when its the pending block", + "$ref": "#/components/schemas/IntOrPending" + }, + "hash": { + "description": "The block hash or null when its the pending block", + "$ref": "#/components/schemas/KeccakOrPending" + }, + "parentHash": { + "description": "Hash of the parent block", + "$ref": "#/components/schemas/Keccak" + }, + "nonce": { + "description": "Randomly selected number to satisfy the proof-of-work or null when its the pending block", + "$ref": "#/components/schemas/IntOrPending" + }, + "sha3Uncles": { + "description": "Keccak hash of the uncles data in the block", + "$ref": "#/components/schemas/Keccak" + }, + "logsBloom": { + "type": "string", + "description": "The bloom filter for the logs of the block or null when its the pending block", + "pattern": "^0x[a-fA-F\\d]+$" + }, + "transactionsRoot": { + "description": "The root of the transactions trie of the block.", + "$ref": "#/components/schemas/Keccak" + }, + "stateRoot": { + "description": "The root of the final state trie of the block", + "$ref": "#/components/schemas/Keccak" + }, + "receiptsRoot": { + "description": "The root of the receipts trie of the block", + "$ref": "#/components/schemas/Keccak" + }, + "miner": { + "description": "The address of the beneficiary to whom the mining rewards were given or null when its the pending block", + "oneOf": [ + { + "$ref": "#/components/schemas/Address" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + }, + "difficulty": { + "type": "string", + "description": "Integer of the difficulty for this block" + }, + "totalDifficulty": { + "description": "Integer of the total difficulty of the chain until this block", + "$ref": "#/components/schemas/IntOrPending" + }, + "extraData": { + "type": "string", + "description": "The 'extra data' field of this block" + }, + "size": { + "type": "string", + "description": "Integer the size of this block in bytes" + }, + "gasLimit": { + "type": "string", + "description": "The maximum gas allowed in this block" + }, + "gasUsed": { + "type": "string", + "description": "The total used gas by all transactions in this block" + }, + "timestamp": { + "type": "string", + "description": "The unix timestamp for when the block was collated" + }, + "transactions": { + "description": "Array of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter", + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/Transaction" + }, + { + "$ref": "#/components/schemas/TransactionHash" + } + ] + } + }, + "uncles": { + "description": "Array of uncle hashes", + "type": "array", + "items": { + "description": "Block hash of the RLP encoding of an uncle block", + "$ref": "#/components/schemas/Keccak" + } + } + } + }, + "Transaction": { + "type": "object", + "required": [ + "gas", + "gasPrice", + "nonce" + ], + "properties": { + "blockHash": { + "description": "Hash of the block where this transaction was in. null when its pending", + "$ref": "#/components/schemas/KeccakOrPending" + }, + "blockNumber": { + "description": "Block number where this transaction was in. null when its pending", + "$ref": "#/components/schemas/IntOrPending" + }, + "from": { + "description": "Address of the sender", + "$ref": "#/components/schemas/Address" + }, + "gas": { + "type": "string", + "description": "The gas limit provided by the sender in Wei" + }, + "gasPrice": { + "type": "string", + "description": "The gas price willing to be paid by the sender in Wei" + }, + "hash": { + "$ref": "#/components/schemas/TransactionHash" + }, + "input": { + "type": "string", + "description": "The data field sent with the transaction" + }, + "nonce": { + "description": "The total number of prior transactions made by the sender", + "$ref": "#/components/schemas/Nonce" + }, + "to": { + "description": "address of the receiver. null when its a contract creation transaction", + "$ref": "#/components/schemas/Address" + }, + "transactionIndex": { + "description": "Integer of the transaction's index position in the block. null when its pending", + "$ref": "#/components/schemas/IntOrPending" + }, + "value": { + "description": "Value of ubiq being transferred in Wei", + "$ref": "#/components/schemas/Keccak" + }, + "v": { + "type": "string", + "description": "ECDSA recovery id" + }, + "r": { + "type": "string", + "description": "ECDSA signature r" + }, + "s": { + "type": "string", + "description": "ECDSA signature s" + } + } + }, + "TransactionHash": { + "type": "string", + "description": "Keccak 256 Hash of the RLP encoding of a transaction", + "$ref": "#/components/schemas/Keccak" + }, + "KeccakOrPending": { + "oneOf": [ + { + "$ref": "#/components/schemas/Keccak" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + }, + "IntOrPending": { + "oneOf": [ + { + "$ref": "#/components/schemas/Integer" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + }, + "Keccak": { + "type": "string", + "description": "Hex representation of a Keccak 256 hash", + "pattern": "^0x[a-fA-F\\d]{64}$" + }, + "Nonce": { + "description": "A number only to be used once", + "pattern": "^0x[a-fA-F0-9]+$", + "type": "string" + }, + "Null": { + "type": "null", + "description": "Null" + }, + "Integer": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "Hex representation of the integer" + }, + "Address": { + "type": "string", + "pattern": "^0x[a-fA-F\\d]{40}$" + }, + "Position": { + "type": "string", + "description": "Hex representation of the storage slot where the variable exists", + "pattern": "^0x([a-fA-F0-9]?)+$" + }, + "DataWord": { + "type": "string", + "description": "Hex representation of a 256 bit unit of data", + "pattern": "^0x([a-fA-F\\d]{64})?$" + }, + "Bytes": { + "type": "string", + "description": "Hex representation of a variable length byte array", + "pattern": "^0x([a-fA-F0-9]?)+$" + } + }, + "contentDescriptors": { + "Block": { + "name": "block", + "summary": "A block", + "description": "A block object", + "schema": { + "$ref": "#/components/schemas/Block" + } + }, + "Null": { + "name": "Null", + "description": "JSON Null value", + "summary": "Null value", + "schema": { + "type": "null", + "description": "Null value" + } + }, + "Signature": { + "name": "signature", + "summary": "The signature.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Bytes", + "pattern": "0x^([A-Fa-f0-9]{2}){65}$" + } + }, + "GasPrice": { + "name": "gasPrice", + "required": true, + "schema": { + "description": "Integer of the current gas price", + "$ref": "#/components/schemas/Integer" + } + }, + "Transaction": { + "required": true, + "name": "transaction", + "schema": { + "$ref": "#/components/schemas/Transaction" + } + }, + "TransactionResult": { + "name": "transactionResult", + "description": "Returns a transaction or null", + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/Transaction" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + } + }, + "Message": { + "name": "message", + "required": true, + "schema": { + "$ref": "#/components/schemas/Bytes" + } + }, + "Filter": { + "name": "filter", + "required": true, + "schema": { + "type": "object", + "description": "A filter used to monitor the blockchain for log/events", + "properties": { + "fromBlock": { + "description": "Block from which to begin filtering events", + "$ref": "#/components/schemas/BlockNumber" + }, + "toBlock": { + "description": "Block from which to end filtering events", + "$ref": "#/components/schemas/BlockNumber" + }, + "address": { + "oneOf": [ + { + "type": "string", + "description": "Address of the contract from which to monitor events", + "$ref": "#/components/schemas/Address" + }, + { + "type": "array", + "description": "List of contract addresses from which to monitor events", + "items": { + "$ref": "#/components/schemas/Address" + } + } + ] + }, + "topics": { + "type": "array", + "description": "Array of 32 Bytes DATA topics. Topics are order-dependent. Each topic can also be an array of DATA with 'or' options", + "items": { + "description": "Indexable 32 bytes piece of data (made from the event's function signature in solidity)", + "$ref": "#/components/schemas/DataWord" + } + } + } + } + }, + "Address": { + "name": "address", + "required": true, + "schema": { + "$ref": "#/components/schemas/Address" + } + }, + "BlockHash": { + "name": "blockHash", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlockHash" + } + }, + "Nonce": { + "name": "nonce", + "required": true, + "schema": { + "$ref": "#/components/schemas/Nonce" + } + }, + "Position": { + "name": "key", + "required": true, + "schema": { + "$ref": "#/components/schemas/Position" + } + }, + "Logs": { + "name": "logs", + "description": "An array of all logs matching filter with given id.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Log" + } + } + }, + "FilterId": { + "name": "filterId", + "schema": { + "description": "The filter ID for use in ` + "`" + `eth_getFilterChanges` + "`" + `", + "$ref": "#/components/schemas/Integer" + } + }, + "BlockNumber": { + "name": "blockNumber", + "required": true, + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/BlockNumber" + }, + { + "$ref": "#/components/schemas/BlockNumberTag" + } + ] + } + }, + "TransactionHash": { + "name": "transactionHash", + "required": true, + "schema": { + "$ref": "#/components/schemas/TransactionHash" + } + } + } + } + } + +` diff --git a/internal/openrpc/openrpc_schema_test.go b/internal/openrpc/openrpc_schema_test.go new file mode 100644 index 000000000000..a355aada9d3c --- /dev/null +++ b/internal/openrpc/openrpc_schema_test.go @@ -0,0 +1,14 @@ +package openrpc_test + +import ( + "testing" + + "github.com/ubiq/go-ubiq/internal/openrpc" + "github.com/ubiq/go-ubiq/rpc" +) + +func TestDefaultSchema(t *testing.T) { + if err := rpc.SetDefaultOpenRPCSchemaRaw(openrpc.OpenRPCSchema); err != nil { + t.Fatal(err) + } +} diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index 4d3bf07d4079..aa76d00694ef 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -22,7 +22,7 @@ var Modules = map[string]string{ "admin": Admin_JS, "chequebook": Chequebook_JS, "clique": Clique_JS, - "ubqhash": Ubqhash_JS, + "ubqhash": Ubqhash_JS, "debug": Debug_JS, "eth": Eth_JS, "miner": Miner_JS, @@ -625,7 +625,13 @@ web3._extend({ const RPC_JS = ` web3._extend({ property: 'rpc', - methods: [], + methods: [ + new web3._extend.Method({ + name: 'discover', + call: 'rpc.discover', + params: 0 + }), + ], properties: [ new web3._extend.Property({ name: 'modules', diff --git a/rpc/json.go b/rpc/json.go index b2e8c7bab3f3..5965cd8b5355 100644 --- a/rpc/json.go +++ b/rpc/json.go @@ -31,7 +31,6 @@ import ( const ( vsn = "2.0" - serviceMethodSeparator = "_" subscribeMethodSuffix = "_subscribe" unsubscribeMethodSuffix = "_unsubscribe" notificationMethodSuffix = "_subscription" @@ -39,7 +38,20 @@ const ( defaultWriteTimeout = 10 * time.Second // used if context has no deadline ) -var null = json.RawMessage("null") +var ( + null = json.RawMessage("null") + serviceMethodSeparators = []string{"_", "."} + errInvalidMethodName = errors.New("invalid method name") +) + +func elementizeMethodName(methodName string) (module, method string, err error) { + for _, sep := range serviceMethodSeparators { + if s := strings.SplitN(methodName, sep, 2); len(s) == 2 { + return s[0], s[1], nil + } + } + return "", "", errInvalidMethodName +} type subscriptionResult struct { ID string `json:"subscription"` @@ -82,8 +94,8 @@ func (msg *jsonrpcMessage) isUnsubscribe() bool { } func (msg *jsonrpcMessage) namespace() string { - elem := strings.SplitN(msg.Method, serviceMethodSeparator, 2) - return elem[0] + module, _, _ := elementizeMethodName(msg.Method) + return module // even if err != nil, empty string is returned so err can be ignored } func (msg *jsonrpcMessage) String() string { diff --git a/rpc/openrpc.go b/rpc/openrpc.go new file mode 100644 index 000000000000..fce062fbd2d9 --- /dev/null +++ b/rpc/openrpc.go @@ -0,0 +1,25 @@ +// Copyright 2019 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package rpc + +type OpenRPCDiscoverSchemaT struct { + OpenRPC string `json:"openrpc"` + Info map[string]interface{} `json:"info"` + Servers []map[string]interface{} `json:"servers"` + Methods []map[string]interface{} `json:"methods"` + Components map[string]interface{} `json:"components"` +} diff --git a/rpc/server.go b/rpc/server.go index bf8da478319f..3921e75f84a3 100644 --- a/rpc/server.go +++ b/rpc/server.go @@ -18,6 +18,9 @@ package rpc import ( "context" + "encoding/json" + "errors" + "fmt" "io" "sync/atomic" @@ -27,6 +30,16 @@ import ( const MetadataApi = "rpc" +var ( + // defaultOpenRPCSchemaRaw can be used to establish a default (package-wide) OpenRPC schema from raw JSON. + // Methods will be cross referenced with actual registered method names in order to serve + // only server-enabled methods, enabling user and on-the-fly server endpoint availability configuration. + defaultOpenRPCSchemaRaw string + + errOpenRPCDiscoverUnavailable = errors.New("openrpc discover data unavailable") + errOpenRPCDiscoverSchemaInvalid = errors.New("openrpc discover data invalid") +) + // CodecOption specifies which type of messages a codec supports. // // Deprecated: this option is no longer honored by Server. @@ -42,15 +55,21 @@ const ( // Server is an RPC server. type Server struct { - services serviceRegistry - idgen func() ID - run int32 - codecs mapset.Set + services serviceRegistry + idgen func() ID + run int32 + codecs mapset.Set + OpenRPCSchemaRaw string } // NewServer creates a new server instance with no registered handlers. func NewServer() *Server { - server := &Server{idgen: randomIDGenerator(), codecs: mapset.NewSet(), run: 1} + server := &Server{ + idgen: randomIDGenerator(), + codecs: mapset.NewSet(), + run: 1, + OpenRPCSchemaRaw: defaultOpenRPCSchemaRaw, + } // Register the default service providing meta information about the RPC service such // as the services and methods it offers. rpcService := &RPCService{server} @@ -58,6 +77,35 @@ func NewServer() *Server { return server } +func validateOpenRPCSchemaRaw(schemaJSON string) error { + if schemaJSON == "" { + return errOpenRPCDiscoverSchemaInvalid + } + var schema OpenRPCDiscoverSchemaT + if err := json.Unmarshal([]byte(schemaJSON), &schema); err != nil { + return fmt.Errorf("%v: %v", errOpenRPCDiscoverSchemaInvalid, err) + } + return nil +} + +// SetDefaultOpenRPCSchemaRaw validates and sets the package-wide OpenRPC schema data. +func SetDefaultOpenRPCSchemaRaw(schemaJSON string) error { + if err := validateOpenRPCSchemaRaw(schemaJSON); err != nil { + return err + } + defaultOpenRPCSchemaRaw = schemaJSON + return nil +} + +// SetOpenRPCSchemaRaw validates and sets the raw OpenRPC schema data for a server. +func (s *Server) SetOpenRPCSchemaRaw(schemaJSON string) error { + if err := validateOpenRPCSchemaRaw(schemaJSON); err != nil { + return err + } + s.OpenRPCSchemaRaw = schemaJSON + return nil +} + // RegisterName creates a service for the given receiver type under the given name. When no // methods on the given receiver match the criteria to be either a RPC method or a // subscription an error is returned. Otherwise a new service is created and added to the @@ -145,3 +193,66 @@ func (s *RPCService) Modules() map[string]string { } return modules } + +func (s *RPCService) methods() map[string][]string { + s.server.services.mu.Lock() + defer s.server.services.mu.Unlock() + + methods := make(map[string][]string) + for name, ser := range s.server.services.services { + for s := range ser.callbacks { + _, ok := methods[name] + if !ok { + methods[name] = []string{s} + } else { + methods[name] = append(methods[name], s) + } + } + } + return methods +} + +// Discover returns a configured schema that is audited for actual server availability. +// Only methods that the server makes available are included in the 'methods' array of +// the discover schema. Components are not audited. +func (s *RPCService) Discover() (schema *OpenRPCDiscoverSchemaT, err error) { + if s.server.OpenRPCSchemaRaw == "" { + return nil, errOpenRPCDiscoverUnavailable + } + schema = &OpenRPCDiscoverSchemaT{ + Servers: make([]map[string]interface{}, 0), + } + err = json.Unmarshal([]byte(s.server.OpenRPCSchemaRaw), schema) + if err != nil { + log.Crit("openrpc json umarshal", "error", err) + } + + // Audit documented schema methods vs. actual server availability + // This removes methods described in the OpenRPC JSON schema document + // which are not currently exposed on the server's API. + // This is done on the fly (as opposed to at servre init or schema setting) + // because it's possible that exposed APIs could be modified in proc. + schemaMethodsAvailable := []map[string]interface{}{} + serverMethodsAvailable := s.methods() + + for _, m := range schema.Methods { + module, path, err := elementizeMethodName(m["name"].(string)) + if err != nil { + return nil, err + } + paths, ok := serverMethodsAvailable[module] + if !ok { + continue + } + + // the module exists, does the path exist? + for _, pa := range paths { + if pa == path { + schemaMethodsAvailable = append(schemaMethodsAvailable, m) + break + } + } + } + schema.Methods = schemaMethodsAvailable + return +} diff --git a/rpc/service.go b/rpc/service.go index 26700fe7a8a0..a407dd568d88 100644 --- a/rpc/service.go +++ b/rpc/service.go @@ -22,7 +22,6 @@ import ( "fmt" "reflect" "runtime" - "strings" "sync" "unicode" "unicode/utf8" @@ -95,13 +94,13 @@ func (r *serviceRegistry) registerName(name string, rcvr interface{}) error { // callback returns the callback corresponding to the given RPC method name. func (r *serviceRegistry) callback(method string) *callback { - elem := strings.SplitN(method, serviceMethodSeparator, 2) - if len(elem) != 2 { + module, mthd, err := elementizeMethodName(method) + if err != nil { return nil } r.mu.Lock() defer r.mu.Unlock() - return r.services[elem[0]].callbacks[elem[1]] + return r.services[module].callbacks[mthd] } // subscription returns a subscription callback in the given service. From 9320e204891ca11ac5f072b323606933c08d026b Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Wed, 18 Sep 2019 20:38:17 +0200 Subject: [PATCH 04/29] update openrpc_schema --- internal/openrpc/openrpc_schema.go | 53 ++++++++++++++---------------- 1 file changed, 25 insertions(+), 28 deletions(-) diff --git a/internal/openrpc/openrpc_schema.go b/internal/openrpc/openrpc_schema.go index 414f830ef536..e480595016c5 100644 --- a/internal/openrpc/openrpc_schema.go +++ b/internal/openrpc/openrpc_schema.go @@ -35,8 +35,7 @@ const OpenRPCSchema = ` "methods": [ { "name": "web3_clientVersion", - "description": "Returns the version of the current client", - "summary": "current client version", + "summary": "Returns the version of the current client.", "params": [], "result": { "name": "clientVersion", @@ -49,15 +48,14 @@ const OpenRPCSchema = ` }, { "name": "web3_sha3", - "summary": "Hashes data", - "description": "Hashes data using the Keccak-256 algorithm", + "summary": "Hashes data using the Keccak-256 algorithm.", "params": [ { "name": "data", "description": "data to hash using the Keccak-256 algorithm", "summary": "data to hash", "schema": { - "title": "datahash", + "title": "data", "type": "string", "pattern": "^0x[a-fA-F\\d]+$" } @@ -88,7 +86,7 @@ const OpenRPCSchema = ` }, { "name": "net_listening", - "summary": "returns listening status", + "summary": "Returns listening status.", "description": "Determines if this client is listening for new network connections.", "params": [], "result": { @@ -113,8 +111,7 @@ const OpenRPCSchema = ` }, { "name": "net_peerCount", - "summary": "number of peers", - "description": "Returns the number of peers currently connected to this client.", + "summary": "Returns the number of peers currently connected to this client.", "params": [], "result": { "name": "quantity", @@ -128,16 +125,15 @@ const OpenRPCSchema = ` }, { "name": "net_version", - "summary": "chain ID associated with network", - "description": "Returns the chain ID associated with the current network.", + "summary": "Returns the network ID associated with the current network.", "params": [], "result": { - "name": "chainID", - "description": "chain ID associated with the current network", + "name": "networkID", + "description": "Network ID associated with the current network", "schema": { - "title": "chainID", + "title": "networkID", "type": "string", - "pattern": "^0x[a-fA-F\\d]+$" + "pattern": "^[\\d]+$" } } }, @@ -170,12 +166,12 @@ const OpenRPCSchema = ` }, { "name": "eth_chainId", - "summary": "Returns the currently configured chain id", + "summary": "Returns the currently configured chain id.", "description": "Returns the currently configured chain id, a value used in replay-protected transaction signing as introduced by [EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md).", "params": [], "result": { "name": "chainId", - "description": "hex format integer of the current chain id. Defaults are mainnet=8", + "description": "hex format integer of the current chain id. Defaults are ETC=61, ETH=1, Morden=62.", "schema": { "title": "chainId", "type": "string", @@ -197,7 +193,8 @@ const OpenRPCSchema = ` }, { "name": "eth_estimateGas", - "summary": "Generates and returns an estimate of how much gas is necessary to allow the transaction to complete. The transaction will not be added to the blockchain. Note that the estimate may be significantly more than the amount of gas actually used by the transaction, for a variety of reasons including EVM mechanics and node performance.", + "summary": "Generates and returns an estimate of how much gas is necessary to allow the transaction to complete.", + "description": "Generates and returns an estimate of how much gas is necessary to allow the transaction to complete. The transaction will not be added to the blockchain. Note that the estimate may be significantly more than the amount of gas actually used by the transaction, for a variety of reasons including EVM mechanics and node performance.", "params": [ { "$ref": "#/components/contentDescriptors/Transaction" @@ -213,7 +210,7 @@ const OpenRPCSchema = ` }, { "name": "eth_gasPrice", - "summary": "Returns the current price per gas in wei", + "summary": "Returns the current price per gas in wei.", "params": [], "result": { "$ref": "#/components/contentDescriptors/GasPrice" @@ -221,12 +218,12 @@ const OpenRPCSchema = ` }, { "name": "eth_getBalance", - "summary": "Returns balance of a given account or contract", + "summary": "Returns Ubiq balance of a given or account or contract in wei.", "params": [ { "name": "address", "required": true, - "description": "The address of the acccount or contract", + "description": "The address of the account or contract", "schema": { "$ref": "#/components/schemas/Address" } @@ -256,7 +253,7 @@ const OpenRPCSchema = ` }, { "name": "eth_getBlockByHash", - "summary": "Gets a block for a given hash", + "summary": "Gets a block for a given hash.", "params": [ { "name": "blockHash", @@ -292,7 +289,7 @@ const OpenRPCSchema = ` }, { "name": "eth_getBlockByNumber", - "summary": "Gets a block for a given number salad", + "summary": "Gets a block for a given number salad.", "params": [ { "$ref": "#/components/contentDescriptors/BlockNumber" @@ -807,7 +804,7 @@ const OpenRPCSchema = ` "title": "storageKeys", "description": "The storage keys of all the storage slots being requested", "items": { - "description": "A storage key is indexed from the solidity compiler by the order it is declaired. For mappings it uses the keccak of the mapping key with its position (and recursively for X-dimensional mappings)", + "description": "A storage key is indexed from the solidity compiler by the order it is declared. For mappings it uses the keccak of the mapping key with its position (and recursively for X-dimensional mappings)", "$ref": "#/components/schemas/Integer" } } @@ -834,7 +831,7 @@ const OpenRPCSchema = ` "$ref": "#/components/schemas/AccountProof" }, "balance": { - "description": "The balance of the account or contract of the request", + "description": "The Ubiq balance of the account or contract of the request", "$ref": "#/components/schemas/Integer" }, "codeHash": { @@ -941,7 +938,7 @@ const OpenRPCSchema = ` }, { "name": "eth_pendingTransactions", - "summary": "Returns the pending transactions list", + "summary": "Returns the pending transactions list.", "params": [], "result": { "name": "pendingTransactions", @@ -1000,7 +997,7 @@ const OpenRPCSchema = ` { "name": "id", "required": true, - "description": "String identifiying the client", + "description": "String identifying the client", "schema": { "$ref": "#/components/schemas/DataWord" } @@ -1139,7 +1136,7 @@ const OpenRPCSchema = ` "schemas": { "ProofNode": { "type": "string", - "description": "An indiviual node used to prove a path down a merkle-patricia-tree", + "description": "An individual node used to prove a path down a merkle-patricia-tree", "$ref": "#/components/schemas/Bytes" }, "AccountProof": { @@ -1579,7 +1576,7 @@ const OpenRPCSchema = ` "$ref": "#/components/schemas/IntOrPending" }, "value": { - "description": "Value of ubiq being transferred in Wei", + "description": "Value of Ubiq being transferred in Wei", "$ref": "#/components/schemas/Keccak" }, "v": { From 4753ebd341657abb82d38a9a0235708708e460bc Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Mon, 4 Nov 2019 12:40:37 +0100 Subject: [PATCH 05/29] openrpc: sync with updated spec --- internal/openrpc/openrpc_schema.go | 3220 ++++++++++++++-------------- 1 file changed, 1602 insertions(+), 1618 deletions(-) diff --git a/internal/openrpc/openrpc_schema.go b/internal/openrpc/openrpc_schema.go index e480595016c5..05d10c7679f0 100644 --- a/internal/openrpc/openrpc_schema.go +++ b/internal/openrpc/openrpc_schema.go @@ -22,1814 +22,1798 @@ package openrpc // methods. const OpenRPCSchema = ` { - "openrpc": "1.0.0", - "info": { - "version": "1.0.10", - "title": "Ubiq JSON-RPC", - "description": "This API lets you interact with an EVM-based client via JSON-RPC", - "license": { - "name": "Apache 2.0", - "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + "openrpc": "1.0.0", + "info": { + "version": "1.0.10", + "title": "Ubiq JSON-RPC", + "description": "This API lets you interact with an EVM-based client via JSON-RPC", + "license": { + "name": "Apache 2.0", + "url": "https://www.apache.org/licenses/LICENSE-2.0.html" + } + }, + "methods": [ + { + "name": "web3_clientVersion", + "summary": "Returns the version of the current client.", + "params": [], + "result": { + "name": "clientVersion", + "description": "client version", + "schema": { + "title": "clientVersion", + "type": "string" + } } }, - "methods": [ - { - "name": "web3_clientVersion", - "summary": "Returns the version of the current client.", - "params": [], - "result": { - "name": "clientVersion", - "description": "client version", + { + "name": "web3_sha3", + "summary": "Hashes data using the Keccak-256 algorithm.", + "params": [ + { + "name": "data", + "description": "data to hash using the Keccak-256 algorithm", + "summary": "data to hash", "schema": { - "title": "clientVersion", - "type": "string" + "title": "data", + "type": "string", + "pattern": "^0x[a-fA-F\\d]+$" } } + ], + "result": { + "name": "hashedData", + "description": "Keccak-256 hash of the given data", + "schema": { + "$ref": "#/components/schemas/Keccak" + } }, - { - "name": "web3_sha3", - "summary": "Hashes data using the Keccak-256 algorithm.", - "params": [ - { - "name": "data", - "description": "data to hash using the Keccak-256 algorithm", - "summary": "data to hash", - "schema": { - "title": "data", - "type": "string", - "pattern": "^0x[a-fA-F\\d]+$" + "examples": [ + { + "name": "sha3Example", + "params": [ + { + "name": "sha3ParamExample", + "value": "0x68656c6c6f20776f726c64" } + ], + "result": { + "name": "sha3ResultExample", + "value": "0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad" } - ], - "result": { - "name": "hashedData", - "description": "Keccak-256 hash of the given data", - "schema": { - "$ref": "#/components/schemas/Keccak" + } + ] + }, + { + "name": "net_listening", + "summary": "Returns listening status.", + "description": "Determines if this client is listening for new network connections.", + "params": [], + "result": { + "name": "netListeningResult", + "description": "` + "`" + `true` + "`" + ` if listening is active or ` + "`" + `false` + "`" + ` if listening is not active", + "schema": { + "title": "isNetListening", + "type": "boolean" + } + }, + "examples": [ + { + "name": "netListeningTrueExample", + "description": "example of true result for net_listening", + "params": [], + "result": { + "name": "netListeningExampleFalseResult", + "value": true } + } + ] + }, + { + "name": "net_peerCount", + "summary": "Returns the number of peers currently connected to this client.", + "params": [], + "result": { + "name": "quantity", + "schema": { + "$ref": "#/components/schemas/Integer" + } + } + }, + { + "name": "net_version", + "summary": "Returns the network ID associated with the current network.", + "params": [], + "result": { + "name": "networkID", + "description": "Network ID associated with the current network", + "schema": { + "title": "networkID", + "type": "string", + "pattern": "^[\\d]+$" + } + } + }, + { + "name": "eth_blockNumber", + "summary": "Returns the number of most recent block.", + "params": [], + "result": { + "name": "blockNumber", + "schema": { + "$ref": "#/components/schemas/BlockNumber" + } + } + }, + { + "name": "eth_call", + "summary": "Executes a new message call (locally) immediately without creating a transaction on the block chain.", + "params": [ + { + "$ref": "#/components/contentDescriptors/Transaction" }, - "examples": [ - { - "name": "sha3Example", - "params": [ - { - "name": "sha3ParamExample", - "value": "0x68656c6c6f20776f726c64" - } - ], - "result": { - "name": "sha3ResultExample", - "value": "0x47173285a8d7341e5e972fc677286384f802f8ef42a5ec5f03bbfa254cb01fad" - } - } - ] - }, - { - "name": "net_listening", - "summary": "Returns listening status.", - "description": "Determines if this client is listening for new network connections.", - "params": [], - "result": { - "name": "netListeningResult", - "description": "` + "`" + `true` + "`" + ` if listening is active or ` + "`" + `false` + "`" + ` if listening is not active", + { + "$ref": "#/components/contentDescriptors/BlockNumber" + } + ], + "result": { + "name": "returnValue", + "description": "The return value of the executed contract", + "schema": { + "$ref": "#/components/schemas/Bytes" + } + } + }, + { + "name": "eth_chainId", + "summary": "Returns the currently configured chain id.", + "description": "Returns the currently configured chain id, a value used in replay-protected transaction signing as introduced by [EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md).", + "params": [], + "result": { + "name": "chainId", + "description": "hex format integer of the current chain id. Defaults are UBQ=8, ETC=61, ETH=1", + "schema": { + "title": "chainId", + "type": "string", + "pattern": "^0x[a-fA-F\\d]+$" + } + } + }, + { + "name": "eth_coinbase", + "summary": "Returns the client coinbase address.", + "params": [], + "result": { + "name": "address", + "description": "The address owned by the client that is used as default for things like the mining reward", + "schema": { + "$ref": "#/components/schemas/Address" + } + } + }, + { + "name": "eth_estimateGas", + "summary": "Generates and returns an estimate of how much gas is necessary to allow the transaction to complete.", + "description": "Generates and returns an estimate of how much gas is necessary to allow the transaction to complete. The transaction will not be added to the blockchain. Note that the estimate may be significantly more than the amount of gas actually used by the transaction, for a variety of reasons including EVM mechanics and node performance.", + "params": [ + { + "$ref": "#/components/contentDescriptors/Transaction" + } + ], + "result": { + "name": "gasUsed", + "description": "The amount of gas used", + "schema": { + "$ref": "#/components/schemas/Integer" + } + } + }, + { + "name": "eth_gasPrice", + "summary": "Returns the current price per gas in wei.", + "params": [], + "result": { + "$ref": "#/components/contentDescriptors/GasPrice" + } + }, + { + "name": "eth_getBalance", + "summary": "Returns the UBQ balance of a given account or contract in wei.", + "params": [ + { + "name": "address", + "required": true, + "description": "The address of the account or contract", "schema": { - "title": "isNetListening", - "type": "boolean" + "$ref": "#/components/schemas/Address" } }, - "examples": [ - { - "name": "netListeningTrueExample", - "description": "example of true result for net_listening", - "params": [], - "result": { - "name": "netListeningExampleFalseResult", - "value": true + { + "$ref": "#/components/contentDescriptors/BlockNumber" + } + ], + "result": { + "name": "getBalanceResult", + "schema": { + "title": "getBalanceResult", + "oneOf": [ + { + "$ref": "#/components/schemas/Integer" + }, + { + "$ref": "#/components/schemas/Null" } + ] + } + } + }, + { + "name": "eth_getBlockByHash", + "summary": "Gets a block for a given hash.", + "params": [ + { + "name": "blockHash", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlockHash" } - ] - }, - { - "name": "net_peerCount", - "summary": "Returns the number of peers currently connected to this client.", - "params": [], - "result": { - "name": "quantity", - "description": "number of connected peers.", + }, + { + "name": "includeTransactions", + "description": "If ` + "`" + `true` + "`" + ` it returns the full transaction objects, if ` + "`" + `false` + "`" + ` only the hashes of the transactions.", + "required": true, "schema": { - "title": "numConnectedPeers", - "description": "Hex representation of number of connected peers", - "type": "string" + "title": "isTransactionsIncluded", + "type": "boolean" } } - }, - { - "name": "net_version", - "summary": "Returns the network ID associated with the current network.", - "params": [], - "result": { - "name": "networkID", - "description": "Network ID associated with the current network", + ], + "result": { + "name": "getBlockByHashResult", + "schema": { + "title": "getBlockByHashResult", + "oneOf": [ + { + "$ref": "#/components/schemas/Block" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + } + } + }, + { + "name": "eth_getBlockByNumber", + "summary": "Gets a block for a given number salad.", + "params": [ + { + "$ref": "#/components/contentDescriptors/BlockNumber" + }, + { + "name": "includeTransactions", + "description": "If ` + "`" + `true` + "`" + ` it returns the full transaction objects, if ` + "`" + `false` + "`" + ` only the hashes of the transactions.", + "required": true, "schema": { - "title": "networkID", - "type": "string", - "pattern": "^[\\d]+$" + "title": "isTransactionsIncluded", + "type": "boolean" } } - }, - { - "name": "eth_blockNumber", - "summary": "Returns the number of most recent block.", - "params": [], - "result": { + ], + "result": { + "name": "getBlockByNumberResult", + "schema": { + "title": "getBlockByNumberResult", + "oneOf": [ + { + "$ref": "#/components/schemas/Block" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + } + } + }, + { + "name": "eth_getBlockTransactionCountByHash", + "summary": "Returns the number of transactions in a block from a block matching the given block hash.", + "params": [ + { + "$ref": "#/components/contentDescriptors/BlockHash" + } + ], + "result": { + "name": "blockTransactionCountByHash", + "description": "The Number of total transactions in the given block", + "schema": { + "title": "blockTransactionCountByHash", + "oneOf": [ + { + "$ref": "#/components/schemas/Integer" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + } + } + }, + { + "name": "eth_getBlockTransactionCountByNumber", + "summary": "Returns the number of transactions in a block from a block matching the given block number.", + "params": [ + { "$ref": "#/components/contentDescriptors/BlockNumber" } - }, - { - "name": "eth_call", - "summary": "Executes a new message call (locally) immediately without creating a transaction on the block chain.", - "params": [ - { - "$ref": "#/components/contentDescriptors/Transaction" - }, - { - "$ref": "#/components/contentDescriptors/BlockNumber" - } - ], - "result": { - "name": "returnValue", - "description": "The return value of the executed contract", + ], + "result": { + "name": "blockTransactionCountByHash", + "description": "The Number of total transactions in the given block", + "schema": { + "title": "blockTransactionCountByHash", + "oneOf": [ + { + "$ref": "#/components/schemas/Integer" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + } + } + }, + { + "name": "eth_getCode", + "summary": "Returns code at a given contract address", + "params": [ + { + "name": "address", + "required": true, + "description": "The address of the contract", "schema": { - "$ref": "#/components/schemas/Bytes" + "$ref": "#/components/schemas/Address" } + }, + { + "$ref": "#/components/contentDescriptors/BlockNumber" } - }, - { - "name": "eth_chainId", - "summary": "Returns the currently configured chain id.", - "description": "Returns the currently configured chain id, a value used in replay-protected transaction signing as introduced by [EIP-155](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md).", - "params": [], - "result": { - "name": "chainId", - "description": "hex format integer of the current chain id. Defaults are ETC=61, ETH=1, Morden=62.", + ], + "result": { + "name": "bytes", + "schema": { + "$ref": "#/components/schemas/Bytes" + } + } + }, + { + "name": "eth_getFilterChanges", + "summary": "Polling method for a filter, which returns an array of logs which occurred since last poll.", + "params": [ + { + "name": "filterId", + "required": true, "schema": { - "title": "chainId", - "type": "string", - "pattern": "^0x[a-fA-F\\d]+$" + "$ref": "#/components/schemas/FilterId" } } - }, - { - "name": "eth_coinbase", - "summary": "Returns the client coinbase address.", - "params": [], - "result": { - "name": "address", - "description": "The address owned by the client that is used as default for things like the mining reward", - "schema": { - "$ref": "#/components/schemas/Address" + ], + "result": { + "name": "logResult", + "schema": { + "title": "logResult", + "type": "array", + "items": { + "$ref": "#/components/schemas/Log" } } - }, - { - "name": "eth_estimateGas", - "summary": "Generates and returns an estimate of how much gas is necessary to allow the transaction to complete.", - "description": "Generates and returns an estimate of how much gas is necessary to allow the transaction to complete. The transaction will not be added to the blockchain. Note that the estimate may be significantly more than the amount of gas actually used by the transaction, for a variety of reasons including EVM mechanics and node performance.", - "params": [ - { - "$ref": "#/components/contentDescriptors/Transaction" + } + }, + { + "name": "eth_getFilterLogs", + "summary": "Returns an array of all logs matching filter with given id.", + "params": [ + { + "name": "filterId", + "required": true, + "schema": { + "$ref": "#/components/schemas/FilterId" } - ], - "result": { - "name": "gasUsed", - "description": "The amount of gas used", + } + ], + "result": { + "$ref": "#/components/contentDescriptors/Logs" + } + }, + { + "name": "eth_getRawTransactionByHash", + "summary": "Returns raw transaction data of a transaction with the given hash.", + "params": [ + { + "$ref": "#/components/contentDescriptors/TransactionHash" + } + ], + "result": { + "name": "rawTransactionByHash", + "description": "The raw transaction data", + "schema": { + "$ref": "#/components/schemas/Bytes" + } + } + }, + { + "name": "eth_getRawTransactionByBlockHashAndIndex", + "summary": "Returns raw transaction data of a transaction with the given hash.", + "params": [ + { + "$ref": "#/components/contentDescriptors/BlockHash" + }, + { + "name": "index", + "description": "The ordering in which a transaction is mined within its block.", + "required": true, "schema": { "$ref": "#/components/schemas/Integer" } } - }, - { - "name": "eth_gasPrice", - "summary": "Returns the current price per gas in wei.", - "params": [], - "result": { - "$ref": "#/components/contentDescriptors/GasPrice" + ], + "result": { + "name": "rawTransaction", + "description": "The raw transaction data", + "schema": { + "$ref": "#/components/schemas/Bytes" } - }, - { - "name": "eth_getBalance", - "summary": "Returns Ubiq balance of a given or account or contract in wei.", - "params": [ - { - "name": "address", - "required": true, - "description": "The address of the account or contract", - "schema": { - "$ref": "#/components/schemas/Address" - } - }, - { - "name": "blockNumber", - "description": "A BlockNumber at which to request the balance", - "schema": { - "$ref": "#/components/schemas/BlockNumber" - } - } - ], - "result": { - "name": "getBalanceResult", + } + }, + { + "name": "eth_getRawTransactionByBlockNumberAndIndex", + "summary": "Returns raw transaction data of a transaction with the given hash.", + "params": [ + { + "$ref": "#/components/contentDescriptors/BlockNumber" + }, + { + "name": "index", + "description": "The ordering in which a transaction is mined within its block.", + "required": true, "schema": { - "title": "getBalanceResult", - "oneOf": [ - { - "$ref": "#/components/schemas/Integer" - }, - { - "$ref": "#/components/schemas/Null" - } - ] + "$ref": "#/components/schemas/Integer" } } - }, - { - "name": "eth_getBlockByHash", - "summary": "Gets a block for a given hash.", - "params": [ - { - "name": "blockHash", - "required": true, - "schema": { - "$ref": "#/components/schemas/BlockHash" - } - }, - { - "name": "includeTransactions", - "description": "If ` + "`" + `true` + "`" + ` it returns the full transaction objects, if ` + "`" + `false` + "`" + ` only the hashes of the transactions.", - "required": true, - "schema": { - "title": "isTransactionsIncluded", - "type": "boolean" - } - } - ], - "result": { - "name": "getBlockByHashResult", + ], + "result": { + "name": "rawTransaction", + "description": "The raw transaction data", + "schema": { + "$ref": "#/components/schemas/Bytes" + } + } + }, + { + "name": "eth_getLogs", + "summary": "Returns an array of all logs matching a given filter object.", + "params": [ + { + "$ref": "#/components/contentDescriptors/Filter" + } + ], + "result": { + "$ref": "#/components/contentDescriptors/Logs" + } + }, + { + "name": "eth_getStorageAt", + "summary": "Gets a storage value from a contract address, a position, and an optional blockNumber", + "params": [ + { + "$ref": "#/components/contentDescriptors/Address" + }, + { + "$ref": "#/components/contentDescriptors/Position" + }, + { + "$ref": "#/components/contentDescriptors/BlockNumber" + } + ], + "result": { + "name": "dataWord", + "schema": { + "$ref": "#/components/schemas/DataWord" + } + } + }, + { + "name": "eth_getTransactionByBlockHashAndIndex", + "summary": "Returns the information about a transaction requested by the block hash and index of which it was mined.", + "params": [ + { + "$ref": "#/components/contentDescriptors/BlockHash" + }, + { + "name": "index", + "description": "The ordering in which a transaction is mined within its block.", + "required": true, "schema": { - "title": "getBlockByHashResult", - "oneOf": [ - { - "$ref": "#/components/schemas/Block" - }, - { - "$ref": "#/components/schemas/Null" - } - ] + "$ref": "#/components/schemas/Integer" } } + ], + "result": { + "$ref": "#/components/contentDescriptors/TransactionResult" }, - { - "name": "eth_getBlockByNumber", - "summary": "Gets a block for a given number salad.", - "params": [ - { - "$ref": "#/components/contentDescriptors/BlockNumber" - }, - { - "name": "includeTransactions", - "description": "If ` + "`" + `true` + "`" + ` it returns the full transaction objects, if ` + "`" + `false` + "`" + ` only the hashes of the transactions.", - "required": true, - "schema": { - "title": "isTransactionsIncluded", - "type": "boolean" + "examples": [ + { + "name": "nullExample", + "params": [ + { + "name": "blockHashExample", + "value": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" + }, + { + "name": "indexExample", + "value": "0x0" } + ], + "result": { + "name": "nullResultExample", + "value": null } - ], - "result": { - "name": "getBlockByNumberResult", + } + ] + }, + { + "name": "eth_getTransactionByBlockNumberAndIndex", + "summary": "Returns the information about a transaction requested by the block hash and index of which it was mined.", + "params": [ + { + "$ref": "#/components/contentDescriptors/BlockNumber" + }, + { + "name": "index", + "description": "The ordering in which a transaction is mined within its block.", + "required": true, "schema": { - "title": "getBlockByNumberResult", - "oneOf": [ - { - "$ref": "#/components/schemas/Block" - }, - { - "$ref": "#/components/schemas/Null" - } - ] + "$ref": "#/components/schemas/Integer" } } - }, - { - "name": "eth_getBlockTransactionCountByHash", - "summary": "Returns the number of transactions in a block from a block matching the given block hash.", - "params": [ - { - "$ref": "#/components/contentDescriptors/BlockHash" - } - ], - "result": { - "name": "blockTransactionCountByHash", - "description": "The Number of total transactions in the given block", - "schema": { - "title": "blockTransactionCountByHash", - "oneOf": [ - { - "$ref": "#/components/schemas/Integer" - }, - { - "$ref": "#/components/schemas/Null" - } - ] - } + ], + "result": { + "$ref": "#/components/contentDescriptors/TransactionResult" + } + }, + { + "name": "eth_getTransactionByHash", + "summary": "Returns the information about a transaction requested by transaction hash.", + "params": [ + { + "$ref": "#/components/contentDescriptors/TransactionHash" } - }, - { - "name": "eth_getBlockTransactionCountByNumber", - "summary": "Returns the number of transactions in a block from a block matching the given block number.", - "params": [ - { - "$ref": "#/components/contentDescriptors/BlockNumber" - } - ], - "result": { - "name": "blockTransactionCountByHash", - "description": "The Number of total transactions in the given block", + ], + "result": { + "$ref": "#/components/contentDescriptors/TransactionResult" + } + }, + { + "name": "eth_getTransactionCount", + "summary": "Returns the number of transactions sent from an address", + "params": [ + { + "$ref": "#/components/contentDescriptors/Address" + }, + { + "$ref": "#/components/contentDescriptors/BlockNumber" + } + ], + "result": { + "name": "transactionCount", + "schema": { + "$ref": "#/components/schemas/Integer" + } + } + }, + { + "name": "eth_getTransactionReceipt", + "summary": "Returns the receipt information of a transaction by its hash.", + "params": [ + { + "$ref": "#/components/contentDescriptors/TransactionHash" + } + ], + "result": { + "name": "transactionReceiptResult", + "description": "returns either a receipt or null", + "schema": { + "title": "transactionReceiptOrNull", + "oneOf": [ + { + "$ref": "#/components/schemas/Receipt" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + } + } + }, + { + "name": "eth_getUncleByBlockHashAndIndex", + "summary": "Returns information about a uncle of a block by hash and uncle index position.", + "params": [ + { + "$ref": "#/components/contentDescriptors/BlockHash" + }, + { + "name": "index", + "description": "The ordering in which a uncle is included within its block.", + "required": true, "schema": { - "title": "blockTransactionCountByHash", - "oneOf": [ - { - "$ref": "#/components/schemas/Integer" - }, - { - "$ref": "#/components/schemas/Null" - } - ] + "$ref": "#/components/schemas/Integer" } } - }, - { - "name": "eth_getCode", - "summary": "Returns code at a given contract address", - "params": [ - { - "name": "address", - "required": true, - "description": "The address of the contract", - "schema": { - "$ref": "#/components/schemas/Address" - } - }, - { - "name": "blockNumber", - "description": "A BlockNumber of which the code existed", - "schema": { - "$ref": "#/components/schemas/BlockNumber" + ], + "result": { + "name": "uncle", + "schema": { + "title": "uncleOrNull", + "oneOf": [ + { + "$ref": "#/components/schemas/Uncle" + }, + { + "$ref": "#/components/schemas/Null" } + ] + } + } + }, + { + "name": "eth_getUncleByBlockNumberAndIndex", + "summary": "Returns information about a uncle of a block by hash and uncle index position.", + "params": [ + { + "name": "uncleBlockNumber", + "description": "The block in which the uncle was included", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlockNumber" } - ], - "result": { - "name": "bytes", + }, + { + "name": "index", + "description": "The ordering in which a uncle is included within its block.", + "required": true, "schema": { - "$ref": "#/components/schemas/Bytes" + "$ref": "#/components/schemas/Integer" } } + ], + "result": { + "name": "uncleResult", + "description": "returns an uncle or null", + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/Uncle" + }, + { + "$ref": "#/components/schemas/Null" + } + ] + } }, - { - "name": "eth_getFilterChanges", - "summary": "Polling method for a filter, which returns an array of logs which occurred since last poll.", - "params": [ - { - "name": "filterId", - "required": true, - "schema": { - "$ref": "#/components/schemas/FilterId" + "examples": [ + { + "name": "nullResultExample", + "params": [ + { + "name": "uncleBlockNumberExample", + "value": "0x0" + }, + { + "name": "uncleBlockNumberIndexExample", + "value": "0x0" } + ], + "result": { + "name": "nullResultExample", + "value": null } - ], - "result": { - "name": "logResult", - "schema": { - "title": "logResult", - "type": "array", - "items": { - "$ref": "#/components/schemas/Log" + } + ] + }, + { + "name": "eth_getUncleCountByBlockHash", + "summary": "Returns the number of uncles in a block from a block matching the given block hash.", + "params": [ + { + "$ref": "#/components/contentDescriptors/BlockHash" + } + ], + "result": { + "name": "uncleCountResult", + "schema": { + "title": "uncleCountOrNull", + "oneOf": [ + { + "description": "The Number of total uncles in the given block", + "$ref": "#/components/schemas/Integer" + }, + { + "$ref": "#/components/schemas/Null" } - } + ] } - }, - { - "name": "eth_getFilterLogs", - "summary": "Returns an array of all logs matching filter with given id.", - "params": [ - { - "name": "filterId", - "required": true, - "schema": { - "$ref": "#/components/schemas/FilterId" + } + }, + { + "name": "eth_getUncleCountByBlockNumber", + "summary": "Returns the number of uncles in a block from a block matching the given block number.", + "params": [ + { + "$ref": "#/components/contentDescriptors/BlockNumber" + } + ], + "result": { + "name": "uncleCountResult", + "schema": { + "title": "uncleCountOrNull", + "oneOf": [ + { + "description": "The Number of total uncles in the given block", + "$ref": "#/components/schemas/Integer" + }, + { + "$ref": "#/components/schemas/Null" } - } - ], - "result": { - "$ref": "#/components/contentDescriptors/Logs" + ] } - }, - { - "name": "eth_getRawTransactionByHash", - "summary": "Returns raw transaction data of a transaction with the given hash.", - "params": [ - { - "$ref": "#/components/contentDescriptors/TransactionHash" + } + }, + { + "name": "eth_getProof", + "summary": "Returns the account- and storage-values of the specified account including the Merkle-proof.", + "params": [ + { + "name": "address", + "description": "The address of the account or contract", + "required": true, + "schema": { + "$ref": "#/components/schemas/Address" } - ], - "result": { - "name": "rawTransactionByHash", - "description": "The raw transaction data", + }, + { + "name": "storageKeys", + "required": true, "schema": { - "$ref": "#/components/schemas/Bytes" + "title": "storageKeys", + "description": "The storage keys of all the storage slots being requested", + "items": { + "description": "A storage key is indexed from the solidity compiler by the order it is declared. For mappings it uses the keccak of the mapping key with its position (and recursively for X-dimensional mappings)", + "$ref": "#/components/schemas/Integer" + } } + }, + { + "$ref": "#/components/contentDescriptors/BlockNumber" } - }, - { - "name": "eth_getRawTransactionByBlockHashAndIndex", - "summary": "Returns raw transaction data of a transaction with the given hash.", - "params": [ - { - "$ref": "#/components/contentDescriptors/BlockHash" - }, - { - "name": "index", - "description": "The ordering in which a transaction is mined within its block.", - "required": true, - "schema": { - "$ref": "#/components/schemas/Integer" + ], + "result": { + "name": "account", + "schema": { + "title": "proofAccountOrNull", + "oneOf": [ + { + "title": "proofAccount", + "type": "object", + "description": "The merkle proofs of the specified account connecting them to the blockhash of the block specified", + "properties": { + "address": { + "description": "The address of the account or contract of the request", + "$ref": "#/components/schemas/Address" + }, + "accountProof": { + "$ref": "#/components/schemas/AccountProof" + }, + "balance": { + "description": "The Ubiq balance of the account or contract of the request", + "$ref": "#/components/schemas/Integer" + }, + "codeHash": { + "description": "The code hash of the contract of the request (keccak(NULL) if external account)", + "$ref": "#/components/schemas/Keccak" + }, + "nonce": { + "description": "The transaction count of the account or contract of the request", + "$ref": "#/components/schemas/Nonce" + }, + "storageHash": { + "description": "The storage hash of the contract of the request (keccak(rlp(NULL)) if external account)", + "$ref": "#/components/schemas/Keccak" + }, + "storageProof": { + "$ref": "#/components/schemas/StorageProof" + } + } + }, + { + "$ref": "#/components/schemas/Null" + } + ] + } + } + }, + { + "name": "eth_getWork", + "summary": "Returns the hash of the current block, the seedHash, and the boundary condition to be met ('target').", + "params": [], + "result": { + "name": "work", + "schema": { + "type": "array", + "items": [ + { + "$ref": "#/components/schemas/PowHash" + }, + { + "$ref": "#/components/schemas/SeedHash" + }, + { + "$ref": "#/components/schemas/Difficulty" } + ] + } + } + }, + { + "name": "eth_hashrate", + "summary": "Returns the number of hashes per second that the node is mining with.", + "params": [], + "result": { + "name": "hashesPerSecond", + "schema": { + "description": "Integer of the number of hashes per second", + "$ref": "#/components/schemas/Integer" + } + } + }, + { + "name": "eth_mining", + "summary": "Returns true if client is actively mining new blocks.", + "params": [], + "result": { + "name": "mining", + "schema": { + "description": "Whether of not the client is mining", + "type": "boolean" + } + } + }, + { + "name": "eth_newBlockFilter", + "summary": "Creates a filter in the node, to notify when a new block arrives. To check if the state has changed, call eth_getFilterChanges.", + "params": [], + "result": { + "$ref": "#/components/contentDescriptors/FilterId" + } + }, + { + "name": "eth_newFilter", + "summary": "Creates a filter object, based on filter options, to notify when the state changes (logs). To check if the state has changed, call eth_getFilterChanges.", + "params": [ + { + "$ref": "#/components/contentDescriptors/Filter" + } + ], + "result": { + "name": "filterId", + "schema": { + "description": "The filter ID for use in ` + "`" + `eth_getFilterChanges` + "`" + `", + "$ref": "#/components/schemas/Integer" + } + } + }, + { + "name": "eth_newPendingTransactionFilter", + "summary": "Creates a filter in the node, to notify when new pending transactions arrive. To check if the state has changed, call eth_getFilterChanges.", + "params": [], + "result": { + "$ref": "#/components/contentDescriptors/FilterId" + } + }, + { + "name": "eth_pendingTransactions", + "summary": "Returns the pending transactions list.", + "params": [], + "result": { + "name": "pendingTransactions", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Transaction" } - ], - "result": { - "name": "rawTransaction", - "description": "The raw transaction data", + } + } + }, + { + "name": "eth_protocolVersion", + "summary": "Returns the current ubiq protocol version.", + "params": [], + "result": { + "name": "protocolVersion", + "schema": { + "description": "The current ubiq protocol version", + "$ref": "#/components/schemas/Integer" + } + } + }, + { + "name": "eth_sendRawTransaction", + "summary": "Creates new message call transaction or a contract creation for signed transactions.", + "params": [ + { + "name": "signedTransactionData", + "required": true, + "description": "The signed transaction data", "schema": { "$ref": "#/components/schemas/Bytes" } } - }, - { - "name": "eth_getRawTransactionByBlockNumberAndIndex", - "summary": "Returns raw transaction data of a transaction with the given hash.", - "params": [ - { - "$ref": "#/components/contentDescriptors/BlockNumber" - }, - { - "name": "index", - "description": "The ordering in which a transaction is mined within its block.", - "required": true, - "schema": { - "$ref": "#/components/schemas/Integer" - } + ], + "result": { + "name": "transactionHash", + "schema": { + "description": "The transaction hash, or the zero hash if the transaction is not yet available.", + "$ref": "#/components/schemas/Keccak" + } + } + }, + { + "name": "eth_submitHashrate", + "summary": "Returns an array of all logs matching a given filter object.", + "params": [ + { + "name": "hashRate", + "required": true, + "schema": { + "$ref": "#/components/schemas/DataWord" } - ], - "result": { - "name": "rawTransaction", - "description": "The raw transaction data", + }, + { + "name": "id", + "required": true, + "description": "String identifying the client", "schema": { - "$ref": "#/components/schemas/Bytes" + "$ref": "#/components/schemas/DataWord" } } - }, - { - "name": "eth_getLogs", - "summary": "Returns an array of all logs matching a given filter object.", - "params": [ - { - "$ref": "#/components/contentDescriptors/Filter" - } - ], - "result": { - "$ref": "#/components/contentDescriptors/Logs" + ], + "result": { + "name": "submitHashRateSuccess", + "schema": { + "type": "boolean", + "description": "whether of not submitting went through successfully" } - }, - { - "name": "eth_getStorageAt", - "summary": "Gets a storage value from a contract address, a position, and an optional blockNumber", - "params": [ - { - "$ref": "#/components/contentDescriptors/Address" - }, - { - "$ref": "#/components/contentDescriptors/Position" - }, - { - "$ref": "#/components/contentDescriptors/BlockNumber" + } + }, + { + "name": "eth_submitWork", + "summary": "Used for submitting a proof-of-work solution.", + "params": [ + { + "$ref": "#/components/contentDescriptors/Nonce" + }, + { + "name": "powHash", + "required": true, + "schema": { + "$ref": "#/components/schemas/PowHash" } - ], - "result": { - "name": "dataWord", + }, + { + "name": "mixHash", + "required": true, "schema": { - "$ref": "#/components/schemas/DataWord" + "$ref": "#/components/schemas/MixHash" } } + ], + "result": { + "name": "solutionValid", + "description": "returns true if the provided solution is valid, otherwise false.", + "schema": { + "type": "boolean", + "description": "Whether or not the provided solution is valid" + } }, - { - "name": "eth_getTransactionByBlockHashAndIndex", - "summary": "Returns the information about a transaction requested by the block hash and index of which it was mined.", - "params": [ - { - "$ref": "#/components/contentDescriptors/BlockHash" - }, - { - "name": "index", - "description": "The ordering in which a transaction is mined within its block.", - "required": true, - "schema": { - "$ref": "#/components/schemas/Integer" + "examples": [ + { + "name": "submitWorkExample", + "params": [ + { + "name": "nonceExample", + "description": "example of a number only used once", + "value": "0x0000000000000001" + }, + { + "name": "powHashExample", + "description": "proof of work to submit", + "value": "0x6bf2cAE0dE3ec3ecA5E194a6C6e02cf42aADfe1C2c4Fff12E5D36C3Cf7297F22" + }, + { + "name": "mixHashExample", + "description": "the mix digest example", + "value": "0xD1FE5700000000000000000000000000D1FE5700000000000000000000000000" } + ], + "result": { + "name": "solutionInvalidExample", + "description": "this example should return ` + "`" + `false` + "`" + ` as it is not a valid pow to submit", + "value": false } - ], - "result": { - "$ref": "#/components/contentDescriptors/TransactionResult" - }, - "examples": [ - { - "name": "nullExample", - "params": [ - { - "name": "blockHashExample", - "value": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef" - }, - { - "name": "indexExample", - "value": "0x0" + } + ] + }, + { + "name": "eth_syncing", + "summary": "Returns an object with data about the sync status or false.", + "params": [], + "result": { + "name": "syncing", + "schema": { + "oneOf": [ + { + "description": "An object with sync status data", + "type": "object", + "properties": { + "startingBlock": { + "description": "Block at which the import started (will only be reset, after the sync reached his head)", + "$ref": "#/components/schemas/Integer" + }, + "currentBlock": { + "description": "The current block, same as eth_blockNumber", + "$ref": "#/components/schemas/Integer" + }, + "highestBlock": { + "description": "The estimated highest block", + "$ref": "#/components/schemas/Integer" + }, + "knownStates": { + "description": "The known states", + "$ref": "#/components/schemas/Integer" + }, + "pulledStates": { + "description": "The pulled states", + "$ref": "#/components/schemas/Integer" + } } - ], - "result": { - "name": "nullResultExample", - "value": null + }, + { + "type": "boolean", + "description": "The value ` + "`" + `false` + "`" + ` indicating that syncing is complete" } + ] + } + } + }, + { + "name": "eth_uninstallFilter", + "summary": "Uninstalls a filter with given id. Should always be called when watch is no longer needed. Additionally Filters timeout when they aren't requested with eth_getFilterChanges for a period of time.", + "params": [ + { + "name": "filterId", + "required": true, + "schema": { + "$ref": "#/components/schemas/FilterId" } - ] + } + ], + "result": { + "name": "filterUninstalledSuccess", + "schema": { + "type": "boolean", + "description": "Whether of not the filter was successfully uninstalled" + } + } + } + ], + "components": { + "schemas": { + "ProofNode": { + "type": "string", + "description": "An individual node used to prove a path down a merkle-patricia-tree", + "$ref": "#/components/schemas/Bytes" }, - { - "name": "eth_getTransactionByBlockNumberAndIndex", - "summary": "Returns the information about a transaction requested by the block hash and index of which it was mined.", - "params": [ - { - "$ref": "#/components/contentDescriptors/BlockNumber" - }, - { - "name": "index", - "description": "The ordering in which a transaction is mined within its block.", - "required": true, - "schema": { + "AccountProof": { + "$ref": "#/components/schemas/ProofNodes" + }, + "StorageProof": { + "type": "array", + "description": "Current block header PoW hash.", + "items": { + "type": "object", + "description": "Object proving a relationship of a storage value to an account's storageHash.", + "properties": { + "key": { + "description": "The key used to get the storage slot in its account tree", + "$ref": "#/components/schemas/Integer" + }, + "value": { + "description": "The value of the storage slot in its account tree", "$ref": "#/components/schemas/Integer" + }, + "proof": { + "$ref": "#/components/schemas/ProofNodes" } } - ], - "result": { - "$ref": "#/components/contentDescriptors/TransactionResult" } }, - { - "name": "eth_getTransactionByHash", - "summary": "Returns the information about a transaction requested by transaction hash.", - "params": [ - { - "$ref": "#/components/contentDescriptors/TransactionHash" - } - ], - "result": { - "$ref": "#/components/contentDescriptors/TransactionResult" + "ProofNodes": { + "type": "array", + "description": "The set of node values needed to traverse a patricia merkle tree (from root to leaf) to retrieve a value", + "items": { + "$ref": "#/components/schemas/ProofNode" } }, - { - "name": "eth_getTransactionCount", - "summary": "Returns the number of transactions sent from an address", - "params": [ - { - "$ref": "#/components/contentDescriptors/Address" - }, - { - "$ref": "#/components/contentDescriptors/BlockNumber" - } - ], - "result": { - "name": "transactionCount", - "schema": { - "title": "nonceOrNull", - "oneOf": [ - { - "$ref": "#/components/schemas/Nonce" - }, - { - "$ref": "#/components/schemas/Null" - } - ] - } - } + "PowHash": { + "description": "Current block header PoW hash.", + "$ref": "#/components/schemas/DataWord" }, - { - "name": "eth_getTransactionReceipt", - "summary": "Returns the receipt information of a transaction by its hash.", - "params": [ - { - "$ref": "#/components/contentDescriptors/TransactionHash" - } - ], - "result": { - "name": "transactionReceiptResult", - "description": "returns either a receipt or null", - "schema": { - "title": "transactionReceiptOrNull", - "oneOf": [ - { - "$ref": "#/components/schemas/Receipt" - }, - { - "$ref": "#/components/schemas/Null" - } - ] - } - } + "SeedHash": { + "description": "The seed hash used for the DAG.", + "$ref": "#/components/schemas/DataWord" }, - { - "name": "eth_getUncleByBlockHashAndIndex", - "summary": "Returns information about a uncle of a block by hash and uncle index position.", - "params": [ - { - "$ref": "#/components/contentDescriptors/BlockHash" + "MixHash": { + "description": "The mix digest.", + "$ref": "#/components/schemas/DataWord" + }, + "Difficulty": { + "description": "The boundary condition ('target'), 2^256 / difficulty.", + "$ref": "#/components/schemas/DataWord" + }, + "FilterId": { + "type": "string", + "description": "An identifier used to reference the filter." + }, + "BlockHash": { + "type": "string", + "pattern": "^0x[a-fA-F\\d]{64}$", + "description": "The hex representation of the Keccak 256 of the RLP encoded block" + }, + "BlockNumber": { + "type": "string", + "pattern": "^0x[a-fA-F\\d]+$", + "description": "The hex representation of the block's height" + }, + "BlockNumberTag": { + "type": "string", + "description": "The block's height description", + "enum": [ + "earliest", + "latest", + "pending" + ] + }, + "Receipt": { + "type": "object", + "description": "The receipt of a transaction", + "required": [ + "blockHash", + "blockNumber", + "contractAddress", + "cumulativeGasUsed", + "from", + "gasUsed", + "logs", + "logsBloom", + "to", + "transactionHash", + "transactionIndex" + ], + "properties": { + "blockHash": { + "description": "BlockHash of the block in which the transaction was mined", + "$ref": "#/components/schemas/BlockHash" }, - { - "name": "index", - "description": "The ordering in which a uncle is included within its block.", - "required": true, - "schema": { - "$ref": "#/components/schemas/Integer" + "blockNumber": { + "description": "BlockNumber of the block in which the transaction was mined", + "$ref": "#/components/schemas/BlockNumber" + }, + "contractAddress": { + "description": "The contract address created, if the transaction was a contract creation, otherwise null", + "$ref": "#/components/schemas/Address" + }, + "cumulativeGasUsed": { + "description": "The gas units used by the transaction", + "$ref": "#/components/schemas/Integer" + }, + "from": { + "description": "The sender of the transaction", + "$ref": "#/components/schemas/Address" + }, + "gasUsed": { + "description": "The total gas used by the transaction", + "$ref": "#/components/schemas/Integer" + }, + "logs": { + "type": "array", + "description": "An array of all the logs triggered during the transaction", + "items": { + "$ref": "#/components/schemas/Log" } - } - ], - "result": { - "name": "uncle", - "schema": { - "title": "uncleOrNull", - "oneOf": [ - { - "$ref": "#/components/schemas/Uncle" - }, - { - "$ref": "#/components/schemas/Null" - } - ] + }, + "logsBloom": { + "$ref": "#/components/schemas/BloomFilter" + }, + "to": { + "description": "Destination address of the transaction", + "$ref": "#/components/schemas/Address" + }, + "transactionHash": { + "description": "Keccak 256 of the transaction", + "$ref": "#/components/schemas/Keccak" + }, + "transactionIndex": { + "description": "An array of all the logs triggered during the transaction", + "$ref": "#/components/schemas/BloomFilter" + }, + "postTransactionState": { + "description": "The intermediate stateRoot directly after transaction execution.", + "$ref": "#/components/schemas/Keccak" + }, + "status": { + "description": "Whether or not the transaction threw an error.", + "type": "boolean" } } }, - { - "name": "eth_getUncleByBlockNumberAndIndex", - "summary": "Returns information about a uncle of a block by hash and uncle index position.", - "params": [ - { - "name": "uncleBlockNumber", - "description": "The block in which the uncle was included", - "required": true, - "schema": { - "$ref": "#/components/schemas/BlockNumber" - } + "BloomFilter": { + "type": "string", + "description": "A 2048 bit bloom filter from the logs of the transaction. Each log sets 3 bits though taking the low-order 11 bits of each of the first three pairs of bytes in a Keccak 256 hash of the log's byte series" + }, + "Log": { + "type": "object", + "description": "An indexed event generated during a transaction", + "properties": { + "address": { + "description": "Sender of the transaction", + "$ref": "#/components/schemas/Address" }, - { - "name": "index", - "description": "The ordering in which a uncle is included within its block.", - "required": true, + "blockHash": { + "description": "BlockHash of the block in which the transaction was mined", + "$ref": "#/components/schemas/BlockHash" + }, + "blockNumber": { + "description": "BlockNumber of the block in which the transaction was mined", + "$ref": "#/components/schemas/BlockNumber" + }, + "data": { + "description": "The data/input string sent along with the transaction", + "$ref": "#/components/schemas/Bytes" + }, + "logIndex": { + "description": "The index of the event within its transaction, null when its pending", + "$ref": "#/components/schemas/Integer" + }, + "removed": { "schema": { - "$ref": "#/components/schemas/Integer" + "description": "Whether or not the log was orphaned off the main chain", + "type": "boolean" } - } - ], - "result": { - "name": "uncleResult", - "description": "returns an uncle or null", - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/Uncle" - }, - { - "$ref": "#/components/schemas/Null" - } - ] - } - }, - "examples": [ - { - "name": "nullResultExample", - "params": [ - { - "name": "uncleBlockNumberExample", - "value": "0x0" - }, - { - "name": "uncleBlockNumberIndexExample", - "value": "0x0" + }, + "topics": { + "type": "array", + "items": { + "topic": { + "description": "32 Bytes DATA of indexed log arguments. (In solidity: The first topic is the hash of the signature of the event (e.g. Deposit(address,bytes32,uint256))", + "$ref": "#/components/schemas/DataWord" } - ], - "result": { - "name": "nullResultExample", - "value": null } + }, + "transactionHash": { + "description": "The hash of the transaction in which the log occurred", + "$ref": "#/components/schemas/Keccak" + }, + "transactionIndex": { + "description": "The index of the transaction in which the log occurred", + "$ref": "#/components/schemas/Integer" } - ] + } }, - { - "name": "eth_getUncleCountByBlockHash", - "summary": "Returns the number of uncles in a block from a block matching the given block hash.", - "params": [ - { - "$ref": "#/components/contentDescriptors/BlockHash" - } - ], - "result": { - "name": "uncleCountResult", - "schema": { - "title": "uncleCountOrNull", + "Uncle": { + "type": "object", + "description": "Orphaned blocks that can be included in the chain but at a lower block reward. NOTE: An uncle doesn’t contain individual transactions.", + "properties": { + "number": { + "description": "The block number or null when its the pending block", + "$ref": "#/components/schemas/IntOrPending" + }, + "hash": { + "description": "The block hash or null when its the pending block", + "$ref": "#/components/schemas/KeccakOrPending" + }, + "parentHash": { + "description": "Hash of the parent block", + "$ref": "#/components/schemas/Keccak" + }, + "nonce": { + "description": "Randomly selected number to satisfy the proof-of-work or null when its the pending block", + "$ref": "#/components/schemas/IntOrPending" + }, + "sha3Uncles": { + "description": "Keccak hash of the uncles data in the block", + "$ref": "#/components/schemas/Keccak" + }, + "logsBloom": { + "type": "string", + "description": "The bloom filter for the logs of the block or null when its the pending block", + "pattern": "^0x[a-fA-F\\d]+$" + }, + "transactionsRoot": { + "description": "The root of the transactions trie of the block.", + "$ref": "#/components/schemas/Keccak" + }, + "stateRoot": { + "description": "The root of the final state trie of the block", + "$ref": "#/components/schemas/Keccak" + }, + "receiptsRoot": { + "description": "The root of the receipts trie of the block", + "$ref": "#/components/schemas/Keccak" + }, + "miner": { + "description": "The address of the beneficiary to whom the mining rewards were given or null when its the pending block", "oneOf": [ { - "description": "The Number of total uncles in the given block", - "$ref": "#/components/schemas/Integer" + "$ref": "#/components/schemas/Address" }, { "$ref": "#/components/schemas/Null" } ] + }, + "difficulty": { + "type": "string", + "description": "Integer of the difficulty for this block" + }, + "totalDifficulty": { + "description": "Integer of the total difficulty of the chain until this block", + "$ref": "#/components/schemas/IntOrPending" + }, + "extraData": { + "type": "string", + "description": "The 'extra data' field of this block" + }, + "size": { + "type": "string", + "description": "Integer the size of this block in bytes" + }, + "gasLimit": { + "type": "string", + "description": "The maximum gas allowed in this block" + }, + "gasUsed": { + "type": "string", + "description": "The total used gas by all transactions in this block" + }, + "timestamp": { + "type": "string", + "description": "The unix timestamp for when the block was collated" + }, + "uncles": { + "description": "Array of uncle hashes", + "type": "array", + "items": { + "description": "Block hash of the RLP encoding of an uncle block", + "$ref": "#/components/schemas/Keccak" + } } } }, - { - "name": "eth_getUncleCountByBlockNumber", - "summary": "Returns the number of uncles in a block from a block matching the given block number.", - "params": [ - { - "$ref": "#/components/contentDescriptors/BlockNumber" - } - ], - "result": { - "name": "uncleCountResult", - "schema": { - "title": "uncleCountOrNull", + "Block": { + "type": "object", + "properties": { + "number": { + "description": "The block number or null when its the pending block", + "$ref": "#/components/schemas/IntOrPending" + }, + "hash": { + "description": "The block hash or null when its the pending block", + "$ref": "#/components/schemas/KeccakOrPending" + }, + "parentHash": { + "description": "Hash of the parent block", + "$ref": "#/components/schemas/Keccak" + }, + "nonce": { + "description": "Randomly selected number to satisfy the proof-of-work or null when its the pending block", + "$ref": "#/components/schemas/IntOrPending" + }, + "sha3Uncles": { + "description": "Keccak hash of the uncles data in the block", + "$ref": "#/components/schemas/Keccak" + }, + "logsBloom": { + "type": "string", + "description": "The bloom filter for the logs of the block or null when its the pending block", + "pattern": "^0x[a-fA-F\\d]+$" + }, + "transactionsRoot": { + "description": "The root of the transactions trie of the block.", + "$ref": "#/components/schemas/Keccak" + }, + "stateRoot": { + "description": "The root of the final state trie of the block", + "$ref": "#/components/schemas/Keccak" + }, + "receiptsRoot": { + "description": "The root of the receipts trie of the block", + "$ref": "#/components/schemas/Keccak" + }, + "miner": { + "description": "The address of the beneficiary to whom the mining rewards were given or null when its the pending block", "oneOf": [ { - "description": "The Number of total uncles in the given block", - "$ref": "#/components/schemas/Integer" + "$ref": "#/components/schemas/Address" }, { "$ref": "#/components/schemas/Null" } ] - } - } - }, - { - "name": "eth_getProof", - "summary": "Returns the account- and storage-values of the specified account including the Merkle-proof.", - "params": [ - { - "name": "address", - "description": "The address of the account or contract", - "required": true, - "schema": { - "$ref": "#/components/schemas/Address" - } }, - { - "name": "storageKeys", - "required": true, - "schema": { - "title": "storageKeys", - "description": "The storage keys of all the storage slots being requested", - "items": { - "description": "A storage key is indexed from the solidity compiler by the order it is declared. For mappings it uses the keccak of the mapping key with its position (and recursively for X-dimensional mappings)", - "$ref": "#/components/schemas/Integer" - } + "difficulty": { + "type": "string", + "description": "Integer of the difficulty for this block" + }, + "totalDifficulty": { + "description": "Integer of the total difficulty of the chain until this block", + "$ref": "#/components/schemas/IntOrPending" + }, + "extraData": { + "type": "string", + "description": "The 'extra data' field of this block" + }, + "size": { + "type": "string", + "description": "Integer the size of this block in bytes" + }, + "gasLimit": { + "type": "string", + "description": "The maximum gas allowed in this block" + }, + "gasUsed": { + "type": "string", + "description": "The total used gas by all transactions in this block" + }, + "timestamp": { + "type": "string", + "description": "The unix timestamp for when the block was collated" + }, + "transactions": { + "description": "Array of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter", + "type": "array", + "items": { + "oneOf": [ + { + "$ref": "#/components/schemas/Transaction" + }, + { + "$ref": "#/components/schemas/TransactionHash" + } + ] } }, - { - "$ref": "#/components/contentDescriptors/BlockNumber" + "uncles": { + "description": "Array of uncle hashes", + "type": "array", + "items": { + "description": "Block hash of the RLP encoding of an uncle block", + "$ref": "#/components/schemas/Keccak" + } } + } + }, + "Transaction": { + "type": "object", + "required": [ + "gas", + "gasPrice", + "nonce" ], - "result": { - "name": "account", - "schema": { - "title": "proofAccountOrNull", - "oneOf": [ - { - "title": "proofAccount", - "type": "object", - "description": "The merkle proofs of the specified account connecting them to the blockhash of the block specified", - "properties": { - "address": { - "description": "The address of the account or contract of the request", - "$ref": "#/components/schemas/Address" - }, - "accountProof": { - "$ref": "#/components/schemas/AccountProof" - }, - "balance": { - "description": "The Ubiq balance of the account or contract of the request", - "$ref": "#/components/schemas/Integer" - }, - "codeHash": { - "description": "The code hash of the contract of the request (keccak(NULL) if external account)", - "$ref": "#/components/schemas/Keccak" - }, - "nonce": { - "description": "The transaction count of the account or contract of the request", - "$ref": "#/components/schemas/Nonce" - }, - "storageHash": { - "description": "The storage hash of the contract of the request (keccak(rlp(NULL)) if external account)", - "$ref": "#/components/schemas/Keccak" - }, - "storageProof": { - "$ref": "#/components/schemas/StorageProof" - } - } - }, - { - "$ref": "#/components/schemas/Null" - } - ] + "properties": { + "blockHash": { + "description": "Hash of the block where this transaction was in. null when its pending", + "$ref": "#/components/schemas/KeccakOrPending" + }, + "blockNumber": { + "description": "Block number where this transaction was in. null when its pending", + "$ref": "#/components/schemas/IntOrPending" + }, + "from": { + "description": "Address of the sender", + "$ref": "#/components/schemas/Address" + }, + "gas": { + "type": "string", + "description": "The gas limit provided by the sender in Wei" + }, + "gasPrice": { + "type": "string", + "description": "The gas price willing to be paid by the sender in Wei" + }, + "hash": { + "$ref": "#/components/schemas/TransactionHash" + }, + "input": { + "type": "string", + "description": "The data field sent with the transaction" + }, + "nonce": { + "description": "The total number of prior transactions made by the sender", + "$ref": "#/components/schemas/Nonce" + }, + "to": { + "description": "address of the receiver. null when its a contract creation transaction", + "$ref": "#/components/schemas/Address" + }, + "transactionIndex": { + "description": "Integer of the transaction's index position in the block. null when its pending", + "$ref": "#/components/schemas/IntOrPending" + }, + "value": { + "description": "Value of Ubiq being transferred in Wei", + "$ref": "#/components/schemas/Keccak" + }, + "v": { + "type": "string", + "description": "ECDSA recovery id" + }, + "r": { + "type": "string", + "description": "ECDSA signature r" + }, + "s": { + "type": "string", + "description": "ECDSA signature s" } } }, - { - "name": "eth_getWork", - "summary": "Returns the hash of the current block, the seedHash, and the boundary condition to be met ('target').", - "params": [], - "result": { - "name": "work", - "schema": { - "type": "array", - "items": [ - { - "$ref": "#/components/schemas/PowHash" - }, - { - "$ref": "#/components/schemas/SeedHash" - }, - { - "$ref": "#/components/schemas/Difficulty" - } - ] + "TransactionHash": { + "type": "string", + "description": "Keccak 256 Hash of the RLP encoding of a transaction", + "$ref": "#/components/schemas/Keccak" + }, + "KeccakOrPending": { + "oneOf": [ + { + "$ref": "#/components/schemas/Keccak" + }, + { + "$ref": "#/components/schemas/Null" } - } + ] }, - { - "name": "eth_hashrate", - "summary": "Returns the number of hashes per second that the node is mining with.", - "params": [], - "result": { - "name": "hashesPerSecond", - "schema": { - "description": "Integer of the number of hashes per second", + "IntOrPending": { + "oneOf": [ + { "$ref": "#/components/schemas/Integer" + }, + { + "$ref": "#/components/schemas/Null" } - } + ] }, - { - "name": "eth_mining", - "summary": "Returns true if client is actively mining new blocks.", - "params": [], - "result": { - "name": "mining", - "schema": { - "description": "Whether of not the client is mining", - "type": "boolean" - } - } + "Keccak": { + "type": "string", + "description": "Hex representation of a Keccak 256 hash", + "pattern": "^0x[a-fA-F\\d]{64}$" }, - { - "name": "eth_newBlockFilter", - "summary": "Creates a filter in the node, to notify when a new block arrives. To check if the state has changed, call eth_getFilterChanges.", - "params": [], - "result": { - "$ref": "#/components/contentDescriptors/FilterId" - } + "Nonce": { + "description": "A number only to be used once", + "pattern": "^0x[a-fA-F0-9]+$", + "type": "string" }, - { - "name": "eth_newFilter", - "summary": "Creates a filter object, based on filter options, to notify when the state changes (logs). To check if the state has changed, call eth_getFilterChanges.", - "params": [ - { - "$ref": "#/components/contentDescriptors/Filter" - } - ], - "result": { - "name": "filterId", - "schema": { - "description": "The filter ID for use in ` + "`" + `eth_getFilterChanges` + "`" + `", - "$ref": "#/components/schemas/Integer" - } - } + "Null": { + "type": "null", + "description": "Null" }, - { - "name": "eth_newPendingTransactionFilter", - "summary": "Creates a filter in the node, to notify when new pending transactions arrive. To check if the state has changed, call eth_getFilterChanges.", - "params": [], - "result": { - "$ref": "#/components/contentDescriptors/FilterId" - } + "Integer": { + "type": "string", + "pattern": "^0x[a-fA-F0-9]+$", + "description": "Hex representation of an integer" }, - { - "name": "eth_pendingTransactions", - "summary": "Returns the pending transactions list.", - "params": [], - "result": { - "name": "pendingTransactions", - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Transaction" - } - } - } + "Address": { + "type": "string", + "pattern": "^0x[a-fA-F\\d]{40}$" }, - { - "name": "eth_protocolVersion", - "summary": "Returns the current ubiq protocol version.", - "params": [], - "result": { - "name": "protocolVersion", - "schema": { - "description": "The current ubiq protocol version", - "$ref": "#/components/schemas/Integer" - } + "Position": { + "type": "string", + "description": "Hex representation of the storage slot where the variable exists", + "pattern": "^0x([a-fA-F0-9]?)+$" + }, + "DataWord": { + "type": "string", + "description": "Hex representation of a 256 bit unit of data", + "pattern": "^0x([a-fA-F\\d]{64})?$" + }, + "Bytes": { + "type": "string", + "description": "Hex representation of a variable length byte array", + "pattern": "^0x([a-fA-F0-9]?)+$" + } + }, + "contentDescriptors": { + "Block": { + "name": "block", + "summary": "A block", + "description": "A block object", + "schema": { + "$ref": "#/components/schemas/Block" } }, - { - "name": "eth_sendRawTransaction", - "summary": "Creates new message call transaction or a contract creation for signed transactions.", - "params": [ - { - "name": "signedTransactionData", - "required": true, - "description": "The signed transaction data", - "schema": { - "$ref": "#/components/schemas/Bytes" - } - } - ], - "result": { - "name": "transactionHash", - "schema": { - "description": "The transaction hash, or the zero hash if the transaction is not yet available.", - "$ref": "#/components/schemas/Keccak" - } + "Null": { + "name": "Null", + "description": "JSON Null value", + "summary": "Null value", + "schema": { + "type": "null", + "description": "Null value" } }, - { - "name": "eth_submitHashrate", - "summary": "Returns an array of all logs matching a given filter object.", - "params": [ - { - "name": "hashRate", - "required": true, - "schema": { - "$ref": "#/components/schemas/DataWord" - } - }, - { - "name": "id", - "required": true, - "description": "String identifying the client", - "schema": { - "$ref": "#/components/schemas/DataWord" - } - } - ], - "result": { - "name": "submitHashRateSuccess", - "schema": { - "type": "boolean", - "description": "whether of not submitting went through successfully" - } + "Signature": { + "name": "signature", + "summary": "The signature.", + "required": true, + "schema": { + "$ref": "#/components/schemas/Bytes", + "pattern": "0x^([A-Fa-f0-9]{2}){65}$" } }, - { - "name": "eth_submitWork", - "summary": "Used for submitting a proof-of-work solution.", - "params": [ - { - "$ref": "#/components/contentDescriptors/Nonce" - }, - { - "name": "powHash", - "required": true, - "schema": { - "$ref": "#/components/schemas/PowHash" - } - }, - { - "name": "mixHash", - "required": true, - "schema": { - "$ref": "#/components/schemas/MixHash" - } - } - ], - "result": { - "name": "solutionValid", - "description": "returns true if the provided solution is valid, otherwise false.", - "schema": { - "type": "boolean", - "description": "Whether or not the provided solution is valid" - } - }, - "examples": [ - { - "name": "submitWorkExample", - "params": [ - { - "name": "nonceExample", - "description": "example of a number only used once", - "value": "0x0000000000000001" - }, - { - "name": "powHashExample", - "description": "proof of work to submit", - "value": "0x6bf2cAE0dE3ec3ecA5E194a6C6e02cf42aADfe1C2c4Fff12E5D36C3Cf7297F22" - }, - { - "name": "mixHashExample", - "description": "the mix digest example", - "value": "0xD1FE5700000000000000000000000000D1FE5700000000000000000000000000" - } - ], - "result": { - "name": "solutionInvalidExample", - "description": "this example should return ` + "`" + `false` + "`" + ` as it is not a valid pow to submit", - "value": false - } - } - ] + "GasPrice": { + "name": "gasPrice", + "required": true, + "schema": { + "description": "Integer of the current gas price", + "$ref": "#/components/schemas/Integer" + } }, - { - "name": "eth_syncing", - "summary": "Returns an object with data about the sync status or false.", - "params": [], - "result": { - "name": "syncing", - "schema": { - "oneOf": [ - { - "description": "An object with sync status data", - "type": "object", - "properties": { - "startingBlock": { - "description": "Block at which the import started (will only be reset, after the sync reached his head)", - "$ref": "#/components/schemas/Integer" - }, - "currentBlock": { - "description": "The current block, same as eth_blockNumber", - "$ref": "#/components/schemas/Integer" - }, - "highestBlock": { - "description": "The estimated highest block", - "$ref": "#/components/schemas/Integer" - }, - "knownStates": { - "description": "The known states", - "$ref": "#/components/schemas/Integer" - }, - "pulledStates": { - "description": "The pulled states", - "$ref": "#/components/schemas/Integer" - } - } - }, - { - "type": "boolean", - "description": "The value ` + "`" + `false` + "`" + ` indicating that syncing is complete" - } - ] - } + "Transaction": { + "required": true, + "name": "transaction", + "schema": { + "$ref": "#/components/schemas/Transaction" } }, - { - "name": "eth_uninstallFilter", - "summary": "Uninstalls a filter with given id. Should always be called when watch is no longer needed. Additionally Filters timeout when they aren't requested with eth_getFilterChanges for a period of time.", - "params": [ - { - "name": "filterId", - "required": true, - "schema": { - "$ref": "#/components/schemas/FilterId" + "TransactionResult": { + "name": "transactionResult", + "description": "Returns a transaction or null", + "schema": { + "oneOf": [ + { + "$ref": "#/components/schemas/Transaction" + }, + { + "$ref": "#/components/schemas/Null" } - } - ], - "result": { - "name": "filterUninstalledSuccess", - "schema": { - "type": "boolean", - "description": "Whether of not the filter was successfully uninstalled" - } + ] } - } - ], - "components": { - "schemas": { - "ProofNode": { - "type": "string", - "description": "An individual node used to prove a path down a merkle-patricia-tree", + }, + "Message": { + "name": "message", + "required": true, + "schema": { "$ref": "#/components/schemas/Bytes" - }, - "AccountProof": { - "$ref": "#/components/schemas/ProofNodes" - }, - "StorageProof": { - "type": "array", - "description": "Current block header PoW hash.", - "items": { - "type": "object", - "description": "Object proving a relationship of a storage value to an account's storageHash.", - "properties": { - "key": { - "description": "The key used to get the storage slot in its account tree", - "$ref": "#/components/schemas/Integer" - }, - "value": { - "description": "The value of the storage slot in its account tree", - "$ref": "#/components/schemas/Integer" - }, - "proof": { - "$ref": "#/components/schemas/ProofNodes" - } - } - } - }, - "ProofNodes": { - "type": "array", - "description": "The set of node values needed to traverse a patricia merkle tree (from root to leaf) to retrieve a value", - "items": { - "$ref": "#/components/schemas/ProofNode" - } - }, - "PowHash": { - "description": "Current block header PoW hash.", - "$ref": "#/components/schemas/DataWord" - }, - "SeedHash": { - "description": "The seed hash used for the DAG.", - "$ref": "#/components/schemas/DataWord" - }, - "MixHash": { - "description": "The mix digest.", - "$ref": "#/components/schemas/DataWord" - }, - "Difficulty": { - "description": "The boundary condition ('target'), 2^256 / difficulty.", - "$ref": "#/components/schemas/DataWord" - }, - "FilterId": { - "type": "string", - "description": "An identifier used to reference the filter." - }, - "BlockHash": { - "type": "string", - "pattern": "^0x[a-fA-F\\d]{64}$", - "description": "The hex representation of the Keccak 256 of the RLP encoded block" - }, - "BlockNumber": { - "type": "string", - "pattern": "^0x[a-fA-F\\d]+$", - "description": "The hex representation of the block's height" - }, - "BlockNumberTag": { - "type": "string", - "description": "The optional block height description", - "enum": [ - "earliest", - "latest", - "pending" - ] - }, - "Receipt": { + } + }, + "Filter": { + "name": "filter", + "required": true, + "schema": { "type": "object", - "description": "The receipt of a transaction", - "required": [ - "blockHash", - "blockNumber", - "contractAddress", - "cumulativeGasUsed", - "from", - "gasUsed", - "logs", - "logsBloom", - "to", - "transactionHash", - "transactionIndex" - ], + "description": "A filter used to monitor the blockchain for log/events", "properties": { - "blockHash": { - "description": "BlockHash of the block in which the transaction was mined", - "$ref": "#/components/schemas/BlockHash" - }, - "blockNumber": { - "description": "BlockNumber of the block in which the transaction was mined", + "fromBlock": { + "description": "Block from which to begin filtering events", "$ref": "#/components/schemas/BlockNumber" }, - "contractAddress": { - "description": "The contract address created, if the transaction was a contract creation, otherwise null", - "$ref": "#/components/schemas/Address" - }, - "cumulativeGasUsed": { - "description": "The gas units used by the transaction", - "$ref": "#/components/schemas/Integer" - }, - "from": { - "description": "The sender of the transaction", - "$ref": "#/components/schemas/Address" - }, - "gasUsed": { - "description": "The total gas used by the transaction", - "$ref": "#/components/schemas/Integer" - }, - "logs": { - "type": "array", - "description": "An array of all the logs triggered during the transaction", - "items": { - "$ref": "#/components/schemas/Log" - } - }, - "logsBloom": { - "$ref": "#/components/schemas/BloomFilter" - }, - "to": { - "description": "Destination address of the transaction", - "$ref": "#/components/schemas/Address" - }, - "transactionHash": { - "description": "Keccak 256 of the transaction", - "$ref": "#/components/schemas/Keccak" - }, - "transactionIndex": { - "description": "An array of all the logs triggered during the transaction", - "$ref": "#/components/schemas/BloomFilter" - }, - "postTransactionState": { - "description": "The intermediate stateRoot directly after transaction execution.", - "$ref": "#/components/schemas/Keccak" - }, - "status": { - "description": "Whether or not the transaction threw an error.", - "type": "boolean" - } - } - }, - "BloomFilter": { - "type": "string", - "description": "A 2048 bit bloom filter from the logs of the transaction. Each log sets 3 bits though taking the low-order 11 bits of each of the first three pairs of bytes in a Keccak 256 hash of the log's byte series" - }, - "Log": { - "type": "object", - "description": "An indexed event generated during a transaction", - "properties": { - "address": { - "description": "Sender of the transaction", - "$ref": "#/components/schemas/Address" - }, - "blockHash": { - "description": "BlockHash of the block in which the transaction was mined", - "$ref": "#/components/schemas/BlockHash" - }, - "blockNumber": { - "description": "BlockNumber of the block in which the transaction was mined", + "toBlock": { + "description": "Block from which to end filtering events", "$ref": "#/components/schemas/BlockNumber" }, - "data": { - "description": "The data/input string sent along with the transaction", - "$ref": "#/components/schemas/Bytes" - }, - "logIndex": { - "description": "The index of the event within its transaction, null when its pending", - "$ref": "#/components/schemas/Integer" - }, - "removed": { - "schema": { - "description": "Whether or not the log was orphaned off the main chain", - "type": "boolean" - } - }, - "topics": { - "type": "array", - "items": { - "topic": { - "description": "32 Bytes DATA of indexed log arguments. (In solidity: The first topic is the hash of the signature of the event (e.g. Deposit(address,bytes32,uint256))", - "$ref": "#/components/schemas/DataWord" - } - } - }, - "transactionHash": { - "description": "The hash of the transaction in which the log occurred", - "$ref": "#/components/schemas/Keccak" - }, - "transactionIndex": { - "description": "The index of the transaction in which the log occurred", - "$ref": "#/components/schemas/Integer" - } - } - }, - "Uncle": { - "type": "object", - "description": "Orphaned blocks that can be included in the chain but at a lower block reward. NOTE: An uncle doesn’t contain individual transactions.", - "properties": { - "number": { - "description": "The block number or null when its the pending block", - "$ref": "#/components/schemas/IntOrPending" - }, - "hash": { - "description": "The block hash or null when its the pending block", - "$ref": "#/components/schemas/KeccakOrPending" - }, - "parentHash": { - "description": "Hash of the parent block", - "$ref": "#/components/schemas/Keccak" - }, - "nonce": { - "description": "Randomly selected number to satisfy the proof-of-work or null when its the pending block", - "$ref": "#/components/schemas/IntOrPending" - }, - "sha3Uncles": { - "description": "Keccak hash of the uncles data in the block", - "$ref": "#/components/schemas/Keccak" - }, - "logsBloom": { - "type": "string", - "description": "The bloom filter for the logs of the block or null when its the pending block", - "pattern": "^0x[a-fA-F\\d]+$" - }, - "transactionsRoot": { - "description": "The root of the transactions trie of the block.", - "$ref": "#/components/schemas/Keccak" - }, - "stateRoot": { - "description": "The root of the final state trie of the block", - "$ref": "#/components/schemas/Keccak" - }, - "receiptsRoot": { - "description": "The root of the receipts trie of the block", - "$ref": "#/components/schemas/Keccak" - }, - "miner": { - "description": "The address of the beneficiary to whom the mining rewards were given or null when its the pending block", - "oneOf": [ - { - "$ref": "#/components/schemas/Address" - }, - { - "$ref": "#/components/schemas/Null" - } - ] - }, - "difficulty": { - "type": "string", - "description": "Integer of the difficulty for this block" - }, - "totalDifficulty": { - "description": "Integer of the total difficulty of the chain until this block", - "$ref": "#/components/schemas/IntOrPending" - }, - "extraData": { - "type": "string", - "description": "The 'extra data' field of this block" - }, - "size": { - "type": "string", - "description": "Integer the size of this block in bytes" - }, - "gasLimit": { - "type": "string", - "description": "The maximum gas allowed in this block" - }, - "gasUsed": { - "type": "string", - "description": "The total used gas by all transactions in this block" - }, - "timestamp": { - "type": "string", - "description": "The unix timestamp for when the block was collated" - }, - "uncles": { - "description": "Array of uncle hashes", - "type": "array", - "items": { - "description": "Block hash of the RLP encoding of an uncle block", - "$ref": "#/components/schemas/Keccak" - } - } - } - }, - "Block": { - "type": "object", - "properties": { - "number": { - "description": "The block number or null when its the pending block", - "$ref": "#/components/schemas/IntOrPending" - }, - "hash": { - "description": "The block hash or null when its the pending block", - "$ref": "#/components/schemas/KeccakOrPending" - }, - "parentHash": { - "description": "Hash of the parent block", - "$ref": "#/components/schemas/Keccak" - }, - "nonce": { - "description": "Randomly selected number to satisfy the proof-of-work or null when its the pending block", - "$ref": "#/components/schemas/IntOrPending" - }, - "sha3Uncles": { - "description": "Keccak hash of the uncles data in the block", - "$ref": "#/components/schemas/Keccak" - }, - "logsBloom": { - "type": "string", - "description": "The bloom filter for the logs of the block or null when its the pending block", - "pattern": "^0x[a-fA-F\\d]+$" - }, - "transactionsRoot": { - "description": "The root of the transactions trie of the block.", - "$ref": "#/components/schemas/Keccak" - }, - "stateRoot": { - "description": "The root of the final state trie of the block", - "$ref": "#/components/schemas/Keccak" - }, - "receiptsRoot": { - "description": "The root of the receipts trie of the block", - "$ref": "#/components/schemas/Keccak" - }, - "miner": { - "description": "The address of the beneficiary to whom the mining rewards were given or null when its the pending block", + "address": { "oneOf": [ { + "type": "string", + "description": "Address of the contract from which to monitor events", "$ref": "#/components/schemas/Address" }, { - "$ref": "#/components/schemas/Null" + "type": "array", + "description": "List of contract addresses from which to monitor events", + "items": { + "$ref": "#/components/schemas/Address" + } } ] }, - "difficulty": { - "type": "string", - "description": "Integer of the difficulty for this block" - }, - "totalDifficulty": { - "description": "Integer of the total difficulty of the chain until this block", - "$ref": "#/components/schemas/IntOrPending" - }, - "extraData": { - "type": "string", - "description": "The 'extra data' field of this block" - }, - "size": { - "type": "string", - "description": "Integer the size of this block in bytes" - }, - "gasLimit": { - "type": "string", - "description": "The maximum gas allowed in this block" - }, - "gasUsed": { - "type": "string", - "description": "The total used gas by all transactions in this block" - }, - "timestamp": { - "type": "string", - "description": "The unix timestamp for when the block was collated" - }, - "transactions": { - "description": "Array of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter", - "type": "array", - "items": { - "oneOf": [ - { - "$ref": "#/components/schemas/Transaction" - }, - { - "$ref": "#/components/schemas/TransactionHash" - } - ] - } - }, - "uncles": { - "description": "Array of uncle hashes", + "topics": { "type": "array", + "description": "Array of 32 Bytes DATA topics. Topics are order-dependent. Each topic can also be an array of DATA with 'or' options", "items": { - "description": "Block hash of the RLP encoding of an uncle block", - "$ref": "#/components/schemas/Keccak" + "description": "Indexable 32 bytes piece of data (made from the event's function signature in solidity)", + "$ref": "#/components/schemas/DataWord" } } } - }, - "Transaction": { - "type": "object", - "required": [ - "gas", - "gasPrice", - "nonce" - ], - "properties": { - "blockHash": { - "description": "Hash of the block where this transaction was in. null when its pending", - "$ref": "#/components/schemas/KeccakOrPending" - }, - "blockNumber": { - "description": "Block number where this transaction was in. null when its pending", - "$ref": "#/components/schemas/IntOrPending" - }, - "from": { - "description": "Address of the sender", - "$ref": "#/components/schemas/Address" - }, - "gas": { - "type": "string", - "description": "The gas limit provided by the sender in Wei" - }, - "gasPrice": { - "type": "string", - "description": "The gas price willing to be paid by the sender in Wei" - }, - "hash": { - "$ref": "#/components/schemas/TransactionHash" - }, - "input": { - "type": "string", - "description": "The data field sent with the transaction" - }, - "nonce": { - "description": "The total number of prior transactions made by the sender", - "$ref": "#/components/schemas/Nonce" - }, - "to": { - "description": "address of the receiver. null when its a contract creation transaction", - "$ref": "#/components/schemas/Address" - }, - "transactionIndex": { - "description": "Integer of the transaction's index position in the block. null when its pending", - "$ref": "#/components/schemas/IntOrPending" - }, - "value": { - "description": "Value of Ubiq being transferred in Wei", - "$ref": "#/components/schemas/Keccak" - }, - "v": { - "type": "string", - "description": "ECDSA recovery id" - }, - "r": { - "type": "string", - "description": "ECDSA signature r" - }, - "s": { - "type": "string", - "description": "ECDSA signature s" - } + } + }, + "Address": { + "name": "address", + "required": true, + "schema": { + "$ref": "#/components/schemas/Address" + } + }, + "BlockHash": { + "name": "blockHash", + "required": true, + "schema": { + "$ref": "#/components/schemas/BlockHash" + } + }, + "Nonce": { + "name": "nonce", + "required": true, + "schema": { + "$ref": "#/components/schemas/Nonce" + } + }, + "Position": { + "name": "key", + "required": true, + "schema": { + "$ref": "#/components/schemas/Position" + } + }, + "Logs": { + "name": "logs", + "description": "An array of all logs matching filter with given id.", + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Log" } - }, - "TransactionHash": { - "type": "string", - "description": "Keccak 256 Hash of the RLP encoding of a transaction", - "$ref": "#/components/schemas/Keccak" - }, - "KeccakOrPending": { - "oneOf": [ - { - "$ref": "#/components/schemas/Keccak" - }, - { - "$ref": "#/components/schemas/Null" - } - ] - }, - "IntOrPending": { + } + }, + "FilterId": { + "name": "filterId", + "schema": { + "description": "The filter ID for use in ` + "`" + `eth_getFilterChanges` + "`" + `", + "$ref": "#/components/schemas/Integer" + } + }, + "BlockNumber": { + "name": "blockNumber", + "required": true, + "schema": { "oneOf": [ { - "$ref": "#/components/schemas/Integer" + "$ref": "#/components/schemas/BlockNumber" }, { - "$ref": "#/components/schemas/Null" + "$ref": "#/components/schemas/BlockNumberTag" } ] - }, - "Keccak": { - "type": "string", - "description": "Hex representation of a Keccak 256 hash", - "pattern": "^0x[a-fA-F\\d]{64}$" - }, - "Nonce": { - "description": "A number only to be used once", - "pattern": "^0x[a-fA-F0-9]+$", - "type": "string" - }, - "Null": { - "type": "null", - "description": "Null" - }, - "Integer": { - "type": "string", - "pattern": "^0x[a-fA-F0-9]+$", - "description": "Hex representation of the integer" - }, - "Address": { - "type": "string", - "pattern": "^0x[a-fA-F\\d]{40}$" - }, - "Position": { - "type": "string", - "description": "Hex representation of the storage slot where the variable exists", - "pattern": "^0x([a-fA-F0-9]?)+$" - }, - "DataWord": { - "type": "string", - "description": "Hex representation of a 256 bit unit of data", - "pattern": "^0x([a-fA-F\\d]{64})?$" - }, - "Bytes": { - "type": "string", - "description": "Hex representation of a variable length byte array", - "pattern": "^0x([a-fA-F0-9]?)+$" } }, - "contentDescriptors": { - "Block": { - "name": "block", - "summary": "A block", - "description": "A block object", - "schema": { - "$ref": "#/components/schemas/Block" - } - }, - "Null": { - "name": "Null", - "description": "JSON Null value", - "summary": "Null value", - "schema": { - "type": "null", - "description": "Null value" - } - }, - "Signature": { - "name": "signature", - "summary": "The signature.", - "required": true, - "schema": { - "$ref": "#/components/schemas/Bytes", - "pattern": "0x^([A-Fa-f0-9]{2}){65}$" - } - }, - "GasPrice": { - "name": "gasPrice", - "required": true, - "schema": { - "description": "Integer of the current gas price", - "$ref": "#/components/schemas/Integer" - } - }, - "Transaction": { - "required": true, - "name": "transaction", - "schema": { - "$ref": "#/components/schemas/Transaction" - } - }, - "TransactionResult": { - "name": "transactionResult", - "description": "Returns a transaction or null", - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/Transaction" - }, - { - "$ref": "#/components/schemas/Null" - } - ] - } - }, - "Message": { - "name": "message", - "required": true, - "schema": { - "$ref": "#/components/schemas/Bytes" - } - }, - "Filter": { - "name": "filter", - "required": true, - "schema": { - "type": "object", - "description": "A filter used to monitor the blockchain for log/events", - "properties": { - "fromBlock": { - "description": "Block from which to begin filtering events", - "$ref": "#/components/schemas/BlockNumber" - }, - "toBlock": { - "description": "Block from which to end filtering events", - "$ref": "#/components/schemas/BlockNumber" - }, - "address": { - "oneOf": [ - { - "type": "string", - "description": "Address of the contract from which to monitor events", - "$ref": "#/components/schemas/Address" - }, - { - "type": "array", - "description": "List of contract addresses from which to monitor events", - "items": { - "$ref": "#/components/schemas/Address" - } - } - ] - }, - "topics": { - "type": "array", - "description": "Array of 32 Bytes DATA topics. Topics are order-dependent. Each topic can also be an array of DATA with 'or' options", - "items": { - "description": "Indexable 32 bytes piece of data (made from the event's function signature in solidity)", - "$ref": "#/components/schemas/DataWord" - } - } - } - } - }, - "Address": { - "name": "address", - "required": true, - "schema": { - "$ref": "#/components/schemas/Address" - } - }, - "BlockHash": { - "name": "blockHash", - "required": true, - "schema": { - "$ref": "#/components/schemas/BlockHash" - } - }, - "Nonce": { - "name": "nonce", - "required": true, - "schema": { - "$ref": "#/components/schemas/Nonce" - } - }, - "Position": { - "name": "key", - "required": true, - "schema": { - "$ref": "#/components/schemas/Position" - } - }, - "Logs": { - "name": "logs", - "description": "An array of all logs matching filter with given id.", - "schema": { - "type": "array", - "items": { - "$ref": "#/components/schemas/Log" - } - } - }, - "FilterId": { - "name": "filterId", - "schema": { - "description": "The filter ID for use in ` + "`" + `eth_getFilterChanges` + "`" + `", - "$ref": "#/components/schemas/Integer" - } - }, - "BlockNumber": { - "name": "blockNumber", - "required": true, - "schema": { - "oneOf": [ - { - "$ref": "#/components/schemas/BlockNumber" - }, - { - "$ref": "#/components/schemas/BlockNumberTag" - } - ] - } - }, - "TransactionHash": { - "name": "transactionHash", - "required": true, - "schema": { - "$ref": "#/components/schemas/TransactionHash" - } + "TransactionHash": { + "name": "transactionHash", + "required": true, + "schema": { + "$ref": "#/components/schemas/TransactionHash" } } } } +} ` From aaec135b3400f5dba1b26d62fd7448aeef5c4c27 Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Tue, 26 Nov 2019 18:06:59 +0100 Subject: [PATCH 06/29] swarm: remove swarm (use ipfs) --- Makefile | 10 +- build/ci.go | 40 +- build/update-license.go | 2 - cmd/swarm/access.go | 297 --- cmd/swarm/access_test.go | 614 ----- cmd/swarm/bootnodes.go | 24 - cmd/swarm/config.go | 447 ---- cmd/swarm/config_test.go | 575 ----- cmd/swarm/db.go | 147 -- cmd/swarm/download.go | 112 - cmd/swarm/explore.go | 59 - cmd/swarm/export_test.go | 119 - cmd/swarm/feeds.go | 238 -- cmd/swarm/feeds_test.go | 196 -- cmd/swarm/flags.go | 184 -- cmd/swarm/fs.go | 162 -- cmd/swarm/fs_test.go | 260 -- cmd/swarm/global-store/global_store.go | 100 - cmd/swarm/global-store/global_store_test.go | 191 -- cmd/swarm/global-store/main.go | 104 - cmd/swarm/global-store/run_test.go | 49 - cmd/swarm/hash.go | 58 - cmd/swarm/list.go | 70 - cmd/swarm/main.go | 469 ---- cmd/swarm/manifest.go | 353 --- cmd/swarm/manifest_test.go | 597 ----- cmd/swarm/mimegen/generator.go | 124 - cmd/swarm/mimegen/mime.types | 1828 -------------- cmd/swarm/run_test.go | 480 ---- cmd/swarm/swarm-smoke/feed_upload_and_sync.go | 291 --- cmd/swarm/swarm-smoke/main.go | 190 -- cmd/swarm/swarm-smoke/sliding_window.go | 131 - cmd/swarm/swarm-smoke/upload_and_sync.go | 194 -- cmd/swarm/swarm-smoke/upload_speed.go | 73 - cmd/swarm/swarm-smoke/util.go | 235 -- cmd/swarm/swarm-snapshot/create.go | 157 -- cmd/swarm/swarm-snapshot/create_test.go | 143 -- cmd/swarm/swarm-snapshot/main.go | 82 - cmd/swarm/swarm-snapshot/run_test.go | 49 - cmd/swarm/upload.go | 188 -- cmd/swarm/upload_test.go | 356 --- contracts/chequebook/api.go | 68 - contracts/chequebook/cheque.go | 641 ----- contracts/chequebook/cheque_test.go | 487 ---- contracts/chequebook/contract/chequebook.go | 367 --- contracts/chequebook/contract/chequebook.sol | 47 - contracts/chequebook/contract/code.go | 5 - contracts/chequebook/contract/mortal.sol | 10 - contracts/chequebook/contract/owned.sol | 15 - contracts/chequebook/gencode.go | 70 - internal/web3ext/web3ext.go | 57 - p2p/protocols/protocol.go | 6 +- {swarm => p2p}/spancontext/spancontext.go | 0 {swarm => p2p}/tracing/tracing.go | 2 +- swarm/AUTHORS | 35 - swarm/OWNERS | 25 - swarm/README.md | 244 -- swarm/api/act.go | 538 ----- swarm/api/api.go | 1017 -------- swarm/api/api_test.go | 502 ---- swarm/api/client/client.go | 797 ------- swarm/api/client/client_test.go | 589 ----- swarm/api/config.go | 143 -- swarm/api/config_test.go | 59 - swarm/api/encrypt.go | 78 - swarm/api/filesystem.go | 292 --- swarm/api/filesystem_test.go | 199 -- swarm/api/gen_mime.go | 1201 ---------- swarm/api/http/middleware.go | 113 - swarm/api/http/response.go | 132 -- swarm/api/http/response_test.go | 170 -- swarm/api/http/roundtripper.go | 66 - swarm/api/http/roundtripper_test.go | 69 - swarm/api/http/sctx.go | 34 - swarm/api/http/server.go | 887 ------- swarm/api/http/server_test.go | 1313 ---------- swarm/api/http/templates.go | 306 --- swarm/api/http/test_server.go | 97 - swarm/api/inspector.go | 58 - swarm/api/manifest.go | 585 ----- swarm/api/manifest_test.go | 175 -- swarm/api/storage.go | 85 - swarm/api/storage_test.go | 56 - swarm/api/testdata/test0/img/logo.png | Bin 4119 -> 0 bytes swarm/api/testdata/test0/index.css | 9 - swarm/api/testdata/test0/index.html | 10 - swarm/api/uri.go | 144 -- swarm/api/uri_test.go | 175 -- swarm/bmt/bmt.go | 690 ------ swarm/bmt/bmt_r.go | 84 - swarm/bmt/bmt_test.go | 583 ----- swarm/chunk/chunk.go | 5 - swarm/dev/.dockerignore | 2 - swarm/dev/.gitignore | 2 - swarm/dev/Dockerfile | 42 - swarm/dev/Makefile | 14 - swarm/dev/README.md | 20 - swarm/dev/bashrc | 21 - swarm/dev/run.sh | 90 - swarm/dev/scripts/boot-cluster.sh | 288 --- swarm/dev/scripts/random-uploads.sh | 96 - swarm/dev/scripts/stop-cluster.sh | 98 - swarm/dev/scripts/util.sh | 53 - swarm/docker/Dockerfile | 32 - swarm/docker/run-smoke.sh | 7 - swarm/docker/run.sh | 26 - swarm/fuse/fuse_dir.go | 161 -- swarm/fuse/fuse_file.go | 146 -- swarm/fuse/fuse_root.go | 35 - swarm/fuse/swarmfs.go | 65 - swarm/fuse/swarmfs_fallback.go | 51 - swarm/fuse/swarmfs_test.go | 1671 ------------- swarm/fuse/swarmfs_unix.go | 285 --- swarm/fuse/swarmfs_util.go | 121 - swarm/log/log.go | 48 - swarm/metrics/flags.go | 108 - swarm/network/README.md | 152 -- swarm/network/bitvector/bitvector.go | 62 - swarm/network/bitvector/bitvector_test.go | 104 - swarm/network/common.go | 30 - swarm/network/discovery.go | 203 -- swarm/network/discovery_test.go | 57 - swarm/network/fetcher.go | 323 --- swarm/network/fetcher_test.go | 476 ---- swarm/network/hive.go | 244 -- swarm/network/hive_test.go | 155 -- swarm/network/kademlia.go | 870 ------- swarm/network/kademlia_test.go | 562 ----- swarm/network/networkid_test.go | 263 -- swarm/network/priorityqueue/priorityqueue.go | 109 - .../priorityqueue/priorityqueue_test.go | 97 - swarm/network/protocol.go | 389 --- swarm/network/protocol_test.go | 253 -- swarm/network/simulation/bucket.go | 79 - swarm/network/simulation/bucket_test.go | 155 -- swarm/network/simulation/events.go | 217 -- swarm/network/simulation/events_test.go | 107 - swarm/network/simulation/example_test.go | 141 -- swarm/network/simulation/http.go | 68 - swarm/network/simulation/http_test.go | 110 - swarm/network/simulation/kademlia.go | 97 - swarm/network/simulation/kademlia_test.go | 68 - swarm/network/simulation/node.go | 309 --- swarm/network/simulation/node_test.go | 444 ---- swarm/network/simulation/service.go | 65 - swarm/network/simulation/service_test.go | 46 - swarm/network/simulation/simulation.go | 215 -- swarm/network/simulation/simulation_test.go | 203 -- .../simulations/discovery/discovery.go | 17 - .../simulations/discovery/discovery_test.go | 527 ----- .../simulations/discovery/snapshot.json | 1 - swarm/network/simulations/overlay.go | 144 -- swarm/network/simulations/overlay_test.go | 195 -- swarm/network/stream/common_test.go | 373 --- swarm/network/stream/delivery.go | 282 --- swarm/network/stream/delivery_test.go | 734 ------ .../network/stream/intervals/dbstore_test.go | 42 - swarm/network/stream/intervals/intervals.go | 206 -- .../stream/intervals/intervals_test.go | 395 --- swarm/network/stream/intervals/store_test.go | 77 - swarm/network/stream/intervals_test.go | 362 --- swarm/network/stream/lightnode_test.go | 214 -- swarm/network/stream/messages.go | 405 ---- swarm/network/stream/norace_test.go | 24 - swarm/network/stream/peer.go | 430 ---- swarm/network/stream/race_test.go | 23 - .../network/stream/snapshot_retrieval_test.go | 311 --- swarm/network/stream/snapshot_sync_test.go | 344 --- swarm/network/stream/stream.go | 967 -------- swarm/network/stream/streamer_test.go | 1357 ----------- swarm/network/stream/syncer.go | 222 -- swarm/network/stream/syncer_test.go | 359 --- .../network/stream/testing/snapshot_128.json | 1 - swarm/network/stream/testing/snapshot_16.json | 1 - .../network/stream/testing/snapshot_256.json | 1 - swarm/network/stream/testing/snapshot_32.json | 1 - swarm/network/stream/testing/snapshot_4.json | 1 - swarm/network/stream/testing/snapshot_64.json | 1 - .../visualized_snapshot_sync_sim_test.go | 353 --- swarm/network_test.go | 502 ---- swarm/pot/address.go | 210 -- swarm/pot/doc.go | 83 - swarm/pot/pot.go | 787 ------ swarm/pot/pot_test.go | 741 ------ swarm/pss/ARCHITECTURE.md | 144 -- swarm/pss/README.md | 318 --- swarm/pss/api.go | 195 -- swarm/pss/client/client.go | 352 --- swarm/pss/client/client_test.go | 303 --- swarm/pss/client/doc.go | 96 - swarm/pss/doc.go | 61 - swarm/pss/forwarding_test.go | 357 --- swarm/pss/handshake.go | 566 ----- swarm/pss/handshake_none.go | 27 - swarm/pss/handshake_test.go | 267 --- swarm/pss/keystore.go | 281 --- swarm/pss/notify/notify.go | 394 --- swarm/pss/notify/notify_test.go | 257 -- swarm/pss/ping.go | 97 - swarm/pss/protocol.go | 283 --- swarm/pss/protocol_none.go | 23 - swarm/pss/protocol_test.go | 164 -- swarm/pss/pss.go | 840 ------- swarm/pss/pss_test.go | 2108 ----------------- .../testdata/addpsstodiscoverytestsnapshot.pl | 28 - .../testdata/addpsstodiscoverytestsnapshot.sh | 3 - swarm/pss/testdata/snapshot_128.json | 1 - swarm/pss/testdata/snapshot_16.json | 1 - swarm/pss/testdata/snapshot_2.json | 1 - swarm/pss/testdata/snapshot_256.json | 1 - swarm/pss/testdata/snapshot_3.json | 1 - swarm/pss/testdata/snapshot_32.json | 1 - swarm/pss/testdata/snapshot_4.json | 1 - swarm/pss/testdata/snapshot_64.json | 1 - swarm/pss/testdata/snapshot_8.json | 1 - swarm/pss/types.go | 217 -- swarm/pss/writeup.md | 125 - swarm/sctx/sctx.go | 20 - swarm/services/swap/swap.go | 301 --- swarm/services/swap/swap/swap.go | 252 -- swarm/services/swap/swap/swap_test.go | 194 -- swarm/shed/db.go | 329 --- swarm/shed/db_test.go | 110 - swarm/shed/example_store_test.go | 332 --- swarm/shed/field_string.go | 66 - swarm/shed/field_string_test.go | 110 - swarm/shed/field_struct.go | 71 - swarm/shed/field_struct_test.go | 127 - swarm/shed/field_uint64.go | 146 -- swarm/shed/field_uint64_test.go | 300 --- swarm/shed/index.go | 306 --- swarm/shed/index_test.go | 781 ------ swarm/shed/schema.go | 134 -- swarm/shed/schema_test.go | 126 - swarm/state/dbstore.go | 111 - swarm/state/dbstore_test.go | 125 - swarm/storage/chunker.go | 613 ----- swarm/storage/chunker_test.go | 467 ---- swarm/storage/common_test.go | 288 --- swarm/storage/database.go | 82 - swarm/storage/encryption/encryption.go | 152 -- swarm/storage/encryption/encryption_test.go | 151 -- swarm/storage/error.go | 37 - swarm/storage/feed/binaryserializer.go | 44 - swarm/storage/feed/binaryserializer_test.go | 98 - swarm/storage/feed/cacheentry.go | 48 - swarm/storage/feed/doc.go | 43 - swarm/storage/feed/error.go | 73 - swarm/storage/feed/feed.go | 125 - swarm/storage/feed/feed_test.go | 36 - swarm/storage/feed/handler.go | 291 --- swarm/storage/feed/handler_test.go | 506 ---- swarm/storage/feed/id.go | 123 - swarm/storage/feed/id_test.go | 28 - swarm/storage/feed/lookup/epoch.go | 91 - swarm/storage/feed/lookup/epoch_test.go | 57 - swarm/storage/feed/lookup/lookup.go | 180 -- swarm/storage/feed/lookup/lookup_test.go | 414 ---- swarm/storage/feed/query.go | 78 - swarm/storage/feed/query_test.go | 38 - swarm/storage/feed/request.go | 286 --- swarm/storage/feed/request_test.go | 312 --- swarm/storage/feed/sign.go | 75 - swarm/storage/feed/testutil.go | 71 - swarm/storage/feed/timestampprovider.go | 62 - swarm/storage/feed/topic.go | 105 - swarm/storage/feed/topic_test.go | 50 - swarm/storage/feed/update.go | 134 -- swarm/storage/feed/update_test.go | 50 - swarm/storage/filestore.go | 146 -- swarm/storage/filestore_test.go | 207 -- swarm/storage/hasherstore.go | 263 -- swarm/storage/hasherstore_test.go | 124 - swarm/storage/ldbstore.go | 1077 --------- swarm/storage/ldbstore_test.go | 778 ------ swarm/storage/localstore.go | 251 -- swarm/storage/localstore/doc.go | 56 - swarm/storage/localstore/gc.go | 302 --- swarm/storage/localstore/gc_test.go | 358 --- swarm/storage/localstore/index_test.go | 227 -- swarm/storage/localstore/localstore.go | 431 ---- swarm/storage/localstore/localstore_test.go | 520 ---- swarm/storage/localstore/mode_get.go | 154 -- swarm/storage/localstore/mode_get_test.go | 237 -- swarm/storage/localstore/mode_put.go | 160 -- swarm/storage/localstore/mode_put_test.go | 300 --- swarm/storage/localstore/mode_set.go | 205 -- swarm/storage/localstore/mode_set_test.go | 128 - .../localstore/retrieval_index_test.go | 150 -- swarm/storage/localstore/subscription_pull.go | 193 -- .../localstore/subscription_pull_test.go | 478 ---- swarm/storage/localstore/subscription_push.go | 145 -- .../localstore/subscription_push_test.go | 200 -- swarm/storage/localstore_test.go | 244 -- swarm/storage/memstore.go | 92 - swarm/storage/memstore_test.go | 158 -- swarm/storage/mock/db/db.go | 243 -- swarm/storage/mock/db/db_test.go | 75 - swarm/storage/mock/mem/mem.go | 191 -- swarm/storage/mock/mem/mem_test.go | 36 - swarm/storage/mock/mock.go | 111 - swarm/storage/mock/rpc/rpc.go | 90 - swarm/storage/mock/rpc/rpc_test.go | 41 - swarm/storage/mock/test/test.go | 244 -- swarm/storage/netstore.go | 322 --- swarm/storage/netstore_test.go | 692 ------ swarm/storage/pyramid.go | 694 ------ swarm/storage/schema.go | 17 - swarm/storage/swarmhasher.go | 41 - swarm/storage/types.go | 330 --- swarm/storage/types_test.go | 186 -- swarm/swap/swap.go | 98 - swarm/swap/swap_test.go | 184 -- swarm/swarm.go | 554 ----- swarm/swarm_test.go | 375 --- swarm/testutil/file.go | 65 - swarm/version/version.go | 67 - 317 files changed, 9 insertions(+), 73081 deletions(-) delete mode 100644 cmd/swarm/access.go delete mode 100644 cmd/swarm/access_test.go delete mode 100644 cmd/swarm/bootnodes.go delete mode 100644 cmd/swarm/config.go delete mode 100644 cmd/swarm/config_test.go delete mode 100644 cmd/swarm/db.go delete mode 100644 cmd/swarm/download.go delete mode 100644 cmd/swarm/explore.go delete mode 100644 cmd/swarm/export_test.go delete mode 100644 cmd/swarm/feeds.go delete mode 100644 cmd/swarm/feeds_test.go delete mode 100644 cmd/swarm/flags.go delete mode 100644 cmd/swarm/fs.go delete mode 100644 cmd/swarm/fs_test.go delete mode 100644 cmd/swarm/global-store/global_store.go delete mode 100644 cmd/swarm/global-store/global_store_test.go delete mode 100644 cmd/swarm/global-store/main.go delete mode 100644 cmd/swarm/global-store/run_test.go delete mode 100644 cmd/swarm/hash.go delete mode 100644 cmd/swarm/list.go delete mode 100644 cmd/swarm/main.go delete mode 100644 cmd/swarm/manifest.go delete mode 100644 cmd/swarm/manifest_test.go delete mode 100644 cmd/swarm/mimegen/generator.go delete mode 100644 cmd/swarm/mimegen/mime.types delete mode 100644 cmd/swarm/run_test.go delete mode 100644 cmd/swarm/swarm-smoke/feed_upload_and_sync.go delete mode 100644 cmd/swarm/swarm-smoke/main.go delete mode 100644 cmd/swarm/swarm-smoke/sliding_window.go delete mode 100644 cmd/swarm/swarm-smoke/upload_and_sync.go delete mode 100644 cmd/swarm/swarm-smoke/upload_speed.go delete mode 100644 cmd/swarm/swarm-smoke/util.go delete mode 100644 cmd/swarm/swarm-snapshot/create.go delete mode 100644 cmd/swarm/swarm-snapshot/create_test.go delete mode 100644 cmd/swarm/swarm-snapshot/main.go delete mode 100644 cmd/swarm/swarm-snapshot/run_test.go delete mode 100644 cmd/swarm/upload.go delete mode 100644 cmd/swarm/upload_test.go delete mode 100644 contracts/chequebook/api.go delete mode 100644 contracts/chequebook/cheque.go delete mode 100644 contracts/chequebook/cheque_test.go delete mode 100644 contracts/chequebook/contract/chequebook.go delete mode 100644 contracts/chequebook/contract/chequebook.sol delete mode 100644 contracts/chequebook/contract/code.go delete mode 100644 contracts/chequebook/contract/mortal.sol delete mode 100644 contracts/chequebook/contract/owned.sol delete mode 100644 contracts/chequebook/gencode.go rename {swarm => p2p}/spancontext/spancontext.go (100%) rename {swarm => p2p}/tracing/tracing.go (100%) delete mode 100644 swarm/AUTHORS delete mode 100644 swarm/OWNERS delete mode 100644 swarm/README.md delete mode 100644 swarm/api/act.go delete mode 100644 swarm/api/api.go delete mode 100644 swarm/api/api_test.go delete mode 100644 swarm/api/client/client.go delete mode 100644 swarm/api/client/client_test.go delete mode 100644 swarm/api/config.go delete mode 100644 swarm/api/config_test.go delete mode 100644 swarm/api/encrypt.go delete mode 100644 swarm/api/filesystem.go delete mode 100644 swarm/api/filesystem_test.go delete mode 100644 swarm/api/gen_mime.go delete mode 100644 swarm/api/http/middleware.go delete mode 100644 swarm/api/http/response.go delete mode 100644 swarm/api/http/response_test.go delete mode 100644 swarm/api/http/roundtripper.go delete mode 100644 swarm/api/http/roundtripper_test.go delete mode 100644 swarm/api/http/sctx.go delete mode 100644 swarm/api/http/server.go delete mode 100644 swarm/api/http/server_test.go delete mode 100644 swarm/api/http/templates.go delete mode 100644 swarm/api/http/test_server.go delete mode 100644 swarm/api/inspector.go delete mode 100644 swarm/api/manifest.go delete mode 100644 swarm/api/manifest_test.go delete mode 100644 swarm/api/storage.go delete mode 100644 swarm/api/storage_test.go delete mode 100644 swarm/api/testdata/test0/img/logo.png delete mode 100644 swarm/api/testdata/test0/index.css delete mode 100644 swarm/api/testdata/test0/index.html delete mode 100644 swarm/api/uri.go delete mode 100644 swarm/api/uri_test.go delete mode 100644 swarm/bmt/bmt.go delete mode 100644 swarm/bmt/bmt_r.go delete mode 100644 swarm/bmt/bmt_test.go delete mode 100644 swarm/chunk/chunk.go delete mode 100644 swarm/dev/.dockerignore delete mode 100644 swarm/dev/.gitignore delete mode 100644 swarm/dev/Dockerfile delete mode 100644 swarm/dev/Makefile delete mode 100644 swarm/dev/README.md delete mode 100644 swarm/dev/bashrc delete mode 100755 swarm/dev/run.sh delete mode 100755 swarm/dev/scripts/boot-cluster.sh delete mode 100755 swarm/dev/scripts/random-uploads.sh delete mode 100755 swarm/dev/scripts/stop-cluster.sh delete mode 100644 swarm/dev/scripts/util.sh delete mode 100644 swarm/docker/Dockerfile delete mode 100755 swarm/docker/run-smoke.sh delete mode 100755 swarm/docker/run.sh delete mode 100644 swarm/fuse/fuse_dir.go delete mode 100644 swarm/fuse/fuse_file.go delete mode 100644 swarm/fuse/fuse_root.go delete mode 100644 swarm/fuse/swarmfs.go delete mode 100644 swarm/fuse/swarmfs_fallback.go delete mode 100644 swarm/fuse/swarmfs_test.go delete mode 100644 swarm/fuse/swarmfs_unix.go delete mode 100644 swarm/fuse/swarmfs_util.go delete mode 100644 swarm/log/log.go delete mode 100644 swarm/metrics/flags.go delete mode 100644 swarm/network/README.md delete mode 100644 swarm/network/bitvector/bitvector.go delete mode 100644 swarm/network/bitvector/bitvector_test.go delete mode 100644 swarm/network/common.go delete mode 100644 swarm/network/discovery.go delete mode 100644 swarm/network/discovery_test.go delete mode 100644 swarm/network/fetcher.go delete mode 100644 swarm/network/fetcher_test.go delete mode 100644 swarm/network/hive.go delete mode 100644 swarm/network/hive_test.go delete mode 100644 swarm/network/kademlia.go delete mode 100644 swarm/network/kademlia_test.go delete mode 100644 swarm/network/networkid_test.go delete mode 100644 swarm/network/priorityqueue/priorityqueue.go delete mode 100644 swarm/network/priorityqueue/priorityqueue_test.go delete mode 100644 swarm/network/protocol.go delete mode 100644 swarm/network/protocol_test.go delete mode 100644 swarm/network/simulation/bucket.go delete mode 100644 swarm/network/simulation/bucket_test.go delete mode 100644 swarm/network/simulation/events.go delete mode 100644 swarm/network/simulation/events_test.go delete mode 100644 swarm/network/simulation/example_test.go delete mode 100644 swarm/network/simulation/http.go delete mode 100644 swarm/network/simulation/http_test.go delete mode 100644 swarm/network/simulation/kademlia.go delete mode 100644 swarm/network/simulation/kademlia_test.go delete mode 100644 swarm/network/simulation/node.go delete mode 100644 swarm/network/simulation/node_test.go delete mode 100644 swarm/network/simulation/service.go delete mode 100644 swarm/network/simulation/service_test.go delete mode 100644 swarm/network/simulation/simulation.go delete mode 100644 swarm/network/simulation/simulation_test.go delete mode 100644 swarm/network/simulations/discovery/discovery.go delete mode 100644 swarm/network/simulations/discovery/discovery_test.go delete mode 100755 swarm/network/simulations/discovery/snapshot.json delete mode 100644 swarm/network/simulations/overlay.go delete mode 100644 swarm/network/simulations/overlay_test.go delete mode 100644 swarm/network/stream/common_test.go delete mode 100644 swarm/network/stream/delivery.go delete mode 100644 swarm/network/stream/delivery_test.go delete mode 100644 swarm/network/stream/intervals/dbstore_test.go delete mode 100644 swarm/network/stream/intervals/intervals.go delete mode 100644 swarm/network/stream/intervals/intervals_test.go delete mode 100644 swarm/network/stream/intervals/store_test.go delete mode 100644 swarm/network/stream/intervals_test.go delete mode 100644 swarm/network/stream/lightnode_test.go delete mode 100644 swarm/network/stream/messages.go delete mode 100644 swarm/network/stream/norace_test.go delete mode 100644 swarm/network/stream/peer.go delete mode 100644 swarm/network/stream/race_test.go delete mode 100644 swarm/network/stream/snapshot_retrieval_test.go delete mode 100644 swarm/network/stream/snapshot_sync_test.go delete mode 100644 swarm/network/stream/stream.go delete mode 100644 swarm/network/stream/streamer_test.go delete mode 100644 swarm/network/stream/syncer.go delete mode 100644 swarm/network/stream/syncer_test.go delete mode 100644 swarm/network/stream/testing/snapshot_128.json delete mode 100644 swarm/network/stream/testing/snapshot_16.json delete mode 100644 swarm/network/stream/testing/snapshot_256.json delete mode 100644 swarm/network/stream/testing/snapshot_32.json delete mode 100644 swarm/network/stream/testing/snapshot_4.json delete mode 100644 swarm/network/stream/testing/snapshot_64.json delete mode 100644 swarm/network/stream/visualized_snapshot_sync_sim_test.go delete mode 100644 swarm/network_test.go delete mode 100644 swarm/pot/address.go delete mode 100644 swarm/pot/doc.go delete mode 100644 swarm/pot/pot.go delete mode 100644 swarm/pot/pot_test.go delete mode 100644 swarm/pss/ARCHITECTURE.md delete mode 100644 swarm/pss/README.md delete mode 100644 swarm/pss/api.go delete mode 100644 swarm/pss/client/client.go delete mode 100644 swarm/pss/client/client_test.go delete mode 100644 swarm/pss/client/doc.go delete mode 100644 swarm/pss/doc.go delete mode 100644 swarm/pss/forwarding_test.go delete mode 100644 swarm/pss/handshake.go delete mode 100644 swarm/pss/handshake_none.go delete mode 100644 swarm/pss/handshake_test.go delete mode 100644 swarm/pss/keystore.go delete mode 100644 swarm/pss/notify/notify.go delete mode 100644 swarm/pss/notify/notify_test.go delete mode 100644 swarm/pss/ping.go delete mode 100644 swarm/pss/protocol.go delete mode 100644 swarm/pss/protocol_none.go delete mode 100644 swarm/pss/protocol_test.go delete mode 100644 swarm/pss/pss.go delete mode 100644 swarm/pss/pss_test.go delete mode 100644 swarm/pss/testdata/addpsstodiscoverytestsnapshot.pl delete mode 100644 swarm/pss/testdata/addpsstodiscoverytestsnapshot.sh delete mode 100644 swarm/pss/testdata/snapshot_128.json delete mode 100644 swarm/pss/testdata/snapshot_16.json delete mode 100644 swarm/pss/testdata/snapshot_2.json delete mode 100644 swarm/pss/testdata/snapshot_256.json delete mode 100644 swarm/pss/testdata/snapshot_3.json delete mode 100644 swarm/pss/testdata/snapshot_32.json delete mode 100644 swarm/pss/testdata/snapshot_4.json delete mode 100644 swarm/pss/testdata/snapshot_64.json delete mode 100644 swarm/pss/testdata/snapshot_8.json delete mode 100644 swarm/pss/types.go delete mode 100644 swarm/pss/writeup.md delete mode 100644 swarm/sctx/sctx.go delete mode 100644 swarm/services/swap/swap.go delete mode 100644 swarm/services/swap/swap/swap.go delete mode 100644 swarm/services/swap/swap/swap_test.go delete mode 100644 swarm/shed/db.go delete mode 100644 swarm/shed/db_test.go delete mode 100644 swarm/shed/example_store_test.go delete mode 100644 swarm/shed/field_string.go delete mode 100644 swarm/shed/field_string_test.go delete mode 100644 swarm/shed/field_struct.go delete mode 100644 swarm/shed/field_struct_test.go delete mode 100644 swarm/shed/field_uint64.go delete mode 100644 swarm/shed/field_uint64_test.go delete mode 100644 swarm/shed/index.go delete mode 100644 swarm/shed/index_test.go delete mode 100644 swarm/shed/schema.go delete mode 100644 swarm/shed/schema_test.go delete mode 100644 swarm/state/dbstore.go delete mode 100644 swarm/state/dbstore_test.go delete mode 100644 swarm/storage/chunker.go delete mode 100644 swarm/storage/chunker_test.go delete mode 100644 swarm/storage/common_test.go delete mode 100644 swarm/storage/database.go delete mode 100644 swarm/storage/encryption/encryption.go delete mode 100644 swarm/storage/encryption/encryption_test.go delete mode 100644 swarm/storage/error.go delete mode 100644 swarm/storage/feed/binaryserializer.go delete mode 100644 swarm/storage/feed/binaryserializer_test.go delete mode 100644 swarm/storage/feed/cacheentry.go delete mode 100644 swarm/storage/feed/doc.go delete mode 100644 swarm/storage/feed/error.go delete mode 100644 swarm/storage/feed/feed.go delete mode 100644 swarm/storage/feed/feed_test.go delete mode 100644 swarm/storage/feed/handler.go delete mode 100644 swarm/storage/feed/handler_test.go delete mode 100644 swarm/storage/feed/id.go delete mode 100644 swarm/storage/feed/id_test.go delete mode 100644 swarm/storage/feed/lookup/epoch.go delete mode 100644 swarm/storage/feed/lookup/epoch_test.go delete mode 100644 swarm/storage/feed/lookup/lookup.go delete mode 100644 swarm/storage/feed/lookup/lookup_test.go delete mode 100644 swarm/storage/feed/query.go delete mode 100644 swarm/storage/feed/query_test.go delete mode 100644 swarm/storage/feed/request.go delete mode 100644 swarm/storage/feed/request_test.go delete mode 100644 swarm/storage/feed/sign.go delete mode 100644 swarm/storage/feed/testutil.go delete mode 100644 swarm/storage/feed/timestampprovider.go delete mode 100644 swarm/storage/feed/topic.go delete mode 100644 swarm/storage/feed/topic_test.go delete mode 100644 swarm/storage/feed/update.go delete mode 100644 swarm/storage/feed/update_test.go delete mode 100644 swarm/storage/filestore.go delete mode 100644 swarm/storage/filestore_test.go delete mode 100644 swarm/storage/hasherstore.go delete mode 100644 swarm/storage/hasherstore_test.go delete mode 100644 swarm/storage/ldbstore.go delete mode 100644 swarm/storage/ldbstore_test.go delete mode 100644 swarm/storage/localstore.go delete mode 100644 swarm/storage/localstore/doc.go delete mode 100644 swarm/storage/localstore/gc.go delete mode 100644 swarm/storage/localstore/gc_test.go delete mode 100644 swarm/storage/localstore/index_test.go delete mode 100644 swarm/storage/localstore/localstore.go delete mode 100644 swarm/storage/localstore/localstore_test.go delete mode 100644 swarm/storage/localstore/mode_get.go delete mode 100644 swarm/storage/localstore/mode_get_test.go delete mode 100644 swarm/storage/localstore/mode_put.go delete mode 100644 swarm/storage/localstore/mode_put_test.go delete mode 100644 swarm/storage/localstore/mode_set.go delete mode 100644 swarm/storage/localstore/mode_set_test.go delete mode 100644 swarm/storage/localstore/retrieval_index_test.go delete mode 100644 swarm/storage/localstore/subscription_pull.go delete mode 100644 swarm/storage/localstore/subscription_pull_test.go delete mode 100644 swarm/storage/localstore/subscription_push.go delete mode 100644 swarm/storage/localstore/subscription_push_test.go delete mode 100644 swarm/storage/localstore_test.go delete mode 100644 swarm/storage/memstore.go delete mode 100644 swarm/storage/memstore_test.go delete mode 100644 swarm/storage/mock/db/db.go delete mode 100644 swarm/storage/mock/db/db_test.go delete mode 100644 swarm/storage/mock/mem/mem.go delete mode 100644 swarm/storage/mock/mem/mem_test.go delete mode 100644 swarm/storage/mock/mock.go delete mode 100644 swarm/storage/mock/rpc/rpc.go delete mode 100644 swarm/storage/mock/rpc/rpc_test.go delete mode 100644 swarm/storage/mock/test/test.go delete mode 100644 swarm/storage/netstore.go delete mode 100644 swarm/storage/netstore_test.go delete mode 100644 swarm/storage/pyramid.go delete mode 100644 swarm/storage/schema.go delete mode 100644 swarm/storage/swarmhasher.go delete mode 100644 swarm/storage/types.go delete mode 100644 swarm/storage/types_test.go delete mode 100644 swarm/swap/swap.go delete mode 100644 swarm/swap/swap_test.go delete mode 100644 swarm/swarm.go delete mode 100644 swarm/swarm_test.go delete mode 100644 swarm/testutil/file.go delete mode 100644 swarm/version/version.go diff --git a/Makefile b/Makefile index 3f6f10d47bc3..ef39e19a996d 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ # with Go source code. If you know what GOPATH is then you probably # don't need to bother with make. -.PHONY: gubiq android ios gubiq-cross swarm evm all test clean +.PHONY: gubiq android ios gubiq-cross evm all test clean .PHONY: gubiq-linux gubiq-linux-386 gubiq-linux-amd64 gubiq-linux-mips64 gubiq-linux-mips64le .PHONY: gubiq-linux-arm gubiq-linux-arm-5 gubiq-linux-arm-6 gubiq-linux-arm-7 gubiq-linux-arm64 .PHONY: gubiq-darwin gubiq-darwin-386 gubiq-darwin-amd64 @@ -16,11 +16,6 @@ gubiq: @echo "Done building." @echo "Run \"$(GOBIN)/gubiq\" to launch gubiq." -swarm: - build/env.sh go run build/ci.go install ./cmd/swarm - @echo "Done building." - @echo "Run \"$(GOBIN)/swarm\" to launch swarm." - all: build/env.sh go run build/ci.go install @@ -57,9 +52,6 @@ devtools: @type "solc" 2> /dev/null || echo 'Please install solc' @type "protoc" 2> /dev/null || echo 'Please install protoc' -swarm-devtools: - env GOBIN= go install ./cmd/swarm/mimegen - # Cross Compilation Targets (xgo) gubiq-cross: gubiq-linux gubiq-darwin gubiq-windows gubiq-android gubiq-ios diff --git a/build/ci.go b/build/ci.go index bbda28227d22..ab7009c5ea2d 100644 --- a/build/ci.go +++ b/build/ci.go @@ -60,7 +60,6 @@ import ( "github.com/ubiq/go-ubiq/internal/build" "github.com/ubiq/go-ubiq/params" - sv "github.com/ubiq/go-ubiq/swarm/version" ) var ( @@ -82,12 +81,6 @@ var ( executablePath("wnode"), } - // Files that end up in the swarm*.zip archive. - swarmArchiveFiles = []string{ - "COPYING", - executablePath("swarm"), - } - // A debian package is created for all executables listed here. debExecutables = []debExecutable{ { @@ -116,16 +109,7 @@ var ( }, { BinaryName: "wnode", - Description: "Ethereum Whisper diagnostic tool", - }, - } - - // A debian package is created for all executables listed here. - debSwarmExecutables = []debExecutable{ - { - BinaryName: "swarm", - PackageName: "ethereum-swarm", - Description: "Ethereum Swarm daemon and tools", + Description: "Ubiq Whisper diagnostic tool", }, } @@ -135,21 +119,11 @@ var ( Executables: debExecutables, } - debSwarm = debPackage{ - Name: "ethereum-swarm", - Version: sv.Version, - Executables: debSwarmExecutables, - } - // Debian meta packages to build and push to Ubuntu PPA debPackages = []debPackage{ - debSwarm, debEthereum, } - // Packages to be cross-compiled by the xgo command - allCrossCompiledArchiveFiles = append(allToolsArchiveFiles, swarmArchiveFiles...) - // Distros for which packages are created. // Note: vivid is unsupported because there is no golang-1.6 package for it. // Note: wily is unsupported because it was officially deprecated on lanchpad. @@ -402,10 +376,7 @@ func doArchive(cmdline []string) { basegubiq = archiveBasename(*arch, params.ArchiveVersion(env.Commit)) gubiq = "gubiq-" + basegubiq + ext - alltools = "gubiq-alltools-" + basegubiq + ext - - baseswarm = archiveBasename(*arch, sv.ArchiveVersion(env.Commit)) - swarm = "swarm-" + baseswarm + ext + alltools = "gubiq-alltools-" + basegubiq + ext ) maybeSkipArchive(env) if err := build.WriteArchive(gubiq, gubiqArchiveFiles); err != nil { @@ -414,10 +385,7 @@ func doArchive(cmdline []string) { if err := build.WriteArchive(alltools, allToolsArchiveFiles); err != nil { log.Fatal(err) } - if err := build.WriteArchive(swarm, swarmArchiveFiles); err != nil { - log.Fatal(err) - } - for _, archive := range []string{gubiq, alltools, swarm} { + for _, archive := range []string{gubiq, alltools} { if err := archiveUpload(archive, *upload, *signer); err != nil { log.Fatal(err) } @@ -1021,7 +989,7 @@ func doXgo(cmdline []string) { if *alltools { args = append(args, []string{"--dest", GOBIN}...) - for _, res := range allCrossCompiledArchiveFiles { + for _, res := range allToolsArchiveFiles { if strings.HasPrefix(res, GOBIN) { // Binary tool found, cross build it explicitly args = append(args, "./"+filepath.Join("cmd", filepath.Base(res))) diff --git a/build/update-license.go b/build/update-license.go index 323b17dbbd2d..0d4512648829 100644 --- a/build/update-license.go +++ b/build/update-license.go @@ -72,8 +72,6 @@ var ( "internal/jsre/deps", "log/", "common/bitutil/bitutil", - // don't license generated files - "contracts/chequebook/contract/code.go", } // paths with this prefix are licensed as GPL. all other files are LGPL. diff --git a/cmd/swarm/access.go b/cmd/swarm/access.go deleted file mode 100644 index 6c0f75b2d371..000000000000 --- a/cmd/swarm/access.go +++ /dev/null @@ -1,297 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . -package main - -import ( - "crypto/rand" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "strings" - - "github.com/ubiq/go-ubiq/cmd/utils" - "github.com/ubiq/go-ubiq/swarm/api" - "github.com/ubiq/go-ubiq/swarm/api/client" - "gopkg.in/urfave/cli.v1" -) - -var ( - salt = make([]byte, 32) - accessCommand = cli.Command{ - CustomHelpTemplate: helpTemplate, - Name: "access", - Usage: "encrypts a reference and embeds it into a root manifest", - ArgsUsage: "", - Description: "encrypts a reference and embeds it into a root manifest", - Subcommands: []cli.Command{ - { - CustomHelpTemplate: helpTemplate, - Name: "new", - Usage: "encrypts a reference and embeds it into a root manifest", - ArgsUsage: "", - Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest", - Subcommands: []cli.Command{ - { - Action: accessNewPass, - CustomHelpTemplate: helpTemplate, - Flags: []cli.Flag{ - utils.PasswordFileFlag, - SwarmDryRunFlag, - }, - Name: "pass", - Usage: "encrypts a reference with a password and embeds it into a root manifest", - ArgsUsage: "", - Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest", - }, - { - Action: accessNewPK, - CustomHelpTemplate: helpTemplate, - Flags: []cli.Flag{ - utils.PasswordFileFlag, - SwarmDryRunFlag, - SwarmAccessGrantKeyFlag, - }, - Name: "pk", - Usage: "encrypts a reference with the node's private key and a given grantee's public key and embeds it into a root manifest", - ArgsUsage: "", - Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest", - }, - { - Action: accessNewACT, - CustomHelpTemplate: helpTemplate, - Flags: []cli.Flag{ - SwarmAccessGrantKeysFlag, - SwarmDryRunFlag, - utils.PasswordFileFlag, - }, - Name: "act", - Usage: "encrypts a reference with the node's private key and a given grantee's public key and embeds it into a root manifest", - ArgsUsage: "", - Description: "encrypts a reference and embeds it into a root access manifest and prints the resulting manifest", - }, - }, - }, - }, - } -) - -func init() { - if _, err := io.ReadFull(rand.Reader, salt); err != nil { - panic("reading from crypto/rand failed: " + err.Error()) - } -} - -func accessNewPass(ctx *cli.Context) { - args := ctx.Args() - if len(args) != 1 { - utils.Fatalf("Expected 1 argument - the ref") - } - - var ( - ae *api.AccessEntry - accessKey []byte - err error - ref = args[0] - password = getPassPhrase("", 0, makePasswordList(ctx)) - dryRun = ctx.Bool(SwarmDryRunFlag.Name) - ) - accessKey, ae, err = api.DoPassword(ctx, password, salt) - if err != nil { - utils.Fatalf("error getting session key: %v", err) - } - m, err := api.GenerateAccessControlManifest(ctx, ref, accessKey, ae) - if err != nil { - utils.Fatalf("had an error generating the manifest: %v", err) - } - if dryRun { - err = printManifests(m, nil) - if err != nil { - utils.Fatalf("had an error printing the manifests: %v", err) - } - } else { - err = uploadManifests(ctx, m, nil) - if err != nil { - utils.Fatalf("had an error uploading the manifests: %v", err) - } - } -} - -func accessNewPK(ctx *cli.Context) { - args := ctx.Args() - if len(args) != 1 { - utils.Fatalf("Expected 1 argument - the ref") - } - - var ( - ae *api.AccessEntry - sessionKey []byte - err error - ref = args[0] - privateKey = getPrivKey(ctx) - granteePublicKey = ctx.String(SwarmAccessGrantKeyFlag.Name) - dryRun = ctx.Bool(SwarmDryRunFlag.Name) - ) - sessionKey, ae, err = api.DoPK(ctx, privateKey, granteePublicKey, salt) - if err != nil { - utils.Fatalf("error getting session key: %v", err) - } - m, err := api.GenerateAccessControlManifest(ctx, ref, sessionKey, ae) - if err != nil { - utils.Fatalf("had an error generating the manifest: %v", err) - } - if dryRun { - err = printManifests(m, nil) - if err != nil { - utils.Fatalf("had an error printing the manifests: %v", err) - } - } else { - err = uploadManifests(ctx, m, nil) - if err != nil { - utils.Fatalf("had an error uploading the manifests: %v", err) - } - } -} - -func accessNewACT(ctx *cli.Context) { - args := ctx.Args() - if len(args) != 1 { - utils.Fatalf("Expected 1 argument - the ref") - } - - var ( - ae *api.AccessEntry - actManifest *api.Manifest - accessKey []byte - err error - ref = args[0] - pkGrantees = []string{} - passGrantees = []string{} - pkGranteesFilename = ctx.String(SwarmAccessGrantKeysFlag.Name) - passGranteesFilename = ctx.String(utils.PasswordFileFlag.Name) - privateKey = getPrivKey(ctx) - dryRun = ctx.Bool(SwarmDryRunFlag.Name) - ) - if pkGranteesFilename == "" && passGranteesFilename == "" { - utils.Fatalf("you have to provide either a grantee public-keys file or an encryption passwords file (or both)") - } - - if pkGranteesFilename != "" { - bytes, err := ioutil.ReadFile(pkGranteesFilename) - if err != nil { - utils.Fatalf("had an error reading the grantee public key list") - } - pkGrantees = strings.Split(strings.Trim(string(bytes), "\n"), "\n") - } - - if passGranteesFilename != "" { - bytes, err := ioutil.ReadFile(passGranteesFilename) - if err != nil { - utils.Fatalf("could not read password filename: %v", err) - } - passGrantees = strings.Split(strings.Trim(string(bytes), "\n"), "\n") - } - accessKey, ae, actManifest, err = api.DoACT(ctx, privateKey, salt, pkGrantees, passGrantees) - if err != nil { - utils.Fatalf("error generating ACT manifest: %v", err) - } - - if err != nil { - utils.Fatalf("error getting session key: %v", err) - } - m, err := api.GenerateAccessControlManifest(ctx, ref, accessKey, ae) - if err != nil { - utils.Fatalf("error generating root access manifest: %v", err) - } - - if dryRun { - err = printManifests(m, actManifest) - if err != nil { - utils.Fatalf("had an error printing the manifests: %v", err) - } - } else { - err = uploadManifests(ctx, m, actManifest) - if err != nil { - utils.Fatalf("had an error uploading the manifests: %v", err) - } - } -} - -func printManifests(rootAccessManifest, actManifest *api.Manifest) error { - js, err := json.Marshal(rootAccessManifest) - if err != nil { - return err - } - fmt.Println(string(js)) - - if actManifest != nil { - js, err := json.Marshal(actManifest) - if err != nil { - return err - } - fmt.Println(string(js)) - } - return nil -} - -func uploadManifests(ctx *cli.Context, rootAccessManifest, actManifest *api.Manifest) error { - bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/") - client := client.NewClient(bzzapi) - - var ( - key string - err error - ) - if actManifest != nil { - key, err = client.UploadManifest(actManifest, false) - if err != nil { - return err - } - - rootAccessManifest.Entries[0].Access.Act = key - } - key, err = client.UploadManifest(rootAccessManifest, false) - if err != nil { - return err - } - fmt.Println(key) - return nil -} - -// makePasswordList reads password lines from the file specified by the global --password flag -// and also by the same subcommand --password flag. -// This function ia a fork of utils.MakePasswordList to lookup cli context for subcommand. -// Function ctx.SetGlobal is not setting the global flag value that can be accessed -// by ctx.GlobalString using the current version of cli package. -func makePasswordList(ctx *cli.Context) []string { - path := ctx.GlobalString(utils.PasswordFileFlag.Name) - if path == "" { - path = ctx.String(utils.PasswordFileFlag.Name) - if path == "" { - return nil - } - } - text, err := ioutil.ReadFile(path) - if err != nil { - utils.Fatalf("Failed to read password file: %v", err) - } - lines := strings.Split(string(text), "\n") - // Sanitise DOS line endings. - for i := range lines { - lines[i] = strings.TrimRight(lines[i], "\r") - } - return lines -} diff --git a/cmd/swarm/access_test.go b/cmd/swarm/access_test.go deleted file mode 100644 index a972aacc3e92..000000000000 --- a/cmd/swarm/access_test.go +++ /dev/null @@ -1,614 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "bytes" - "crypto/rand" - "encoding/hex" - "encoding/json" - "io" - "io/ioutil" - gorand "math/rand" - "net/http" - "os" - "runtime" - "strings" - "testing" - "time" - - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/crypto/ecies" - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/swarm/api" - swarmapi "github.com/ubiq/go-ubiq/swarm/api/client" - "github.com/ubiq/go-ubiq/swarm/testutil" - "golang.org/x/crypto/sha3" -) - -const ( - hashRegexp = `[a-f\d]{128}` - data = "notsorandomdata" -) - -var DefaultCurve = crypto.S256() - -func TestACT(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip() - } - - initCluster(t) - - cases := []struct { - name string - f func(t *testing.T) - }{ - {"Password", testPassword}, - {"PK", testPK}, - {"ACTWithoutBogus", testACTWithoutBogus}, - {"ACTWithBogus", testACTWithBogus}, - } - - for _, tc := range cases { - t.Run(tc.name, tc.f) - } -} - -// testPassword tests for the correct creation of an ACT manifest protected by a password. -// The test creates bogus content, uploads it encrypted, then creates the wrapping manifest with the Access entry -// The parties participating - node (publisher), uploads to second node then disappears. Content which was uploaded -// is then fetched through 2nd node. since the tested code is not key-aware - we can just -// fetch from the 2nd node using HTTP BasicAuth -func testPassword(t *testing.T) { - dataFilename := testutil.TempFileWithContent(t, data) - defer os.RemoveAll(dataFilename) - - // upload the file with 'swarm up' and expect a hash - up := runSwarm(t, - "--bzzapi", - cluster.Nodes[0].URL, - "up", - "--encrypt", - dataFilename) - _, matches := up.ExpectRegexp(hashRegexp) - up.ExpectExit() - - if len(matches) < 1 { - t.Fatal("no matches found") - } - - ref := matches[0] - tmp, err := ioutil.TempDir("", "swarm-test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmp) - password := "smth" - passwordFilename := testutil.TempFileWithContent(t, "smth") - defer os.RemoveAll(passwordFilename) - - up = runSwarm(t, - "access", - "new", - "pass", - "--dry-run", - "--password", - passwordFilename, - ref, - ) - - _, matches = up.ExpectRegexp(".+") - up.ExpectExit() - - if len(matches) == 0 { - t.Fatalf("stdout not matched") - } - - var m api.Manifest - - err = json.Unmarshal([]byte(matches[0]), &m) - if err != nil { - t.Fatalf("unmarshal manifest: %v", err) - } - - if len(m.Entries) != 1 { - t.Fatalf("expected one manifest entry, got %v", len(m.Entries)) - } - - e := m.Entries[0] - - ct := "application/bzz-manifest+json" - if e.ContentType != ct { - t.Errorf("expected %q content type, got %q", ct, e.ContentType) - } - - if e.Access == nil { - t.Fatal("manifest access is nil") - } - - a := e.Access - - if a.Type != "pass" { - t.Errorf(`got access type %q, expected "pass"`, a.Type) - } - if len(a.Salt) < 32 { - t.Errorf(`got salt with length %v, expected not less the 32 bytes`, len(a.Salt)) - } - if a.KdfParams == nil { - t.Fatal("manifest access kdf params is nil") - } - if a.Publisher != "" { - t.Fatal("should be empty") - } - - client := swarmapi.NewClient(cluster.Nodes[0].URL) - - hash, err := client.UploadManifest(&m, false) - if err != nil { - t.Fatal(err) - } - - url := cluster.Nodes[0].URL + "/" + "bzz:/" + hash - - httpClient := &http.Client{} - response, err := httpClient.Get(url) - if err != nil { - t.Fatal(err) - } - if response.StatusCode != http.StatusUnauthorized { - t.Fatal("should be a 401") - } - authHeader := response.Header.Get("WWW-Authenticate") - if authHeader == "" { - t.Fatal("should be something here") - } - - req, err := http.NewRequest(http.MethodGet, url, nil) - if err != nil { - t.Fatal(err) - } - req.SetBasicAuth("", password) - - response, err = http.DefaultClient.Do(req) - if err != nil { - t.Fatal(err) - } - defer response.Body.Close() - - if response.StatusCode != http.StatusOK { - t.Errorf("expected status %v, got %v", http.StatusOK, response.StatusCode) - } - d, err := ioutil.ReadAll(response.Body) - if err != nil { - t.Fatal(err) - } - if string(d) != data { - t.Errorf("expected decrypted data %q, got %q", data, string(d)) - } - - wrongPasswordFilename := testutil.TempFileWithContent(t, "just wr0ng") - defer os.RemoveAll(wrongPasswordFilename) - - //download file with 'swarm down' with wrong password - up = runSwarm(t, - "--bzzapi", - cluster.Nodes[0].URL, - "down", - "bzz:/"+hash, - tmp, - "--password", - wrongPasswordFilename) - - _, matches = up.ExpectRegexp("unauthorized") - if len(matches) != 1 && matches[0] != "unauthorized" { - t.Fatal(`"unauthorized" not found in output"`) - } - up.ExpectExit() -} - -// testPK tests for the correct creation of an ACT manifest between two parties (publisher and grantee). -// The test creates bogus content, uploads it encrypted, then creates the wrapping manifest with the Access entry -// The parties participating - node (publisher), uploads to second node (which is also the grantee) then disappears. -// Content which was uploaded is then fetched through the grantee's http proxy. Since the tested code is private-key aware, -// the test will fail if the proxy's given private key is not granted on the ACT. -func testPK(t *testing.T) { - dataFilename := testutil.TempFileWithContent(t, data) - defer os.RemoveAll(dataFilename) - - // upload the file with 'swarm up' and expect a hash - up := runSwarm(t, - "--bzzapi", - cluster.Nodes[0].URL, - "up", - "--encrypt", - dataFilename) - _, matches := up.ExpectRegexp(hashRegexp) - up.ExpectExit() - - if len(matches) < 1 { - t.Fatal("no matches found") - } - - ref := matches[0] - pk := cluster.Nodes[0].PrivateKey - granteePubKey := crypto.CompressPubkey(&pk.PublicKey) - - publisherDir, err := ioutil.TempDir("", "swarm-account-dir-temp") - if err != nil { - t.Fatal(err) - } - - passwordFilename := testutil.TempFileWithContent(t, testPassphrase) - defer os.RemoveAll(passwordFilename) - - _, publisherAccount := getTestAccount(t, publisherDir) - up = runSwarm(t, - "--bzzaccount", - publisherAccount.Address.String(), - "--password", - passwordFilename, - "--datadir", - publisherDir, - "--bzzapi", - cluster.Nodes[0].URL, - "access", - "new", - "pk", - "--dry-run", - "--grant-key", - hex.EncodeToString(granteePubKey), - ref, - ) - - _, matches = up.ExpectRegexp(".+") - up.ExpectExit() - - if len(matches) == 0 { - t.Fatalf("stdout not matched") - } - - //get the public key from the publisher directory - publicKeyFromDataDir := runSwarm(t, - "--bzzaccount", - publisherAccount.Address.String(), - "--password", - passwordFilename, - "--datadir", - publisherDir, - "print-keys", - "--compressed", - ) - _, publicKeyString := publicKeyFromDataDir.ExpectRegexp(".+") - publicKeyFromDataDir.ExpectExit() - pkComp := strings.Split(publicKeyString[0], "=")[1] - var m api.Manifest - - err = json.Unmarshal([]byte(matches[0]), &m) - if err != nil { - t.Fatalf("unmarshal manifest: %v", err) - } - - if len(m.Entries) != 1 { - t.Fatalf("expected one manifest entry, got %v", len(m.Entries)) - } - - e := m.Entries[0] - - ct := "application/bzz-manifest+json" - if e.ContentType != ct { - t.Errorf("expected %q content type, got %q", ct, e.ContentType) - } - - if e.Access == nil { - t.Fatal("manifest access is nil") - } - - a := e.Access - - if a.Type != "pk" { - t.Errorf(`got access type %q, expected "pk"`, a.Type) - } - if len(a.Salt) < 32 { - t.Errorf(`got salt with length %v, expected not less the 32 bytes`, len(a.Salt)) - } - if a.KdfParams != nil { - t.Fatal("manifest access kdf params should be nil") - } - if a.Publisher != pkComp { - t.Fatal("publisher key did not match") - } - client := swarmapi.NewClient(cluster.Nodes[0].URL) - - hash, err := client.UploadManifest(&m, false) - if err != nil { - t.Fatal(err) - } - - httpClient := &http.Client{} - - url := cluster.Nodes[0].URL + "/" + "bzz:/" + hash - response, err := httpClient.Get(url) - if err != nil { - t.Fatal(err) - } - if response.StatusCode != http.StatusOK { - t.Fatal("should be a 200") - } - d, err := ioutil.ReadAll(response.Body) - if err != nil { - t.Fatal(err) - } - if string(d) != data { - t.Errorf("expected decrypted data %q, got %q", data, string(d)) - } -} - -// testACTWithoutBogus tests the creation of the ACT manifest end-to-end, without any bogus entries (i.e. default scenario = 3 nodes 1 unauthorized) -func testACTWithoutBogus(t *testing.T) { - testACT(t, 0) -} - -// testACTWithBogus tests the creation of the ACT manifest end-to-end, with 100 bogus entries (i.e. 100 EC keys + default scenario = 3 nodes 1 unauthorized = 103 keys in the ACT manifest) -func testACTWithBogus(t *testing.T) { - testACT(t, 100) -} - -// testACT tests the e2e creation, uploading and downloading of an ACT access control with both EC keys AND password protection -// the test fires up a 3 node cluster, then randomly picks 2 nodes which will be acting as grantees to the data -// set and also protects the ACT with a password. the third node should fail decoding the reference as it will not be granted access. -// the third node then then tries to download using a correct password (and succeeds) then uses a wrong password and fails. -// the publisher uploads through one of the nodes then disappears. -func testACT(t *testing.T, bogusEntries int) { - var uploadThroughNode = cluster.Nodes[0] - client := swarmapi.NewClient(uploadThroughNode.URL) - - r1 := gorand.New(gorand.NewSource(time.Now().UnixNano())) - nodeToSkip := r1.Intn(clusterSize) // a number between 0 and 2 (node indices in `cluster`) - dataFilename := testutil.TempFileWithContent(t, data) - defer os.RemoveAll(dataFilename) - - // upload the file with 'swarm up' and expect a hash - up := runSwarm(t, - "--bzzapi", - cluster.Nodes[0].URL, - "up", - "--encrypt", - dataFilename) - _, matches := up.ExpectRegexp(hashRegexp) - up.ExpectExit() - - if len(matches) < 1 { - t.Fatal("no matches found") - } - - ref := matches[0] - grantees := []string{} - for i, v := range cluster.Nodes { - if i == nodeToSkip { - continue - } - pk := v.PrivateKey - granteePubKey := crypto.CompressPubkey(&pk.PublicKey) - grantees = append(grantees, hex.EncodeToString(granteePubKey)) - } - - if bogusEntries > 0 { - bogusGrantees := []string{} - - for i := 0; i < bogusEntries; i++ { - prv, err := ecies.GenerateKey(rand.Reader, DefaultCurve, nil) - if err != nil { - t.Fatal(err) - } - bogusGrantees = append(bogusGrantees, hex.EncodeToString(crypto.CompressPubkey(&prv.ExportECDSA().PublicKey))) - } - r2 := gorand.New(gorand.NewSource(time.Now().UnixNano())) - for i := 0; i < len(grantees); i++ { - insertAtIdx := r2.Intn(len(bogusGrantees)) - bogusGrantees = append(bogusGrantees[:insertAtIdx], append([]string{grantees[i]}, bogusGrantees[insertAtIdx:]...)...) - } - grantees = bogusGrantees - } - granteesPubkeyListFile := testutil.TempFileWithContent(t, strings.Join(grantees, "\n")) - defer os.RemoveAll(granteesPubkeyListFile) - - publisherDir, err := ioutil.TempDir("", "swarm-account-dir-temp") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(publisherDir) - - passwordFilename := testutil.TempFileWithContent(t, testPassphrase) - defer os.RemoveAll(passwordFilename) - actPasswordFilename := testutil.TempFileWithContent(t, "smth") - defer os.RemoveAll(actPasswordFilename) - _, publisherAccount := getTestAccount(t, publisherDir) - up = runSwarm(t, - "--bzzaccount", - publisherAccount.Address.String(), - "--password", - passwordFilename, - "--datadir", - publisherDir, - "--bzzapi", - cluster.Nodes[0].URL, - "access", - "new", - "act", - "--grant-keys", - granteesPubkeyListFile, - "--password", - actPasswordFilename, - ref, - ) - - _, matches = up.ExpectRegexp(`[a-f\d]{64}`) - up.ExpectExit() - - if len(matches) == 0 { - t.Fatalf("stdout not matched") - } - - //get the public key from the publisher directory - publicKeyFromDataDir := runSwarm(t, - "--bzzaccount", - publisherAccount.Address.String(), - "--password", - passwordFilename, - "--datadir", - publisherDir, - "print-keys", - "--compressed", - ) - _, publicKeyString := publicKeyFromDataDir.ExpectRegexp(".+") - publicKeyFromDataDir.ExpectExit() - pkComp := strings.Split(publicKeyString[0], "=")[1] - - hash := matches[0] - m, _, err := client.DownloadManifest(hash) - if err != nil { - t.Fatalf("unmarshal manifest: %v", err) - } - - if len(m.Entries) != 1 { - t.Fatalf("expected one manifest entry, got %v", len(m.Entries)) - } - - e := m.Entries[0] - - ct := "application/bzz-manifest+json" - if e.ContentType != ct { - t.Errorf("expected %q content type, got %q", ct, e.ContentType) - } - - if e.Access == nil { - t.Fatal("manifest access is nil") - } - - a := e.Access - - if a.Type != "act" { - t.Fatalf(`got access type %q, expected "act"`, a.Type) - } - if len(a.Salt) < 32 { - t.Fatalf(`got salt with length %v, expected not less the 32 bytes`, len(a.Salt)) - } - - if a.Publisher != pkComp { - t.Fatal("publisher key did not match") - } - httpClient := &http.Client{} - - // all nodes except the skipped node should be able to decrypt the content - for i, node := range cluster.Nodes { - log.Debug("trying to fetch from node", "node index", i) - - url := node.URL + "/" + "bzz:/" + hash - response, err := httpClient.Get(url) - if err != nil { - t.Fatal(err) - } - log.Debug("got response from node", "response code", response.StatusCode) - - if i == nodeToSkip { - log.Debug("reached node to skip", "status code", response.StatusCode) - - if response.StatusCode != http.StatusUnauthorized { - t.Fatalf("should be a 401") - } - - // try downloading using a password instead, using the unauthorized node - passwordUrl := strings.Replace(url, "http://", "http://:smth@", -1) - response, err = httpClient.Get(passwordUrl) - if err != nil { - t.Fatal(err) - } - if response.StatusCode != http.StatusOK { - t.Fatal("should be a 200") - } - - // now try with the wrong password, expect 401 - passwordUrl = strings.Replace(url, "http://", "http://:smthWrong@", -1) - response, err = httpClient.Get(passwordUrl) - if err != nil { - t.Fatal(err) - } - if response.StatusCode != http.StatusUnauthorized { - t.Fatal("should be a 401") - } - continue - } - - if response.StatusCode != http.StatusOK { - t.Fatal("should be a 200") - } - d, err := ioutil.ReadAll(response.Body) - if err != nil { - t.Fatal(err) - } - if string(d) != data { - t.Errorf("expected decrypted data %q, got %q", data, string(d)) - } - } -} - -// TestKeypairSanity is a sanity test for the crypto scheme for ACT. it asserts the correct shared secret according to -// the specs at https://github.com/ethersphere/swarm-docs/blob/eb857afda906c6e7bb90d37f3f334ccce5eef230/act.md -func TestKeypairSanity(t *testing.T) { - salt := make([]byte, 32) - if _, err := io.ReadFull(rand.Reader, salt); err != nil { - t.Fatalf("reading from crypto/rand failed: %v", err.Error()) - } - sharedSecret := "a85586744a1ddd56a7ed9f33fa24f40dd745b3a941be296a0d60e329dbdb896d" - - for i, v := range []struct { - publisherPriv string - granteePub string - }{ - { - publisherPriv: "ec5541555f3bc6376788425e9d1a62f55a82901683fd7062c5eddcc373a73459", - granteePub: "0226f213613e843a413ad35b40f193910d26eb35f00154afcde9ded57479a6224a", - }, - { - publisherPriv: "70c7a73011aa56584a0009ab874794ee7e5652fd0c6911cd02f8b6267dd82d2d", - granteePub: "02e6f8d5e28faaa899744972bb847b6eb805a160494690c9ee7197ae9f619181db", - }, - } { - b, _ := hex.DecodeString(v.granteePub) - granteePub, _ := crypto.DecompressPubkey(b) - publisherPrivate, _ := crypto.HexToECDSA(v.publisherPriv) - - ssKey, err := api.NewSessionKeyPK(publisherPrivate, granteePub, salt) - if err != nil { - t.Fatal(err) - } - - hasher := sha3.NewLegacyKeccak256() - hasher.Write(salt) - shared, err := hex.DecodeString(sharedSecret) - if err != nil { - t.Fatal(err) - } - hasher.Write(shared) - sum := hasher.Sum(nil) - - if !bytes.Equal(ssKey, sum) { - t.Fatalf("%d: got a session key mismatch", i) - } - } -} diff --git a/cmd/swarm/bootnodes.go b/cmd/swarm/bootnodes.go deleted file mode 100644 index ce3cd5288e56..000000000000 --- a/cmd/swarm/bootnodes.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -var SwarmBootnodes = []string{ - // EF Swarm Bootnode - AWS - eu-central-1 - "enode://4c113504601930bf2000c29bcd98d1716b6167749f58bad703bae338332fe93cc9d9204f08afb44100dc7bea479205f5d162df579f9a8f76f8b402d339709023@3.122.203.99:30301", - // EF Swarm Bootnode - AWS - us-west-2 - "enode://89f2ede3371bff1ad9f2088f2012984e280287a4e2b68007c2a6ad994909c51886b4a8e9e2ecc97f9910aca538398e0a5804b0ee80a187fde1ba4f32626322ba@52.35.212.179:30301", -} diff --git a/cmd/swarm/config.go b/cmd/swarm/config.go deleted file mode 100644 index aa7b5a610488..000000000000 --- a/cmd/swarm/config.go +++ /dev/null @@ -1,447 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "errors" - "fmt" - "io" - "os" - "reflect" - "strconv" - "strings" - "time" - "unicode" - - cli "gopkg.in/urfave/cli.v1" - - "github.com/ubiq/go-ubiq/cmd/utils" - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/node" - "github.com/naoina/toml" - - bzzapi "github.com/ubiq/go-ubiq/swarm/api" -) - -var ( - //flag definition for the dumpconfig command - DumpConfigCommand = cli.Command{ - Action: utils.MigrateFlags(dumpConfig), - Name: "dumpconfig", - Usage: "Show configuration values", - ArgsUsage: "", - Flags: app.Flags, - Category: "MISCELLANEOUS COMMANDS", - Description: `The dumpconfig command shows configuration values.`, - } - - //flag definition for the config file command - SwarmTomlConfigPathFlag = cli.StringFlag{ - Name: "config", - Usage: "TOML configuration file", - } -) - -//constants for environment variables -const ( - SWARM_ENV_CHEQUEBOOK_ADDR = "SWARM_CHEQUEBOOK_ADDR" - SWARM_ENV_ACCOUNT = "SWARM_ACCOUNT" - SWARM_ENV_LISTEN_ADDR = "SWARM_LISTEN_ADDR" - SWARM_ENV_PORT = "SWARM_PORT" - SWARM_ENV_NETWORK_ID = "SWARM_NETWORK_ID" - SWARM_ENV_SWAP_ENABLE = "SWARM_SWAP_ENABLE" - SWARM_ENV_SWAP_API = "SWARM_SWAP_API" - SWARM_ENV_SYNC_DISABLE = "SWARM_SYNC_DISABLE" - SWARM_ENV_SYNC_UPDATE_DELAY = "SWARM_ENV_SYNC_UPDATE_DELAY" - SWARM_ENV_MAX_STREAM_PEER_SERVERS = "SWARM_ENV_MAX_STREAM_PEER_SERVERS" - SWARM_ENV_LIGHT_NODE_ENABLE = "SWARM_LIGHT_NODE_ENABLE" - SWARM_ENV_DELIVERY_SKIP_CHECK = "SWARM_DELIVERY_SKIP_CHECK" - SWARM_ENV_ENS_API = "SWARM_ENS_API" - SWARM_ENV_ENS_ADDR = "SWARM_ENS_ADDR" - SWARM_ENV_CORS = "SWARM_CORS" - SWARM_ENV_BOOTNODES = "SWARM_BOOTNODES" - SWARM_ENV_PSS_ENABLE = "SWARM_PSS_ENABLE" - SWARM_ENV_STORE_PATH = "SWARM_STORE_PATH" - SWARM_ENV_STORE_CAPACITY = "SWARM_STORE_CAPACITY" - SWARM_ENV_STORE_CACHE_CAPACITY = "SWARM_STORE_CACHE_CAPACITY" - SWARM_ENV_BOOTNODE_MODE = "SWARM_BOOTNODE_MODE" - SWARM_ACCESS_PASSWORD = "SWARM_ACCESS_PASSWORD" - SWARM_AUTO_DEFAULTPATH = "SWARM_AUTO_DEFAULTPATH" - SWARM_GLOBALSTORE_API = "SWARM_GLOBALSTORE_API" - GETH_ENV_DATADIR = "GETH_DATADIR" -) - -// These settings ensure that TOML keys use the same names as Go struct fields. -var tomlSettings = toml.Config{ - NormFieldName: func(rt reflect.Type, key string) string { - return key - }, - FieldToKey: func(rt reflect.Type, field string) string { - return field - }, - MissingField: func(rt reflect.Type, field string) error { - link := "" - if unicode.IsUpper(rune(rt.Name()[0])) && rt.PkgPath() != "main" { - link = fmt.Sprintf(", check github.com/ubiq/go-ubiq/swarm/api/config.go for available fields") - } - return fmt.Errorf("field '%s' is not defined in %s%s", field, rt.String(), link) - }, -} - -//before booting the swarm node, build the configuration -func buildConfig(ctx *cli.Context) (config *bzzapi.Config, err error) { - //start by creating a default config - config = bzzapi.NewConfig() - //first load settings from config file (if provided) - config, err = configFileOverride(config, ctx) - if err != nil { - return nil, err - } - //override settings provided by environment variables - config = envVarsOverride(config) - //override settings provided by command line - config = cmdLineOverride(config, ctx) - //validate configuration parameters - err = validateConfig(config) - - return -} - -//finally, after the configuration build phase is finished, initialize -func initSwarmNode(config *bzzapi.Config, stack *node.Node, ctx *cli.Context) { - //at this point, all vars should be set in the Config - //get the account for the provided swarm account - prvkey := getAccount(config.BzzAccount, ctx, stack) - //set the resolved config path (gubiq --datadir) - config.Path = expandPath(stack.InstanceDir()) - //finally, initialize the configuration - config.Init(prvkey) - //configuration phase completed here - log.Debug("Starting Swarm with the following parameters:") - //after having created the config, print it to screen - log.Debug(printConfig(config)) -} - -//configFileOverride overrides the current config with the config file, if a config file has been provided -func configFileOverride(config *bzzapi.Config, ctx *cli.Context) (*bzzapi.Config, error) { - var err error - - //only do something if the -config flag has been set - if ctx.GlobalIsSet(SwarmTomlConfigPathFlag.Name) { - var filepath string - if filepath = ctx.GlobalString(SwarmTomlConfigPathFlag.Name); filepath == "" { - utils.Fatalf("Config file flag provided with invalid file path") - } - var f *os.File - f, err = os.Open(filepath) - if err != nil { - return nil, err - } - defer f.Close() - - //decode the TOML file into a Config struct - //note that we are decoding into the existing defaultConfig; - //if an entry is not present in the file, the default entry is kept - err = tomlSettings.NewDecoder(f).Decode(&config) - // Add file name to errors that have a line number. - if _, ok := err.(*toml.LineError); ok { - err = errors.New(filepath + ", " + err.Error()) - } - } - return config, err -} - -// cmdLineOverride overrides the current config with whatever is provided through the command line -// most values are not allowed a zero value (empty string), if not otherwise noted -func cmdLineOverride(currentConfig *bzzapi.Config, ctx *cli.Context) *bzzapi.Config { - if keyid := ctx.GlobalString(SwarmAccountFlag.Name); keyid != "" { - currentConfig.BzzAccount = keyid - } - - if chbookaddr := ctx.GlobalString(ChequebookAddrFlag.Name); chbookaddr != "" { - currentConfig.Contract = common.HexToAddress(chbookaddr) - } - - if networkid := ctx.GlobalString(SwarmNetworkIdFlag.Name); networkid != "" { - id, err := strconv.ParseUint(networkid, 10, 64) - if err != nil { - utils.Fatalf("invalid cli flag %s: %v", SwarmNetworkIdFlag.Name, err) - } - if id != 0 { - currentConfig.NetworkID = id - } - } - - if ctx.GlobalIsSet(utils.DataDirFlag.Name) { - if datadir := ctx.GlobalString(utils.DataDirFlag.Name); datadir != "" { - currentConfig.Path = expandPath(datadir) - } - } - - bzzport := ctx.GlobalString(SwarmPortFlag.Name) - if len(bzzport) > 0 { - currentConfig.Port = bzzport - } - - if bzzaddr := ctx.GlobalString(SwarmListenAddrFlag.Name); bzzaddr != "" { - currentConfig.ListenAddr = bzzaddr - } - - if ctx.GlobalIsSet(SwarmSwapEnabledFlag.Name) { - currentConfig.SwapEnabled = true - } - - if ctx.GlobalIsSet(SwarmSyncDisabledFlag.Name) { - currentConfig.SyncEnabled = false - } - - if d := ctx.GlobalDuration(SwarmSyncUpdateDelay.Name); d > 0 { - currentConfig.SyncUpdateDelay = d - } - - // any value including 0 is acceptable - currentConfig.MaxStreamPeerServers = ctx.GlobalInt(SwarmMaxStreamPeerServersFlag.Name) - - if ctx.GlobalIsSet(SwarmLightNodeEnabled.Name) { - currentConfig.LightNodeEnabled = true - } - - if ctx.GlobalIsSet(SwarmDeliverySkipCheckFlag.Name) { - currentConfig.DeliverySkipCheck = true - } - - currentConfig.SwapAPI = ctx.GlobalString(SwarmSwapAPIFlag.Name) - if currentConfig.SwapEnabled && currentConfig.SwapAPI == "" { - utils.Fatalf(SWARM_ERR_SWAP_SET_NO_API) - } - - if ctx.GlobalIsSet(EnsAPIFlag.Name) { - ensAPIs := ctx.GlobalStringSlice(EnsAPIFlag.Name) - // preserve backward compatibility to disable ENS with --ens-api="" - if len(ensAPIs) == 1 && ensAPIs[0] == "" { - ensAPIs = nil - } - for i := range ensAPIs { - ensAPIs[i] = expandPath(ensAPIs[i]) - } - - currentConfig.EnsAPIs = ensAPIs - } - - if cors := ctx.GlobalString(CorsStringFlag.Name); cors != "" { - currentConfig.Cors = cors - } - - if storePath := ctx.GlobalString(SwarmStorePath.Name); storePath != "" { - currentConfig.LocalStoreParams.ChunkDbPath = storePath - } - - if storeCapacity := ctx.GlobalUint64(SwarmStoreCapacity.Name); storeCapacity != 0 { - currentConfig.LocalStoreParams.DbCapacity = storeCapacity - } - - if storeCacheCapacity := ctx.GlobalUint(SwarmStoreCacheCapacity.Name); storeCacheCapacity != 0 { - currentConfig.LocalStoreParams.CacheCapacity = storeCacheCapacity - } - - if ctx.GlobalIsSet(SwarmBootnodeModeFlag.Name) { - currentConfig.BootnodeMode = ctx.GlobalBool(SwarmBootnodeModeFlag.Name) - } - - if ctx.GlobalIsSet(SwarmGlobalStoreAPIFlag.Name) { - currentConfig.GlobalStoreAPI = ctx.GlobalString(SwarmGlobalStoreAPIFlag.Name) - } - - return currentConfig - -} - -// envVarsOverride overrides the current config with whatver is provided in environment variables -// most values are not allowed a zero value (empty string), if not otherwise noted -func envVarsOverride(currentConfig *bzzapi.Config) (config *bzzapi.Config) { - if keyid := os.Getenv(SWARM_ENV_ACCOUNT); keyid != "" { - currentConfig.BzzAccount = keyid - } - - if chbookaddr := os.Getenv(SWARM_ENV_CHEQUEBOOK_ADDR); chbookaddr != "" { - currentConfig.Contract = common.HexToAddress(chbookaddr) - } - - if networkid := os.Getenv(SWARM_ENV_NETWORK_ID); networkid != "" { - id, err := strconv.ParseUint(networkid, 10, 64) - if err != nil { - utils.Fatalf("invalid environment variable %s: %v", SWARM_ENV_NETWORK_ID, err) - } - if id != 0 { - currentConfig.NetworkID = id - } - } - - if datadir := os.Getenv(GETH_ENV_DATADIR); datadir != "" { - currentConfig.Path = expandPath(datadir) - } - - bzzport := os.Getenv(SWARM_ENV_PORT) - if len(bzzport) > 0 { - currentConfig.Port = bzzport - } - - if bzzaddr := os.Getenv(SWARM_ENV_LISTEN_ADDR); bzzaddr != "" { - currentConfig.ListenAddr = bzzaddr - } - - if swapenable := os.Getenv(SWARM_ENV_SWAP_ENABLE); swapenable != "" { - swap, err := strconv.ParseBool(swapenable) - if err != nil { - utils.Fatalf("invalid environment variable %s: %v", SWARM_ENV_SWAP_ENABLE, err) - } - currentConfig.SwapEnabled = swap - } - - if syncdisable := os.Getenv(SWARM_ENV_SYNC_DISABLE); syncdisable != "" { - sync, err := strconv.ParseBool(syncdisable) - if err != nil { - utils.Fatalf("invalid environment variable %s: %v", SWARM_ENV_SYNC_DISABLE, err) - } - currentConfig.SyncEnabled = !sync - } - - if v := os.Getenv(SWARM_ENV_DELIVERY_SKIP_CHECK); v != "" { - skipCheck, err := strconv.ParseBool(v) - if err != nil { - currentConfig.DeliverySkipCheck = skipCheck - } - } - - if v := os.Getenv(SWARM_ENV_SYNC_UPDATE_DELAY); v != "" { - d, err := time.ParseDuration(v) - if err != nil { - utils.Fatalf("invalid environment variable %s: %v", SWARM_ENV_SYNC_UPDATE_DELAY, err) - } - currentConfig.SyncUpdateDelay = d - } - - if max := os.Getenv(SWARM_ENV_MAX_STREAM_PEER_SERVERS); max != "" { - m, err := strconv.Atoi(max) - if err != nil { - utils.Fatalf("invalid environment variable %s: %v", SWARM_ENV_MAX_STREAM_PEER_SERVERS, err) - } - currentConfig.MaxStreamPeerServers = m - } - - if lne := os.Getenv(SWARM_ENV_LIGHT_NODE_ENABLE); lne != "" { - lightnode, err := strconv.ParseBool(lne) - if err != nil { - utils.Fatalf("invalid environment variable %s: %v", SWARM_ENV_LIGHT_NODE_ENABLE, err) - } - currentConfig.LightNodeEnabled = lightnode - } - - if swapapi := os.Getenv(SWARM_ENV_SWAP_API); swapapi != "" { - currentConfig.SwapAPI = swapapi - } - - if currentConfig.SwapEnabled && currentConfig.SwapAPI == "" { - utils.Fatalf(SWARM_ERR_SWAP_SET_NO_API) - } - - if ensapi := os.Getenv(SWARM_ENV_ENS_API); ensapi != "" { - currentConfig.EnsAPIs = strings.Split(ensapi, ",") - } - - if ensaddr := os.Getenv(SWARM_ENV_ENS_ADDR); ensaddr != "" { - currentConfig.EnsRoot = common.HexToAddress(ensaddr) - } - - if cors := os.Getenv(SWARM_ENV_CORS); cors != "" { - currentConfig.Cors = cors - } - - if bm := os.Getenv(SWARM_ENV_BOOTNODE_MODE); bm != "" { - bootnodeMode, err := strconv.ParseBool(bm) - if err != nil { - utils.Fatalf("invalid environment variable %s: %v", SWARM_ENV_BOOTNODE_MODE, err) - } - currentConfig.BootnodeMode = bootnodeMode - } - - if api := os.Getenv(SWARM_GLOBALSTORE_API); api != "" { - currentConfig.GlobalStoreAPI = api - } - - return currentConfig -} - -// dumpConfig is the dumpconfig command. -// writes a default config to STDOUT -func dumpConfig(ctx *cli.Context) error { - cfg, err := buildConfig(ctx) - if err != nil { - utils.Fatalf(fmt.Sprintf("Uh oh - dumpconfig triggered an error %v", err)) - } - comment := "" - out, err := tomlSettings.Marshal(&cfg) - if err != nil { - return err - } - io.WriteString(os.Stdout, comment) - os.Stdout.Write(out) - return nil -} - -//validate configuration parameters -func validateConfig(cfg *bzzapi.Config) (err error) { - for _, ensAPI := range cfg.EnsAPIs { - if ensAPI != "" { - if err := validateEnsAPIs(ensAPI); err != nil { - return fmt.Errorf("invalid format [tld:][contract-addr@]url for ENS API endpoint configuration %q: %v", ensAPI, err) - } - } - } - return nil -} - -//validate EnsAPIs configuration parameter -func validateEnsAPIs(s string) (err error) { - // missing contract address - if strings.HasPrefix(s, "@") { - return errors.New("missing contract address") - } - // missing url - if strings.HasSuffix(s, "@") { - return errors.New("missing url") - } - // missing tld - if strings.HasPrefix(s, ":") { - return errors.New("missing tld") - } - // missing url - if strings.HasSuffix(s, ":") { - return errors.New("missing url") - } - return nil -} - -//print a Config as string -func printConfig(config *bzzapi.Config) string { - out, err := tomlSettings.Marshal(&config) - if err != nil { - return fmt.Sprintf("Something is not right with the configuration: %v", err) - } - return string(out) -} diff --git a/cmd/swarm/config_test.go b/cmd/swarm/config_test.go deleted file mode 100644 index 0f33864f461f..000000000000 --- a/cmd/swarm/config_test.go +++ /dev/null @@ -1,575 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "fmt" - "io" - "io/ioutil" - "net" - "os" - "os/exec" - "testing" - "time" - - "github.com/docker/docker/pkg/reexec" - "github.com/ubiq/go-ubiq/cmd/utils" - "github.com/ubiq/go-ubiq/rpc" - "github.com/ubiq/go-ubiq/swarm" - "github.com/ubiq/go-ubiq/swarm/api" -) - -func TestConfigDump(t *testing.T) { - swarm := runSwarm(t, "dumpconfig") - defaultConf := api.NewConfig() - out, err := tomlSettings.Marshal(&defaultConf) - if err != nil { - t.Fatal(err) - } - swarm.Expect(string(out)) - swarm.ExpectExit() -} - -func TestConfigFailsSwapEnabledNoSwapApi(t *testing.T) { - flags := []string{ - fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42", - fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545", - fmt.Sprintf("--%s", SwarmSwapEnabledFlag.Name), - } - - swarm := runSwarm(t, flags...) - swarm.Expect("Fatal: " + SWARM_ERR_SWAP_SET_NO_API + "\n") - swarm.ExpectExit() -} - -func TestConfigFailsNoBzzAccount(t *testing.T) { - flags := []string{ - fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42", - fmt.Sprintf("--%s", SwarmPortFlag.Name), "54545", - } - - swarm := runSwarm(t, flags...) - swarm.Expect("Fatal: " + SWARM_ERR_NO_BZZACCOUNT + "\n") - swarm.ExpectExit() -} - -func TestConfigCmdLineOverrides(t *testing.T) { - dir, err := ioutil.TempDir("", "bzztest") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - - conf, account := getTestAccount(t, dir) - node := &testNode{Dir: dir} - - // assign ports - httpPort, err := assignTCPPort() - if err != nil { - t.Fatal(err) - } - - flags := []string{ - fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "42", - fmt.Sprintf("--%s", SwarmPortFlag.Name), httpPort, - fmt.Sprintf("--%s", SwarmSyncDisabledFlag.Name), - fmt.Sprintf("--%s", CorsStringFlag.Name), "*", - fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(), - fmt.Sprintf("--%s", SwarmDeliverySkipCheckFlag.Name), - fmt.Sprintf("--%s", EnsAPIFlag.Name), "", - fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir, - fmt.Sprintf("--%s", utils.IPCPathFlag.Name), conf.IPCPath, - } - node.Cmd = runSwarm(t, flags...) - node.Cmd.InputLine(testPassphrase) - defer func() { - if t.Failed() { - node.Shutdown() - } - }() - // wait for the node to start - for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) { - node.Client, err = rpc.Dial(conf.IPCEndpoint()) - if err == nil { - break - } - } - if node.Client == nil { - t.Fatal(err) - } - - // load info - var info swarm.Info - if err := node.Client.Call(&info, "bzz_info"); err != nil { - t.Fatal(err) - } - - if info.Port != httpPort { - t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port) - } - - if info.NetworkID != 42 { - t.Fatalf("Expected network ID to be %d, got %d", 42, info.NetworkID) - } - - if info.SyncEnabled { - t.Fatal("Expected Sync to be disabled, but is true") - } - - if !info.DeliverySkipCheck { - t.Fatal("Expected DeliverySkipCheck to be enabled, but it is not") - } - - if info.Cors != "*" { - t.Fatalf("Expected Cors flag to be set to %s, got %s", "*", info.Cors) - } - - node.Shutdown() -} - -func TestConfigFileOverrides(t *testing.T) { - - // assign ports - httpPort, err := assignTCPPort() - if err != nil { - t.Fatal(err) - } - - //create a config file - //first, create a default conf - defaultConf := api.NewConfig() - //change some values in order to test if they have been loaded - defaultConf.SyncEnabled = false - defaultConf.DeliverySkipCheck = true - defaultConf.NetworkID = 54 - defaultConf.Port = httpPort - defaultConf.DbCapacity = 9000000 - defaultConf.HiveParams.KeepAliveInterval = 6000000000 - defaultConf.Swap.Params.Strategy.AutoCashInterval = 600 * time.Second - //defaultConf.SyncParams.KeyBufferSize = 512 - //create a TOML string - out, err := tomlSettings.Marshal(&defaultConf) - if err != nil { - t.Fatalf("Error creating TOML file in TestFileOverride: %v", err) - } - //create file - f, err := ioutil.TempFile("", "testconfig.toml") - if err != nil { - t.Fatalf("Error writing TOML file in TestFileOverride: %v", err) - } - //write file - _, err = f.WriteString(string(out)) - if err != nil { - t.Fatalf("Error writing TOML file in TestFileOverride: %v", err) - } - f.Sync() - - dir, err := ioutil.TempDir("", "bzztest") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - conf, account := getTestAccount(t, dir) - node := &testNode{Dir: dir} - - flags := []string{ - fmt.Sprintf("--%s", SwarmTomlConfigPathFlag.Name), f.Name(), - fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(), - fmt.Sprintf("--%s", EnsAPIFlag.Name), "", - fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir, - fmt.Sprintf("--%s", utils.IPCPathFlag.Name), conf.IPCPath, - } - node.Cmd = runSwarm(t, flags...) - node.Cmd.InputLine(testPassphrase) - defer func() { - if t.Failed() { - node.Shutdown() - } - }() - // wait for the node to start - for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) { - node.Client, err = rpc.Dial(conf.IPCEndpoint()) - if err == nil { - break - } - } - if node.Client == nil { - t.Fatal(err) - } - - // load info - var info swarm.Info - if err := node.Client.Call(&info, "bzz_info"); err != nil { - t.Fatal(err) - } - - if info.Port != httpPort { - t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port) - } - - if info.NetworkID != 54 { - t.Fatalf("Expected network ID to be %d, got %d", 54, info.NetworkID) - } - - if info.SyncEnabled { - t.Fatal("Expected Sync to be disabled, but is true") - } - - if !info.DeliverySkipCheck { - t.Fatal("Expected DeliverySkipCheck to be enabled, but it is not") - } - - if info.DbCapacity != 9000000 { - t.Fatalf("Expected network ID to be %d, got %d", 54, info.NetworkID) - } - - if info.HiveParams.KeepAliveInterval != 6000000000 { - t.Fatalf("Expected HiveParams KeepAliveInterval to be %d, got %d", uint64(6000000000), uint64(info.HiveParams.KeepAliveInterval)) - } - - if info.Swap.Params.Strategy.AutoCashInterval != 600*time.Second { - t.Fatalf("Expected SwapParams AutoCashInterval to be %ds, got %d", 600, info.Swap.Params.Strategy.AutoCashInterval) - } - - // if info.SyncParams.KeyBufferSize != 512 { - // t.Fatalf("Expected info.SyncParams.KeyBufferSize to be %d, got %d", 512, info.SyncParams.KeyBufferSize) - // } - - node.Shutdown() -} - -func TestConfigEnvVars(t *testing.T) { - // assign ports - httpPort, err := assignTCPPort() - if err != nil { - t.Fatal(err) - } - - envVars := os.Environ() - envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmPortFlag.EnvVar, httpPort)) - envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmNetworkIdFlag.EnvVar, "999")) - envVars = append(envVars, fmt.Sprintf("%s=%s", CorsStringFlag.EnvVar, "*")) - envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmSyncDisabledFlag.EnvVar, "true")) - envVars = append(envVars, fmt.Sprintf("%s=%s", SwarmDeliverySkipCheckFlag.EnvVar, "true")) - - dir, err := ioutil.TempDir("", "bzztest") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - conf, account := getTestAccount(t, dir) - node := &testNode{Dir: dir} - flags := []string{ - fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(), - "--ens-api", "", - "--datadir", dir, - "--ipcpath", conf.IPCPath, - } - - //node.Cmd = runSwarm(t,flags...) - //node.Cmd.cmd.Env = envVars - //the above assignment does not work, so we need a custom Cmd here in order to pass envVars: - cmd := &exec.Cmd{ - Path: reexec.Self(), - Args: append([]string{"swarm-test"}, flags...), - Stderr: os.Stderr, - Stdout: os.Stdout, - } - cmd.Env = envVars - //stdout, err := cmd.StdoutPipe() - //if err != nil { - // t.Fatal(err) - //} - //stdout = bufio.NewReader(stdout) - var stdin io.WriteCloser - if stdin, err = cmd.StdinPipe(); err != nil { - t.Fatal(err) - } - if err := cmd.Start(); err != nil { - t.Fatal(err) - } - - //cmd.InputLine(testPassphrase) - io.WriteString(stdin, testPassphrase+"\n") - defer func() { - if t.Failed() { - node.Shutdown() - cmd.Process.Kill() - } - }() - // wait for the node to start - for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) { - node.Client, err = rpc.Dial(conf.IPCEndpoint()) - if err == nil { - break - } - } - - if node.Client == nil { - t.Fatal(err) - } - - // load info - var info swarm.Info - if err := node.Client.Call(&info, "bzz_info"); err != nil { - t.Fatal(err) - } - - if info.Port != httpPort { - t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port) - } - - if info.NetworkID != 999 { - t.Fatalf("Expected network ID to be %d, got %d", 999, info.NetworkID) - } - - if info.Cors != "*" { - t.Fatalf("Expected Cors flag to be set to %s, got %s", "*", info.Cors) - } - - if info.SyncEnabled { - t.Fatal("Expected Sync to be disabled, but is true") - } - - if !info.DeliverySkipCheck { - t.Fatal("Expected DeliverySkipCheck to be enabled, but it is not") - } - - node.Shutdown() - cmd.Process.Kill() -} - -func TestConfigCmdLineOverridesFile(t *testing.T) { - - // assign ports - httpPort, err := assignTCPPort() - if err != nil { - t.Fatal(err) - } - - //create a config file - //first, create a default conf - defaultConf := api.NewConfig() - //change some values in order to test if they have been loaded - defaultConf.SyncEnabled = true - defaultConf.NetworkID = 54 - defaultConf.Port = "8588" - defaultConf.DbCapacity = 9000000 - defaultConf.HiveParams.KeepAliveInterval = 6000000000 - defaultConf.Swap.Params.Strategy.AutoCashInterval = 600 * time.Second - //defaultConf.SyncParams.KeyBufferSize = 512 - //create a TOML file - out, err := tomlSettings.Marshal(&defaultConf) - if err != nil { - t.Fatalf("Error creating TOML file in TestFileOverride: %v", err) - } - //write file - fname := "testconfig.toml" - f, err := ioutil.TempFile("", fname) - if err != nil { - t.Fatalf("Error writing TOML file in TestFileOverride: %v", err) - } - defer os.Remove(fname) - //write file - _, err = f.WriteString(string(out)) - if err != nil { - t.Fatalf("Error writing TOML file in TestFileOverride: %v", err) - } - f.Sync() - - dir, err := ioutil.TempDir("", "bzztest") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - conf, account := getTestAccount(t, dir) - node := &testNode{Dir: dir} - - expectNetworkId := uint64(77) - - flags := []string{ - fmt.Sprintf("--%s", SwarmNetworkIdFlag.Name), "77", - fmt.Sprintf("--%s", SwarmPortFlag.Name), httpPort, - fmt.Sprintf("--%s", SwarmSyncDisabledFlag.Name), - fmt.Sprintf("--%s", SwarmTomlConfigPathFlag.Name), f.Name(), - fmt.Sprintf("--%s", SwarmAccountFlag.Name), account.Address.String(), - fmt.Sprintf("--%s", EnsAPIFlag.Name), "", - fmt.Sprintf("--%s", utils.DataDirFlag.Name), dir, - fmt.Sprintf("--%s", utils.IPCPathFlag.Name), conf.IPCPath, - } - node.Cmd = runSwarm(t, flags...) - node.Cmd.InputLine(testPassphrase) - defer func() { - if t.Failed() { - node.Shutdown() - } - }() - // wait for the node to start - for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) { - node.Client, err = rpc.Dial(conf.IPCEndpoint()) - if err == nil { - break - } - } - if node.Client == nil { - t.Fatal(err) - } - - // load info - var info swarm.Info - if err := node.Client.Call(&info, "bzz_info"); err != nil { - t.Fatal(err) - } - - if info.Port != httpPort { - t.Fatalf("Expected port to be %s, got %s", httpPort, info.Port) - } - - if info.NetworkID != expectNetworkId { - t.Fatalf("Expected network ID to be %d, got %d", expectNetworkId, info.NetworkID) - } - - if info.SyncEnabled { - t.Fatal("Expected Sync to be disabled, but is true") - } - - if info.LocalStoreParams.DbCapacity != 9000000 { - t.Fatalf("Expected Capacity to be %d, got %d", 9000000, info.LocalStoreParams.DbCapacity) - } - - if info.HiveParams.KeepAliveInterval != 6000000000 { - t.Fatalf("Expected HiveParams KeepAliveInterval to be %d, got %d", uint64(6000000000), uint64(info.HiveParams.KeepAliveInterval)) - } - - if info.Swap.Params.Strategy.AutoCashInterval != 600*time.Second { - t.Fatalf("Expected SwapParams AutoCashInterval to be %ds, got %d", 600, info.Swap.Params.Strategy.AutoCashInterval) - } - - // if info.SyncParams.KeyBufferSize != 512 { - // t.Fatalf("Expected info.SyncParams.KeyBufferSize to be %d, got %d", 512, info.SyncParams.KeyBufferSize) - // } - - node.Shutdown() -} - -func TestConfigValidate(t *testing.T) { - for _, c := range []struct { - cfg *api.Config - err string - }{ - { - cfg: &api.Config{EnsAPIs: []string{ - "/data/testnet/gubiq.ipc", - }}, - }, - { - cfg: &api.Config{EnsAPIs: []string{ - "http://127.0.0.1:1234", - }}, - }, - { - cfg: &api.Config{EnsAPIs: []string{ - "ws://127.0.0.1:1234", - }}, - }, - { - cfg: &api.Config{EnsAPIs: []string{ - "test:/data/testnet/gubiq.ipc", - }}, - }, - { - cfg: &api.Config{EnsAPIs: []string{ - "test:ws://127.0.0.1:1234", - }}, - }, - { - cfg: &api.Config{EnsAPIs: []string{ - "314159265dD8dbb310642f98f50C066173C1259b@/data/testnet/gubiq.ipc", - }}, - }, - { - cfg: &api.Config{EnsAPIs: []string{ - "314159265dD8dbb310642f98f50C066173C1259b@http://127.0.0.1:1234", - }}, - }, - { - cfg: &api.Config{EnsAPIs: []string{ - "314159265dD8dbb310642f98f50C066173C1259b@ws://127.0.0.1:1234", - }}, - }, - { - cfg: &api.Config{EnsAPIs: []string{ - "test:314159265dD8dbb310642f98f50C066173C1259b@/data/testnet/gubiq.ipc", - }}, - }, - { - cfg: &api.Config{EnsAPIs: []string{ - "eth:314159265dD8dbb310642f98f50C066173C1259b@http://127.0.0.1:1234", - }}, - }, - { - cfg: &api.Config{EnsAPIs: []string{ - "eth:314159265dD8dbb310642f98f50C066173C1259b@ws://127.0.0.1:12344", - }}, - }, - { - cfg: &api.Config{EnsAPIs: []string{ - "eth:", - }}, - err: "invalid format [tld:][contract-addr@]url for ENS API endpoint configuration \"eth:\": missing url", - }, - { - cfg: &api.Config{EnsAPIs: []string{ - "314159265dD8dbb310642f98f50C066173C1259b@", - }}, - err: "invalid format [tld:][contract-addr@]url for ENS API endpoint configuration \"314159265dD8dbb310642f98f50C066173C1259b@\": missing url", - }, - { - cfg: &api.Config{EnsAPIs: []string{ - ":314159265dD8dbb310642f98f50C066173C1259", - }}, - err: "invalid format [tld:][contract-addr@]url for ENS API endpoint configuration \":314159265dD8dbb310642f98f50C066173C1259\": missing tld", - }, - { - cfg: &api.Config{EnsAPIs: []string{ - "@/data/testnet/gubiq.ipc", - }}, - err: "invalid format [tld:][contract-addr@]url for ENS API endpoint configuration \"@/data/testnet/gubiq.ipc\": missing contract address", - }, - } { - err := validateConfig(c.cfg) - if c.err != "" && err.Error() != c.err { - t.Errorf("expected error %q, got %q", c.err, err) - } - if c.err == "" && err != nil { - t.Errorf("unexpected error %q", err) - } - } -} - -func assignTCPPort() (string, error) { - l, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - return "", err - } - l.Close() - _, port, err := net.SplitHostPort(l.Addr().String()) - if err != nil { - return "", err - } - return port, nil -} diff --git a/cmd/swarm/db.go b/cmd/swarm/db.go deleted file mode 100644 index 8f3e87255b9e..000000000000 --- a/cmd/swarm/db.go +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "fmt" - "io" - "os" - "path/filepath" - - "github.com/ubiq/go-ubiq/cmd/utils" - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/swarm/storage" - "gopkg.in/urfave/cli.v1" -) - -var dbCommand = cli.Command{ - Name: "db", - CustomHelpTemplate: helpTemplate, - Usage: "manage the local chunk database", - ArgsUsage: "db COMMAND", - Description: "Manage the local chunk database", - Subcommands: []cli.Command{ - { - Action: dbExport, - CustomHelpTemplate: helpTemplate, - Name: "export", - Usage: "export a local chunk database as a tar archive (use - to send to stdout)", - ArgsUsage: " ", - Description: ` -Export a local chunk database as a tar archive (use - to send to stdout). - - swarm db export ~/.ubiq/swarm/bzz-KEY/chunks chunks.tar - -The export may be quite large, consider piping the output through the Unix -pv(1) tool to get a progress bar: - - swarm db export ~/.ubiq/swarm/bzz-KEY/chunks - | pv > chunks.tar -`, - }, - { - Action: dbImport, - CustomHelpTemplate: helpTemplate, - Name: "import", - Usage: "import chunks from a tar archive into a local chunk database (use - to read from stdin)", - ArgsUsage: " ", - Description: `Import chunks from a tar archive into a local chunk database (use - to read from stdin). - - swarm db import ~/.ubiq/swarm/bzz-KEY/chunks chunks.tar - -The import may be quite large, consider piping the input through the Unix -pv(1) tool to get a progress bar: - - pv chunks.tar | swarm db import ~/.ubiq/swarm/bzz-KEY/chunks -`, - }, - }, -} - -func dbExport(ctx *cli.Context) { - args := ctx.Args() - if len(args) != 3 { - utils.Fatalf("invalid arguments, please specify both (path to a local chunk database), (path to write the tar archive to, - for stdout) and the base key") - } - - store, err := openLDBStore(args[0], common.Hex2Bytes(args[2])) - if err != nil { - utils.Fatalf("error opening local chunk database: %s", err) - } - defer store.Close() - - var out io.Writer - if args[1] == "-" { - out = os.Stdout - } else { - f, err := os.Create(args[1]) - if err != nil { - utils.Fatalf("error opening output file: %s", err) - } - defer f.Close() - out = f - } - - count, err := store.Export(out) - if err != nil { - utils.Fatalf("error exporting local chunk database: %s", err) - } - - log.Info(fmt.Sprintf("successfully exported %d chunks", count)) -} - -func dbImport(ctx *cli.Context) { - args := ctx.Args() - if len(args) != 3 { - utils.Fatalf("invalid arguments, please specify both (path to a local chunk database), (path to read the tar archive from, - for stdin) and the base key") - } - - store, err := openLDBStore(args[0], common.Hex2Bytes(args[2])) - if err != nil { - utils.Fatalf("error opening local chunk database: %s", err) - } - defer store.Close() - - var in io.Reader - if args[1] == "-" { - in = os.Stdin - } else { - f, err := os.Open(args[1]) - if err != nil { - utils.Fatalf("error opening input file: %s", err) - } - defer f.Close() - in = f - } - - count, err := store.Import(in) - if err != nil { - utils.Fatalf("error importing local chunk database: %s", err) - } - - log.Info(fmt.Sprintf("successfully imported %d chunks", count)) -} - -func openLDBStore(path string, basekey []byte) (*storage.LDBStore, error) { - if _, err := os.Stat(filepath.Join(path, "CURRENT")); err != nil { - return nil, fmt.Errorf("invalid chunkdb path: %s", err) - } - - storeparams := storage.NewDefaultStoreParams() - ldbparams := storage.NewLDBStoreParams(storeparams, path) - ldbparams.BaseKey = basekey - return storage.NewLDBStore(ldbparams) -} diff --git a/cmd/swarm/download.go b/cmd/swarm/download.go deleted file mode 100644 index 1635512607c8..000000000000 --- a/cmd/swarm/download.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . -package main - -import ( - "fmt" - "os" - "path/filepath" - "strings" - - "github.com/ubiq/go-ubiq/cmd/utils" - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/swarm/api" - swarm "github.com/ubiq/go-ubiq/swarm/api/client" - "gopkg.in/urfave/cli.v1" -) - -var downloadCommand = cli.Command{ - Action: download, - Name: "down", - Flags: []cli.Flag{SwarmRecursiveFlag, SwarmAccessPasswordFlag}, - Usage: "downloads a swarm manifest or a file inside a manifest", - ArgsUsage: " []", - Description: `Downloads a swarm bzz uri to the given dir. When no dir is provided, working directory is assumed. --recursive flag is expected when downloading a manifest with multiple entries.`, -} - -func download(ctx *cli.Context) { - log.Debug("downloading content using swarm down") - args := ctx.Args() - dest := "." - - switch len(args) { - case 0: - utils.Fatalf("Usage: swarm down [options] []") - case 1: - log.Trace(fmt.Sprintf("swarm down: no destination path - assuming working dir")) - default: - log.Trace(fmt.Sprintf("destination path arg: %s", args[1])) - if absDest, err := filepath.Abs(args[1]); err == nil { - dest = absDest - } else { - utils.Fatalf("could not get download path: %v", err) - } - } - - var ( - bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/") - isRecursive = ctx.Bool(SwarmRecursiveFlag.Name) - client = swarm.NewClient(bzzapi) - ) - - if fi, err := os.Stat(dest); err == nil { - if isRecursive && !fi.Mode().IsDir() { - utils.Fatalf("destination path is not a directory!") - } - } else { - if !os.IsNotExist(err) { - utils.Fatalf("could not stat path: %v", err) - } - } - - uri, err := api.Parse(args[0]) - if err != nil { - utils.Fatalf("could not parse uri argument: %v", err) - } - - dl := func(credentials string) error { - // assume behaviour according to --recursive switch - if isRecursive { - if err := client.DownloadDirectory(uri.Addr, uri.Path, dest, credentials); err != nil { - if err == swarm.ErrUnauthorized { - return err - } - return fmt.Errorf("directory %s: %v", uri.Path, err) - } - } else { - // we are downloading a file - log.Debug("downloading file/path from a manifest", "uri.Addr", uri.Addr, "uri.Path", uri.Path) - - err := client.DownloadFile(uri.Addr, uri.Path, dest, credentials) - if err != nil { - if err == swarm.ErrUnauthorized { - return err - } - return fmt.Errorf("file %s from address: %s: %v", uri.Path, uri.Addr, err) - } - } - return nil - } - if passwords := makePasswordList(ctx); passwords != nil { - password := getPassPhrase(fmt.Sprintf("Downloading %s is restricted", uri), 0, passwords) - err = dl(password) - } else { - err = dl("") - } - if err != nil { - utils.Fatalf("download: %v", err) - } -} diff --git a/cmd/swarm/explore.go b/cmd/swarm/explore.go deleted file mode 100644 index 14090b3045a6..000000000000 --- a/cmd/swarm/explore.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -// Command bzzhash computes a swarm tree hash. -package main - -import ( - "context" - "fmt" - "os" - - "github.com/ubiq/go-ubiq/cmd/utils" - "github.com/ubiq/go-ubiq/swarm/storage" - "gopkg.in/urfave/cli.v1" -) - -var hashesCommand = cli.Command{ - Action: hashes, - CustomHelpTemplate: helpTemplate, - Name: "hashes", - Usage: "print all hashes of a file to STDOUT", - ArgsUsage: "", - Description: "Prints all hashes of a file to STDOUT", -} - -func hashes(ctx *cli.Context) { - args := ctx.Args() - if len(args) < 1 { - utils.Fatalf("Usage: swarm hashes ") - } - f, err := os.Open(args[0]) - if err != nil { - utils.Fatalf("Error opening file " + args[1]) - } - defer f.Close() - - fileStore := storage.NewFileStore(&storage.FakeChunkStore{}, storage.NewFileStoreParams()) - refs, err := fileStore.GetAllReferences(context.TODO(), f, false) - if err != nil { - utils.Fatalf("%v\n", err) - } else { - for _, r := range refs { - fmt.Println(r.String()) - } - } -} diff --git a/cmd/swarm/export_test.go b/cmd/swarm/export_test.go deleted file mode 100644 index 86bcf2332ab0..000000000000 --- a/cmd/swarm/export_test.go +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "bytes" - "crypto/md5" - "io" - "net/http" - "os" - "runtime" - "strings" - "testing" - - "github.com/ubiq/go-ubiq/swarm" - "github.com/ubiq/go-ubiq/swarm/testutil" -) - -// TestCLISwarmExportImport perform the following test: -// 1. runs swarm node -// 2. uploads a random file -// 3. runs an export of the local datastore -// 4. runs a second swarm node -// 5. imports the exported datastore -// 6. fetches the uploaded random file from the second node -func TestCLISwarmExportImport(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip() - } - cluster := newTestCluster(t, 1) - - // generate random 1mb file - content := testutil.RandomBytes(1, 1000000) - fileName := testutil.TempFileWithContent(t, string(content)) - defer os.Remove(fileName) - - // upload the file with 'swarm up' and expect a hash - up := runSwarm(t, "--bzzapi", cluster.Nodes[0].URL, "up", fileName) - _, matches := up.ExpectRegexp(`[a-f\d]{64}`) - up.ExpectExit() - hash := matches[0] - - var info swarm.Info - if err := cluster.Nodes[0].Client.Call(&info, "bzz_info"); err != nil { - t.Fatal(err) - } - - cluster.Stop() - defer cluster.Cleanup() - - // generate an export.tar - exportCmd := runSwarm(t, "db", "export", info.Path+"/chunks", info.Path+"/export.tar", strings.TrimPrefix(info.BzzKey, "0x")) - exportCmd.ExpectExit() - - // start second cluster - cluster2 := newTestCluster(t, 1) - - var info2 swarm.Info - if err := cluster2.Nodes[0].Client.Call(&info2, "bzz_info"); err != nil { - t.Fatal(err) - } - - // stop second cluster, so that we close LevelDB - cluster2.Stop() - defer cluster2.Cleanup() - - // import the export.tar - importCmd := runSwarm(t, "db", "import", info2.Path+"/chunks", info.Path+"/export.tar", strings.TrimPrefix(info2.BzzKey, "0x")) - importCmd.ExpectExit() - - // spin second cluster back up - cluster2.StartExistingNodes(t, 1, strings.TrimPrefix(info2.BzzAccount, "0x")) - - // try to fetch imported file - res, err := http.Get(cluster2.Nodes[0].URL + "/bzz:/" + hash) - if err != nil { - t.Fatal(err) - } - - if res.StatusCode != 200 { - t.Fatalf("expected HTTP status %d, got %s", 200, res.Status) - } - - // compare downloaded file with the generated random file - mustEqualFiles(t, bytes.NewReader(content), res.Body) -} - -func mustEqualFiles(t *testing.T, up io.Reader, down io.Reader) { - h := md5.New() - upLen, err := io.Copy(h, up) - if err != nil { - t.Fatal(err) - } - upHash := h.Sum(nil) - h.Reset() - downLen, err := io.Copy(h, down) - if err != nil { - t.Fatal(err) - } - downHash := h.Sum(nil) - - if !bytes.Equal(upHash, downHash) || upLen != downLen { - t.Fatalf("downloaded imported file md5=%x (length %v) is not the same as the generated one mp5=%x (length %v)", downHash, downLen, upHash, upLen) - } -} diff --git a/cmd/swarm/feeds.go b/cmd/swarm/feeds.go deleted file mode 100644 index 7272c169606c..000000000000 --- a/cmd/swarm/feeds.go +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -// Command feed allows the user to create and update signed Swarm feeds -package main - -import ( - "fmt" - "strings" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/common/hexutil" - "github.com/ubiq/go-ubiq/crypto" - - "github.com/ubiq/go-ubiq/cmd/utils" - swarm "github.com/ubiq/go-ubiq/swarm/api/client" - "github.com/ubiq/go-ubiq/swarm/storage/feed" - "gopkg.in/urfave/cli.v1" -) - -var feedCommand = cli.Command{ - CustomHelpTemplate: helpTemplate, - Name: "feed", - Usage: "(Advanced) Create and update Swarm Feeds", - ArgsUsage: "", - Description: "Works with Swarm Feeds", - Subcommands: []cli.Command{ - { - Action: feedCreateManifest, - CustomHelpTemplate: helpTemplate, - Name: "create", - Usage: "creates and publishes a new feed manifest", - Description: `creates and publishes a new feed manifest pointing to a specified user's updates about a particular topic. - The feed topic can be built in the following ways: - * use --topic to set the topic to an arbitrary binary hex string. - * use --name to set the topic to a human-readable name. - For example --name could be set to "profile-picture", meaning this feed allows to get this user's current profile picture. - * use both --topic and --name to create named subtopics. - For example, --topic could be set to an Ubiq contract address and --name could be set to "comments", meaning - this feed tracks a discussion about that contract. - The --user flag allows to have this manifest refer to a user other than yourself. If not specified, - it will then default to your local account (--bzzaccount)`, - Flags: []cli.Flag{SwarmFeedNameFlag, SwarmFeedTopicFlag, SwarmFeedUserFlag}, - }, - { - Action: feedUpdate, - CustomHelpTemplate: helpTemplate, - Name: "update", - Usage: "updates the content of an existing Swarm Feed", - ArgsUsage: "<0x Hex data>", - Description: `publishes a new update on the specified topic - The feed topic can be built in the following ways: - * use --topic to set the topic to an arbitrary binary hex string. - * use --name to set the topic to a human-readable name. - For example --name could be set to "profile-picture", meaning this feed allows to get this user's current profile picture. - * use both --topic and --name to create named subtopics. - For example, --topic could be set to an Ubiq contract address and --name could be set to "comments", meaning - this feed tracks a discussion about that contract. - - If you have a manifest, you can specify it with --manifest to refer to the feed, - instead of using --topic / --name - `, - Flags: []cli.Flag{SwarmFeedManifestFlag, SwarmFeedNameFlag, SwarmFeedTopicFlag}, - }, - { - Action: feedInfo, - CustomHelpTemplate: helpTemplate, - Name: "info", - Usage: "obtains information about an existing Swarm feed", - Description: `obtains information about an existing Swarm feed - The topic can be specified directly with the --topic flag as an hex string - If no topic is specified, the default topic (zero) will be used - The --name flag can be used to specify subtopics with a specific name. - The --user flag allows to refer to a user other than yourself. If not specified, - it will then default to your local account (--bzzaccount) - If you have a manifest, you can specify it with --manifest instead of --topic / --name / ---user - to refer to the feed`, - Flags: []cli.Flag{SwarmFeedManifestFlag, SwarmFeedNameFlag, SwarmFeedTopicFlag, SwarmFeedUserFlag}, - }, - }, -} - -func NewGenericSigner(ctx *cli.Context) feed.Signer { - return feed.NewGenericSigner(getPrivKey(ctx)) -} - -func getTopic(ctx *cli.Context) (topic feed.Topic) { - var name = ctx.String(SwarmFeedNameFlag.Name) - var relatedTopic = ctx.String(SwarmFeedTopicFlag.Name) - var relatedTopicBytes []byte - var err error - - if relatedTopic != "" { - relatedTopicBytes, err = hexutil.Decode(relatedTopic) - if err != nil { - utils.Fatalf("Error parsing topic: %s", err) - } - } - - topic, err = feed.NewTopic(name, relatedTopicBytes) - if err != nil { - utils.Fatalf("Error parsing topic: %s", err) - } - return topic -} - -// swarm feed create [--name ] [--data <0x Hexdata> [--multihash=false]] -// swarm feed update <0x Hexdata> [--multihash=false] -// swarm feed info - -func feedCreateManifest(ctx *cli.Context) { - var ( - bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/") - client = swarm.NewClient(bzzapi) - ) - - newFeedUpdateRequest := feed.NewFirstRequest(getTopic(ctx)) - newFeedUpdateRequest.Feed.User = feedGetUser(ctx) - - manifestAddress, err := client.CreateFeedWithManifest(newFeedUpdateRequest) - if err != nil { - utils.Fatalf("Error creating feed manifest: %s", err.Error()) - return - } - fmt.Println(manifestAddress) // output manifest address to the user in a single line (useful for other commands to pick up) - -} - -func feedUpdate(ctx *cli.Context) { - args := ctx.Args() - - var ( - bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/") - client = swarm.NewClient(bzzapi) - manifestAddressOrDomain = ctx.String(SwarmFeedManifestFlag.Name) - ) - - if len(args) < 1 { - fmt.Println("Incorrect number of arguments") - cli.ShowCommandHelpAndExit(ctx, "update", 1) - return - } - - signer := NewGenericSigner(ctx) - - data, err := hexutil.Decode(args[0]) - if err != nil { - utils.Fatalf("Error parsing data: %s", err.Error()) - return - } - - var updateRequest *feed.Request - var query *feed.Query - - if manifestAddressOrDomain == "" { - query = new(feed.Query) - query.User = signer.Address() - query.Topic = getTopic(ctx) - } - - // Retrieve a feed update request - updateRequest, err = client.GetFeedRequest(query, manifestAddressOrDomain) - if err != nil { - utils.Fatalf("Error retrieving feed status: %s", err.Error()) - } - - // Check that the provided signer matches the request to sign - if updateRequest.User != signer.Address() { - utils.Fatalf("Signer address does not match the update request") - } - - // set the new data - updateRequest.SetData(data) - - // sign update - if err = updateRequest.Sign(signer); err != nil { - utils.Fatalf("Error signing feed update: %s", err.Error()) - } - - // post update - err = client.UpdateFeed(updateRequest) - if err != nil { - utils.Fatalf("Error updating feed: %s", err.Error()) - return - } -} - -func feedInfo(ctx *cli.Context) { - var ( - bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/") - client = swarm.NewClient(bzzapi) - manifestAddressOrDomain = ctx.String(SwarmFeedManifestFlag.Name) - ) - - var query *feed.Query - if manifestAddressOrDomain == "" { - query = new(feed.Query) - query.Topic = getTopic(ctx) - query.User = feedGetUser(ctx) - } - - metadata, err := client.GetFeedRequest(query, manifestAddressOrDomain) - if err != nil { - utils.Fatalf("Error retrieving feed metadata: %s", err.Error()) - return - } - encodedMetadata, err := metadata.MarshalJSON() - if err != nil { - utils.Fatalf("Error encoding metadata to JSON for display:%s", err) - } - fmt.Println(string(encodedMetadata)) -} - -func feedGetUser(ctx *cli.Context) common.Address { - var user = ctx.String(SwarmFeedUserFlag.Name) - if user != "" { - return common.HexToAddress(user) - } - pk := getPrivKey(ctx) - if pk == nil { - utils.Fatalf("Cannot read private key. Must specify --user or --bzzaccount") - } - return crypto.PubkeyToAddress(pk.PublicKey) - -} diff --git a/cmd/swarm/feeds_test.go b/cmd/swarm/feeds_test.go deleted file mode 100644 index 432fbd547a90..000000000000 --- a/cmd/swarm/feeds_test.go +++ /dev/null @@ -1,196 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "bytes" - "encoding/json" - "io/ioutil" - "os" - "testing" - - "github.com/ubiq/go-ubiq/common/hexutil" - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/swarm/api" - swarm "github.com/ubiq/go-ubiq/swarm/api/client" - swarmhttp "github.com/ubiq/go-ubiq/swarm/api/http" - "github.com/ubiq/go-ubiq/swarm/storage/feed" - "github.com/ubiq/go-ubiq/swarm/storage/feed/lookup" - "github.com/ubiq/go-ubiq/swarm/testutil" -) - -func TestCLIFeedUpdate(t *testing.T) { - srv := swarmhttp.NewTestSwarmServer(t, func(api *api.API) swarmhttp.TestServer { - return swarmhttp.NewServer(api, "") - }, nil) - log.Info("starting a test swarm server") - defer srv.Close() - - // create a private key file for signing - privkeyHex := "0000000000000000000000000000000000000000000000000000000000001979" - privKey, _ := crypto.HexToECDSA(privkeyHex) - address := crypto.PubkeyToAddress(privKey.PublicKey) - - pkFileName := testutil.TempFileWithContent(t, privkeyHex) - defer os.Remove(pkFileName) - - // compose a topic. We'll be doing quotes about Miguel de Cervantes - var topic feed.Topic - subject := []byte("Miguel de Cervantes") - copy(topic[:], subject[:]) - name := "quotes" - - // prepare some data for the update - data := []byte("En boca cerrada no entran moscas") - hexData := hexutil.Encode(data) - - flags := []string{ - "--bzzapi", srv.URL, - "--bzzaccount", pkFileName, - "feed", "update", - "--topic", topic.Hex(), - "--name", name, - hexData} - - // create an update and expect an exit without errors - log.Info("updating a feed with 'swarm feed update'") - cmd := runSwarm(t, flags...) - cmd.ExpectExit() - - // now try to get the update using the client - client := swarm.NewClient(srv.URL) - - // build the same topic as before, this time - // we use NewTopic to create a topic automatically. - topic, err := feed.NewTopic(name, subject) - if err != nil { - t.Fatal(err) - } - - // Feed configures whose updates we will be looking up. - fd := feed.Feed{ - Topic: topic, - User: address, - } - - // Build a query to get the latest update - query := feed.NewQueryLatest(&fd, lookup.NoClue) - - // retrieve content! - reader, err := client.QueryFeed(query, "") - if err != nil { - t.Fatal(err) - } - - retrieved, err := ioutil.ReadAll(reader) - if err != nil { - t.Fatal(err) - } - - // check we retrieved the sent information - if !bytes.Equal(data, retrieved) { - t.Fatalf("Received %s, expected %s", retrieved, data) - } - - // Now retrieve info for the next update - flags = []string{ - "--bzzapi", srv.URL, - "feed", "info", - "--topic", topic.Hex(), - "--user", address.Hex(), - } - - log.Info("getting feed info with 'swarm feed info'") - cmd = runSwarm(t, flags...) - _, matches := cmd.ExpectRegexp(`.*`) // regex hack to extract stdout - cmd.ExpectExit() - - // verify we can deserialize the result as a valid JSON - var request feed.Request - err = json.Unmarshal([]byte(matches[0]), &request) - if err != nil { - t.Fatal(err) - } - - // make sure the retrieved feed is the same - if request.Feed != fd { - t.Fatalf("Expected feed to be: %s, got %s", fd, request.Feed) - } - - // test publishing a manifest - flags = []string{ - "--bzzapi", srv.URL, - "--bzzaccount", pkFileName, - "feed", "create", - "--topic", topic.Hex(), - } - - log.Info("Publishing manifest with 'swarm feed create'") - cmd = runSwarm(t, flags...) - _, matches = cmd.ExpectRegexp(`[a-f\d]{64}`) - cmd.ExpectExit() - - manifestAddress := matches[0] // read the received feed manifest - - // now attempt to lookup the latest update using a manifest instead - reader, err = client.QueryFeed(nil, manifestAddress) - if err != nil { - t.Fatal(err) - } - - retrieved, err = ioutil.ReadAll(reader) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(data, retrieved) { - t.Fatalf("Received %s, expected %s", retrieved, data) - } - - // test publishing a manifest for a different user - flags = []string{ - "--bzzapi", srv.URL, - "feed", "create", - "--topic", topic.Hex(), - "--user", "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", // different user - } - - log.Info("Publishing manifest with 'swarm feed create' for a different user") - cmd = runSwarm(t, flags...) - _, matches = cmd.ExpectRegexp(`[a-f\d]{64}`) - cmd.ExpectExit() - - manifestAddress = matches[0] // read the received feed manifest - - // now let's try to update that user's manifest which we don't have the private key for - flags = []string{ - "--bzzapi", srv.URL, - "--bzzaccount", pkFileName, - "feed", "update", - "--manifest", manifestAddress, - hexData} - - // create an update and expect an error given there is a user mismatch - log.Info("updating a feed with 'swarm feed update'") - cmd = runSwarm(t, flags...) - cmd.ExpectRegexp("Fatal:.*") // best way so far to detect a failure. - cmd.ExpectExit() - if cmd.ExitStatus() == 0 { - t.Fatal("Expected nonzero exit code when updating a manifest with the wrong user. Got 0.") - } -} diff --git a/cmd/swarm/flags.go b/cmd/swarm/flags.go deleted file mode 100644 index b092a77476c2..000000000000 --- a/cmd/swarm/flags.go +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -// Command feed allows the user to create and update signed Swarm feeds -package main - -import cli "gopkg.in/urfave/cli.v1" - -var ( - ChequebookAddrFlag = cli.StringFlag{ - Name: "chequebook", - Usage: "chequebook contract address", - EnvVar: SWARM_ENV_CHEQUEBOOK_ADDR, - } - SwarmAccountFlag = cli.StringFlag{ - Name: "bzzaccount", - Usage: "Swarm account key file", - EnvVar: SWARM_ENV_ACCOUNT, - } - SwarmListenAddrFlag = cli.StringFlag{ - Name: "httpaddr", - Usage: "Swarm HTTP API listening interface", - EnvVar: SWARM_ENV_LISTEN_ADDR, - } - SwarmPortFlag = cli.StringFlag{ - Name: "bzzport", - Usage: "Swarm local http api port", - EnvVar: SWARM_ENV_PORT, - } - SwarmNetworkIdFlag = cli.IntFlag{ - Name: "bzznetworkid", - Usage: "Network identifier (integer, default 3=swarm testnet)", - EnvVar: SWARM_ENV_NETWORK_ID, - } - SwarmSwapEnabledFlag = cli.BoolFlag{ - Name: "swap", - Usage: "Swarm SWAP enabled (default false)", - EnvVar: SWARM_ENV_SWAP_ENABLE, - } - SwarmSwapAPIFlag = cli.StringFlag{ - Name: "swap-api", - Usage: "URL of the Ethereum API provider to use to settle SWAP payments", - EnvVar: SWARM_ENV_SWAP_API, - } - SwarmSyncDisabledFlag = cli.BoolTFlag{ - Name: "nosync", - Usage: "Disable swarm syncing", - EnvVar: SWARM_ENV_SYNC_DISABLE, - } - SwarmSyncUpdateDelay = cli.DurationFlag{ - Name: "sync-update-delay", - Usage: "Duration for sync subscriptions update after no new peers are added (default 15s)", - EnvVar: SWARM_ENV_SYNC_UPDATE_DELAY, - } - SwarmMaxStreamPeerServersFlag = cli.IntFlag{ - Name: "max-stream-peer-servers", - Usage: "Limit of Stream peer servers, 0 denotes unlimited", - EnvVar: SWARM_ENV_MAX_STREAM_PEER_SERVERS, - Value: 10000, // A very large default value is possible as stream servers have very small memory footprint - } - SwarmLightNodeEnabled = cli.BoolFlag{ - Name: "lightnode", - Usage: "Enable Swarm LightNode (default false)", - EnvVar: SWARM_ENV_LIGHT_NODE_ENABLE, - } - SwarmDeliverySkipCheckFlag = cli.BoolFlag{ - Name: "delivery-skip-check", - Usage: "Skip chunk delivery check (default false)", - EnvVar: SWARM_ENV_DELIVERY_SKIP_CHECK, - } - EnsAPIFlag = cli.StringSliceFlag{ - Name: "ens-api", - Usage: "ENS API endpoint for a TLD and with contract address, can be repeated, format [tld:][contract-addr@]url", - EnvVar: SWARM_ENV_ENS_API, - } - SwarmApiFlag = cli.StringFlag{ - Name: "bzzapi", - Usage: "Specifies the Swarm HTTP endpoint to connect to", - Value: "http://127.0.0.1:8500", - } - SwarmRecursiveFlag = cli.BoolFlag{ - Name: "recursive", - Usage: "Upload directories recursively", - } - SwarmWantManifestFlag = cli.BoolTFlag{ - Name: "manifest", - Usage: "Automatic manifest upload (default true)", - } - SwarmUploadDefaultPath = cli.StringFlag{ - Name: "defaultpath", - Usage: "path to file served for empty url path (none)", - } - SwarmAccessGrantKeyFlag = cli.StringFlag{ - Name: "grant-key", - Usage: "grants a given public key access to an ACT", - } - SwarmAccessGrantKeysFlag = cli.StringFlag{ - Name: "grant-keys", - Usage: "grants a given list of public keys in the following file (separated by line breaks) access to an ACT", - } - SwarmUpFromStdinFlag = cli.BoolFlag{ - Name: "stdin", - Usage: "reads data to be uploaded from stdin", - } - SwarmUploadMimeType = cli.StringFlag{ - Name: "mime", - Usage: "Manually specify MIME type", - } - SwarmEncryptedFlag = cli.BoolFlag{ - Name: "encrypt", - Usage: "use encrypted upload", - } - SwarmAccessPasswordFlag = cli.StringFlag{ - Name: "password", - Usage: "Password", - EnvVar: SWARM_ACCESS_PASSWORD, - } - SwarmDryRunFlag = cli.BoolFlag{ - Name: "dry-run", - Usage: "dry-run", - } - CorsStringFlag = cli.StringFlag{ - Name: "corsdomain", - Usage: "Domain on which to send Access-Control-Allow-Origin header (multiple domains can be supplied separated by a ',')", - EnvVar: SWARM_ENV_CORS, - } - SwarmStorePath = cli.StringFlag{ - Name: "store.path", - Usage: "Path to leveldb chunk DB (default <$GETH_ENV_DIR>/swarm/bzz-<$BZZ_KEY>/chunks)", - EnvVar: SWARM_ENV_STORE_PATH, - } - SwarmStoreCapacity = cli.Uint64Flag{ - Name: "store.size", - Usage: "Number of chunks (5M is roughly 20-25GB) (default 5000000)", - EnvVar: SWARM_ENV_STORE_CAPACITY, - } - SwarmStoreCacheCapacity = cli.UintFlag{ - Name: "store.cache.size", - Usage: "Number of recent chunks cached in memory (default 5000)", - EnvVar: SWARM_ENV_STORE_CACHE_CAPACITY, - } - SwarmCompressedFlag = cli.BoolFlag{ - Name: "compressed", - Usage: "Prints encryption keys in compressed form", - } - SwarmBootnodeModeFlag = cli.BoolFlag{ - Name: "bootnode-mode", - Usage: "Run Swarm in Bootnode mode", - } - SwarmFeedNameFlag = cli.StringFlag{ - Name: "name", - Usage: "User-defined name for the new feed, limited to 32 characters. If combined with topic, it will refer to a subtopic with this name", - } - SwarmFeedTopicFlag = cli.StringFlag{ - Name: "topic", - Usage: "User-defined topic this feed is tracking, hex encoded. Limited to 64 hexadecimal characters", - } - SwarmFeedManifestFlag = cli.StringFlag{ - Name: "manifest", - Usage: "Refers to the feed through a manifest", - } - SwarmFeedUserFlag = cli.StringFlag{ - Name: "user", - Usage: "Indicates the user who updates the feed", - } - SwarmGlobalStoreAPIFlag = cli.StringFlag{ - Name: "globalstore-api", - Usage: "URL of the Global Store API provider (only for testing)", - EnvVar: SWARM_GLOBALSTORE_API, - } -) diff --git a/cmd/swarm/fs.go b/cmd/swarm/fs.go deleted file mode 100644 index bb1e953254bb..000000000000 --- a/cmd/swarm/fs.go +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "context" - "fmt" - "path/filepath" - "strings" - "time" - - "github.com/ubiq/go-ubiq/cmd/utils" - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/rpc" - "github.com/ubiq/go-ubiq/swarm/fuse" - "gopkg.in/urfave/cli.v1" -) - -var fsCommand = cli.Command{ - Name: "fs", - CustomHelpTemplate: helpTemplate, - Usage: "perform FUSE operations", - ArgsUsage: "fs COMMAND", - Description: "Performs FUSE operations by mounting/unmounting/listing mount points. This assumes you already have a Swarm node running locally. For all operation you must reference the correct path to bzzd.ipc in order to communicate with the node", - Subcommands: []cli.Command{ - { - Action: mount, - CustomHelpTemplate: helpTemplate, - Name: "mount", - Usage: "mount a swarm hash to a mount point", - ArgsUsage: "swarm fs mount ", - Description: "Mounts a Swarm manifest hash to a given mount point. This assumes you already have a Swarm node running locally. You must reference the correct path to your bzzd.ipc file", - }, - { - Action: unmount, - CustomHelpTemplate: helpTemplate, - Name: "unmount", - Usage: "unmount a swarmfs mount", - ArgsUsage: "swarm fs unmount ", - Description: "Unmounts a swarmfs mount residing at . This assumes you already have a Swarm node running locally. You must reference the correct path to your bzzd.ipc file", - }, - { - Action: listMounts, - CustomHelpTemplate: helpTemplate, - Name: "list", - Usage: "list swarmfs mounts", - ArgsUsage: "swarm fs list", - Description: "Lists all mounted swarmfs volumes. This assumes you already have a Swarm node running locally. You must reference the correct path to your bzzd.ipc file", - }, - }, -} - -func mount(cliContext *cli.Context) { - args := cliContext.Args() - if len(args) < 2 { - utils.Fatalf("Usage: swarm fs mount ") - } - - client, err := dialRPC(cliContext) - if err != nil { - utils.Fatalf("had an error dailing to RPC endpoint: %v", err) - } - defer client.Close() - - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - mf := &fuse.MountInfo{} - mountPoint, err := filepath.Abs(filepath.Clean(args[1])) - if err != nil { - utils.Fatalf("error expanding path for mount point: %v", err) - } - err = client.CallContext(ctx, mf, "swarmfs_mount", args[0], mountPoint) - if err != nil { - utils.Fatalf("had an error calling the RPC endpoint while mounting: %v", err) - } -} - -func unmount(cliContext *cli.Context) { - args := cliContext.Args() - - if len(args) < 1 { - utils.Fatalf("Usage: swarm fs unmount ") - } - client, err := dialRPC(cliContext) - if err != nil { - utils.Fatalf("had an error dailing to RPC endpoint: %v", err) - } - defer client.Close() - - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - mf := fuse.MountInfo{} - err = client.CallContext(ctx, &mf, "swarmfs_unmount", args[0]) - if err != nil { - utils.Fatalf("encountered an error calling the RPC endpoint while unmounting: %v", err) - } - fmt.Printf("%s\n", mf.LatestManifest) //print the latest manifest hash for user reference -} - -func listMounts(cliContext *cli.Context) { - client, err := dialRPC(cliContext) - if err != nil { - utils.Fatalf("had an error dailing to RPC endpoint: %v", err) - } - defer client.Close() - - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - mf := []fuse.MountInfo{} - err = client.CallContext(ctx, &mf, "swarmfs_listmounts") - if err != nil { - utils.Fatalf("encountered an error calling the RPC endpoint while listing mounts: %v", err) - } - if len(mf) == 0 { - fmt.Print("Could not found any swarmfs mounts. Please make sure you've specified the correct RPC endpoint\n") - } else { - fmt.Printf("Found %d swarmfs mount(s):\n", len(mf)) - for i, mountInfo := range mf { - fmt.Printf("%d:\n", i) - fmt.Printf("\tMount point: %s\n", mountInfo.MountPoint) - fmt.Printf("\tLatest Manifest: %s\n", mountInfo.LatestManifest) - fmt.Printf("\tStart Manifest: %s\n", mountInfo.StartManifest) - } - } -} - -func dialRPC(ctx *cli.Context) (*rpc.Client, error) { - endpoint := getIPCEndpoint(ctx) - log.Info("IPC endpoint", "path", endpoint) - return rpc.Dial(endpoint) -} - -func getIPCEndpoint(ctx *cli.Context) string { - cfg := defaultNodeConfig - utils.SetNodeConfig(ctx, &cfg) - - endpoint := cfg.IPCEndpoint() - - if strings.HasPrefix(endpoint, "rpc:") || strings.HasPrefix(endpoint, "ipc:") { - // Backwards compatibility with gubiq < 1.5 which required - // these prefixes. - endpoint = endpoint[4:] - } - return endpoint -} diff --git a/cmd/swarm/fs_test.go b/cmd/swarm/fs_test.go deleted file mode 100644 index 7e944a3ef6c6..000000000000 --- a/cmd/swarm/fs_test.go +++ /dev/null @@ -1,260 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -// +build linux freebsd - -package main - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "os" - "path/filepath" - "strings" - "testing" - "time" - - "github.com/ubiq/go-ubiq/cmd/utils" - "github.com/ubiq/go-ubiq/log" -) - -type testFile struct { - filePath string - content string -} - -// TestCLISwarmFsDefaultIPCPath tests if the most basic fs command, i.e., list -// can find and correctly connect to a running Swarm node on the default -// IPCPath. -func TestCLISwarmFsDefaultIPCPath(t *testing.T) { - cluster := newTestCluster(t, 1) - defer cluster.Shutdown() - - handlingNode := cluster.Nodes[0] - list := runSwarm(t, []string{ - "--datadir", handlingNode.Dir, - "fs", - "list", - }...) - - list.WaitExit() - if list.Err != nil { - t.Fatal(list.Err) - } -} - -// TestCLISwarmFs is a high-level test of swarmfs -// -// This test fails on travis for macOS as this executable exits with code 1 -// and without any log messages in the log: -// /Library/Filesystems/osxfuse.fs/Contents/Resources/load_osxfuse. -// This is the reason for this file not being built on darwin architecture. -func TestCLISwarmFs(t *testing.T) { - cluster := newTestCluster(t, 3) - defer cluster.Shutdown() - - // create a tmp dir - mountPoint, err := ioutil.TempDir("", "swarm-test") - log.Debug("swarmfs cli test", "1st mount", mountPoint) - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(mountPoint) - - handlingNode := cluster.Nodes[0] - mhash := doUploadEmptyDir(t, handlingNode) - log.Debug("swarmfs cli test: mounting first run", "ipc path", filepath.Join(handlingNode.Dir, handlingNode.IpcPath)) - - mount := runSwarm(t, []string{ - fmt.Sprintf("--%s", utils.IPCPathFlag.Name), filepath.Join(handlingNode.Dir, handlingNode.IpcPath), - "fs", - "mount", - mhash, - mountPoint, - }...) - mount.ExpectExit() - - filesToAssert := []*testFile{} - - dirPath, err := createDirInDir(mountPoint, "testSubDir") - if err != nil { - t.Fatal(err) - } - dirPath2, err := createDirInDir(dirPath, "AnotherTestSubDir") - if err != nil { - t.Fatal(err) - } - - dummyContent := "somerandomtestcontentthatshouldbeasserted" - dirs := []string{ - mountPoint, - dirPath, - dirPath2, - } - files := []string{"f1.tmp", "f2.tmp"} - for _, d := range dirs { - for _, entry := range files { - tFile, err := createTestFileInPath(d, entry, dummyContent) - if err != nil { - t.Fatal(err) - } - filesToAssert = append(filesToAssert, tFile) - } - } - if len(filesToAssert) != len(dirs)*len(files) { - t.Fatalf("should have %d files to assert now, got %d", len(dirs)*len(files), len(filesToAssert)) - } - hashRegexp := `[a-f\d]{64}` - log.Debug("swarmfs cli test: unmounting first run...", "ipc path", filepath.Join(handlingNode.Dir, handlingNode.IpcPath)) - - unmount := runSwarm(t, []string{ - fmt.Sprintf("--%s", utils.IPCPathFlag.Name), filepath.Join(handlingNode.Dir, handlingNode.IpcPath), - "fs", - "unmount", - mountPoint, - }...) - _, matches := unmount.ExpectRegexp(hashRegexp) - unmount.ExpectExit() - - hash := matches[0] - if hash == mhash { - t.Fatal("this should not be equal") - } - log.Debug("swarmfs cli test: asserting no files in mount point") - - //check that there's nothing in the mount folder - filesInDir, err := ioutil.ReadDir(mountPoint) - if err != nil { - t.Fatalf("had an error reading the directory: %v", err) - } - - if len(filesInDir) != 0 { - t.Fatal("there shouldn't be anything here") - } - - secondMountPoint, err := ioutil.TempDir("", "swarm-test") - log.Debug("swarmfs cli test", "2nd mount point at", secondMountPoint) - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(secondMountPoint) - - log.Debug("swarmfs cli test: remounting at second mount point", "ipc path", filepath.Join(handlingNode.Dir, handlingNode.IpcPath)) - - //remount, check files - newMount := runSwarm(t, []string{ - fmt.Sprintf("--%s", utils.IPCPathFlag.Name), filepath.Join(handlingNode.Dir, handlingNode.IpcPath), - "fs", - "mount", - hash, // the latest hash - secondMountPoint, - }...) - - newMount.ExpectExit() - time.Sleep(1 * time.Second) - - filesInDir, err = ioutil.ReadDir(secondMountPoint) - if err != nil { - t.Fatal(err) - } - - if len(filesInDir) == 0 { - t.Fatal("there should be something here") - } - - log.Debug("swarmfs cli test: traversing file tree to see it matches previous mount") - - for _, file := range filesToAssert { - file.filePath = strings.Replace(file.filePath, mountPoint, secondMountPoint, -1) - fileBytes, err := ioutil.ReadFile(file.filePath) - - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(fileBytes, bytes.NewBufferString(file.content).Bytes()) { - t.Fatal("this should be equal") - } - } - - log.Debug("swarmfs cli test: unmounting second run", "ipc path", filepath.Join(handlingNode.Dir, handlingNode.IpcPath)) - - unmountSec := runSwarm(t, []string{ - fmt.Sprintf("--%s", utils.IPCPathFlag.Name), filepath.Join(handlingNode.Dir, handlingNode.IpcPath), - "fs", - "unmount", - secondMountPoint, - }...) - - _, matches = unmountSec.ExpectRegexp(hashRegexp) - unmountSec.ExpectExit() - - if matches[0] != hash { - t.Fatal("these should be equal - no changes made") - } -} - -func doUploadEmptyDir(t *testing.T, node *testNode) string { - // create a tmp dir - tmpDir, err := ioutil.TempDir("", "swarm-test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpDir) - - hashRegexp := `[a-f\d]{64}` - - flags := []string{ - "--bzzapi", node.URL, - "--recursive", - "up", - tmpDir} - - log.Info("swarmfs cli test: uploading dir with 'swarm up'") - up := runSwarm(t, flags...) - _, matches := up.ExpectRegexp(hashRegexp) - up.ExpectExit() - hash := matches[0] - log.Info("swarmfs cli test: dir uploaded", "hash", hash) - return hash -} - -func createDirInDir(createInDir string, dirToCreate string) (string, error) { - fullpath := filepath.Join(createInDir, dirToCreate) - err := os.MkdirAll(fullpath, 0777) - if err != nil { - return "", err - } - return fullpath, nil -} - -func createTestFileInPath(dir, filename, content string) (*testFile, error) { - tFile := &testFile{} - filePath := filepath.Join(dir, filename) - if file, err := os.Create(filePath); err == nil { - tFile.content = content - tFile.filePath = filePath - - _, err = io.WriteString(file, content) - if err != nil { - return nil, err - } - file.Close() - } - - return tFile, nil -} diff --git a/cmd/swarm/global-store/global_store.go b/cmd/swarm/global-store/global_store.go deleted file mode 100644 index 26473bb5834c..000000000000 --- a/cmd/swarm/global-store/global_store.go +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "net" - "net/http" - "os" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/rpc" - "github.com/ubiq/go-ubiq/swarm/storage/mock" - "github.com/ubiq/go-ubiq/swarm/storage/mock/db" - "github.com/ubiq/go-ubiq/swarm/storage/mock/mem" - cli "gopkg.in/urfave/cli.v1" -) - -// startHTTP starts a global store with HTTP RPC server. -// It is used for "http" cli command. -func startHTTP(ctx *cli.Context) (err error) { - server, cleanup, err := newServer(ctx) - if err != nil { - return err - } - defer cleanup() - - listener, err := net.Listen("tcp", ctx.String("addr")) - if err != nil { - return err - } - log.Info("http", "address", listener.Addr().String()) - - return http.Serve(listener, server) -} - -// startWS starts a global store with WebSocket RPC server. -// It is used for "websocket" cli command. -func startWS(ctx *cli.Context) (err error) { - server, cleanup, err := newServer(ctx) - if err != nil { - return err - } - defer cleanup() - - listener, err := net.Listen("tcp", ctx.String("addr")) - if err != nil { - return err - } - origins := ctx.StringSlice("origins") - log.Info("websocket", "address", listener.Addr().String(), "origins", origins) - - return http.Serve(listener, server.WebsocketHandler(origins)) -} - -// newServer creates a global store and returns its RPC server. -// Returned cleanup function should be called only if err is nil. -func newServer(ctx *cli.Context) (server *rpc.Server, cleanup func(), err error) { - log.PrintOrigins(true) - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(ctx.Int("verbosity")), log.StreamHandler(os.Stdout, log.TerminalFormat(false)))) - - cleanup = func() {} - var globalStore mock.GlobalStorer - dir := ctx.String("dir") - if dir != "" { - dbStore, err := db.NewGlobalStore(dir) - if err != nil { - return nil, nil, err - } - cleanup = func() { - dbStore.Close() - } - globalStore = dbStore - log.Info("database global store", "dir", dir) - } else { - globalStore = mem.NewGlobalStore() - log.Info("in-memory global store") - } - - server = rpc.NewServer() - if err := server.RegisterName("mockStore", globalStore); err != nil { - cleanup() - return nil, nil, err - } - - return server, cleanup, nil -} diff --git a/cmd/swarm/global-store/global_store_test.go b/cmd/swarm/global-store/global_store_test.go deleted file mode 100644 index 3a47a3d02a01..000000000000 --- a/cmd/swarm/global-store/global_store_test.go +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "context" - "io/ioutil" - "net" - "net/http" - "os" - "testing" - "time" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/rpc" - mockRPC "github.com/ubiq/go-ubiq/swarm/storage/mock/rpc" -) - -// TestHTTP_InMemory tests in-memory global store that exposes -// HTTP server. -func TestHTTP_InMemory(t *testing.T) { - testHTTP(t, true) -} - -// TestHTTP_Database tests global store with persisted database -// that exposes HTTP server. -func TestHTTP_Database(t *testing.T) { - dir, err := ioutil.TempDir("", "swarm-global-store-") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - - // create a fresh global store - testHTTP(t, true, "--dir", dir) - - // check if data saved by the previous global store instance - testHTTP(t, false, "--dir", dir) -} - -// testWebsocket starts global store binary with HTTP server -// and validates that it can store and retrieve data. -// If put is false, no data will be stored, only retrieved, -// giving the possibility to check if data is present in the -// storage directory. -func testHTTP(t *testing.T, put bool, args ...string) { - addr := findFreeTCPAddress(t) - testCmd := runGlobalStore(t, append([]string{"http", "--addr", addr}, args...)...) - defer testCmd.Interrupt() - - client, err := rpc.DialHTTP("http://" + addr) - if err != nil { - t.Fatal(err) - } - - // wait until global store process is started as - // rpc.DialHTTP is actually not connecting - for i := 0; i < 1000; i++ { - _, err = http.DefaultClient.Get("http://" + addr) - if err == nil { - break - } - time.Sleep(10 * time.Millisecond) - } - if err != nil { - t.Fatal(err) - } - - store := mockRPC.NewGlobalStore(client) - defer store.Close() - - node := store.NewNodeStore(common.HexToAddress("123abc")) - - wantKey := "key" - wantValue := "value" - - if put { - err = node.Put([]byte(wantKey), []byte(wantValue)) - if err != nil { - t.Fatal(err) - } - } - - gotValue, err := node.Get([]byte(wantKey)) - if err != nil { - t.Fatal(err) - } - - if string(gotValue) != wantValue { - t.Errorf("got value %s for key %s, want %s", string(gotValue), wantKey, wantValue) - } -} - -// TestWebsocket_InMemory tests in-memory global store that exposes -// WebSocket server. -func TestWebsocket_InMemory(t *testing.T) { - testWebsocket(t, true) -} - -// TestWebsocket_Database tests global store with persisted database -// that exposes HTTP server. -func TestWebsocket_Database(t *testing.T) { - dir, err := ioutil.TempDir("", "swarm-global-store-") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - - // create a fresh global store - testWebsocket(t, true, "--dir", dir) - - // check if data saved by the previous global store instance - testWebsocket(t, false, "--dir", dir) -} - -// testWebsocket starts global store binary with WebSocket server -// and validates that it can store and retrieve data. -// If put is false, no data will be stored, only retrieved, -// giving the possibility to check if data is present in the -// storage directory. -func testWebsocket(t *testing.T, put bool, args ...string) { - addr := findFreeTCPAddress(t) - testCmd := runGlobalStore(t, append([]string{"ws", "--addr", addr}, args...)...) - defer testCmd.Interrupt() - - var client *rpc.Client - var err error - // wait until global store process is started - for i := 0; i < 1000; i++ { - client, err = rpc.DialWebsocket(context.Background(), "ws://"+addr, "") - if err == nil { - break - } - time.Sleep(10 * time.Millisecond) - } - if err != nil { - t.Fatal(err) - } - - store := mockRPC.NewGlobalStore(client) - defer store.Close() - - node := store.NewNodeStore(common.HexToAddress("123abc")) - - wantKey := "key" - wantValue := "value" - - if put { - err = node.Put([]byte(wantKey), []byte(wantValue)) - if err != nil { - t.Fatal(err) - } - } - - gotValue, err := node.Get([]byte(wantKey)) - if err != nil { - t.Fatal(err) - } - - if string(gotValue) != wantValue { - t.Errorf("got value %s for key %s, want %s", string(gotValue), wantKey, wantValue) - } -} - -// findFreeTCPAddress returns a local address (IP:Port) to which -// global store can listen on. -func findFreeTCPAddress(t *testing.T) (addr string) { - t.Helper() - - listener, err := net.Listen("tcp", "") - if err != nil { - t.Fatal(err) - } - defer listener.Close() - - return listener.Addr().String() -} diff --git a/cmd/swarm/global-store/main.go b/cmd/swarm/global-store/main.go deleted file mode 100644 index 5eb6ed9e2335..000000000000 --- a/cmd/swarm/global-store/main.go +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "os" - - "github.com/ubiq/go-ubiq/cmd/utils" - "github.com/ubiq/go-ubiq/log" - cli "gopkg.in/urfave/cli.v1" -) - -var gitCommit string // Git SHA1 commit hash of the release (set via linker flags) - -func main() { - err := newApp().Run(os.Args) - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } -} - -// newApp construct a new instance of Swarm Global Store. -// Method Run is called on it in the main function and in tests. -func newApp() (app *cli.App) { - app = utils.NewApp(gitCommit, "Swarm Global Store") - - app.Name = "global-store" - - // app flags (for all commands) - app.Flags = []cli.Flag{ - cli.IntFlag{ - Name: "verbosity", - Value: 3, - Usage: "verbosity level", - }, - } - - app.Commands = []cli.Command{ - { - Name: "http", - Aliases: []string{"h"}, - Usage: "start swarm global store with http server", - Action: startHTTP, - // Flags only for "start" command. - // Allow app flags to be specified after the - // command argument. - Flags: append(app.Flags, - cli.StringFlag{ - Name: "dir", - Value: "", - Usage: "data directory", - }, - cli.StringFlag{ - Name: "addr", - Value: "0.0.0.0:3033", - Usage: "address to listen for http connection", - }, - ), - }, - { - Name: "websocket", - Aliases: []string{"ws"}, - Usage: "start swarm global store with websocket server", - Action: startWS, - // Flags only for "start" command. - // Allow app flags to be specified after the - // command argument. - Flags: append(app.Flags, - cli.StringFlag{ - Name: "dir", - Value: "", - Usage: "data directory", - }, - cli.StringFlag{ - Name: "addr", - Value: "0.0.0.0:3033", - Usage: "address to listen for websocket connection", - }, - cli.StringSliceFlag{ - Name: "origins", - Value: &cli.StringSlice{"*"}, - Usage: "websocket origins", - }, - ), - }, - } - - return app -} diff --git a/cmd/swarm/global-store/run_test.go b/cmd/swarm/global-store/run_test.go deleted file mode 100644 index d58dd5d2d2f7..000000000000 --- a/cmd/swarm/global-store/run_test.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "fmt" - "os" - "testing" - - "github.com/docker/docker/pkg/reexec" - "github.com/ubiq/go-ubiq/internal/cmdtest" -) - -func init() { - reexec.Register("swarm-global-store", func() { - if err := newApp().Run(os.Args); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - os.Exit(0) - }) -} - -func runGlobalStore(t *testing.T, args ...string) *cmdtest.TestCmd { - tt := cmdtest.NewTestCmd(t, nil) - tt.Run("swarm-global-store", args...) - return tt -} - -func TestMain(m *testing.M) { - if reexec.Init() { - return - } - os.Exit(m.Run()) -} diff --git a/cmd/swarm/hash.go b/cmd/swarm/hash.go deleted file mode 100644 index 2f34e064c771..000000000000 --- a/cmd/swarm/hash.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -// Command bzzhash computes a swarm tree hash. -package main - -import ( - "context" - "fmt" - "os" - - "github.com/ubiq/go-ubiq/cmd/utils" - "github.com/ubiq/go-ubiq/swarm/storage" - "gopkg.in/urfave/cli.v1" -) - -var hashCommand = cli.Command{ - Action: hash, - CustomHelpTemplate: helpTemplate, - Name: "hash", - Usage: "print the swarm hash of a file or directory", - ArgsUsage: "", - Description: "Prints the swarm hash of file or directory", -} - -func hash(ctx *cli.Context) { - args := ctx.Args() - if len(args) < 1 { - utils.Fatalf("Usage: swarm hash ") - } - f, err := os.Open(args[0]) - if err != nil { - utils.Fatalf("Error opening file " + args[1]) - } - defer f.Close() - - stat, _ := f.Stat() - fileStore := storage.NewFileStore(&storage.FakeChunkStore{}, storage.NewFileStoreParams()) - addr, _, err := fileStore.Store(context.TODO(), f, stat.Size(), false) - if err != nil { - utils.Fatalf("%v\n", err) - } else { - fmt.Printf("%v\n", addr) - } -} diff --git a/cmd/swarm/list.go b/cmd/swarm/list.go deleted file mode 100644 index 1768dbf0db57..000000000000 --- a/cmd/swarm/list.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "fmt" - "os" - "strings" - "text/tabwriter" - - "github.com/ubiq/go-ubiq/cmd/utils" - swarm "github.com/ubiq/go-ubiq/swarm/api/client" - "gopkg.in/urfave/cli.v1" -) - -var listCommand = cli.Command{ - Action: list, - CustomHelpTemplate: helpTemplate, - Name: "ls", - Usage: "list files and directories contained in a manifest", - ArgsUsage: " []", - Description: "Lists files and directories contained in a manifest", -} - -func list(ctx *cli.Context) { - args := ctx.Args() - - if len(args) < 1 { - utils.Fatalf("Please supply a manifest reference as the first argument") - } else if len(args) > 2 { - utils.Fatalf("Too many arguments - usage 'swarm ls manifest [prefix]'") - } - manifest := args[0] - - var prefix string - if len(args) == 2 { - prefix = args[1] - } - - bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/") - client := swarm.NewClient(bzzapi) - list, err := client.List(manifest, prefix, "") - if err != nil { - utils.Fatalf("Failed to generate file and directory list: %s", err) - } - - w := tabwriter.NewWriter(os.Stdout, 1, 2, 2, ' ', 0) - defer w.Flush() - fmt.Fprintln(w, "HASH\tCONTENT TYPE\tPATH") - for _, prefix := range list.CommonPrefixes { - fmt.Fprintf(w, "%s\t%s\t%s\n", "", "DIR", prefix) - } - for _, entry := range list.Entries { - fmt.Fprintf(w, "%s\t%s\t%s\n", entry.Hash, entry.ContentType, entry.Path) - } -} diff --git a/cmd/swarm/main.go b/cmd/swarm/main.go deleted file mode 100644 index 93de30b79b9b..000000000000 --- a/cmd/swarm/main.go +++ /dev/null @@ -1,469 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "crypto/ecdsa" - "encoding/hex" - "fmt" - "io/ioutil" - "os" - "os/signal" - "runtime" - "sort" - "strconv" - "strings" - "syscall" - - "github.com/ubiq/go-ubiq/accounts" - "github.com/ubiq/go-ubiq/accounts/keystore" - "github.com/ubiq/go-ubiq/cmd/utils" - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/console" - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/internal/debug" - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/rpc" - "github.com/ubiq/go-ubiq/swarm" - bzzapi "github.com/ubiq/go-ubiq/swarm/api" - swarmmetrics "github.com/ubiq/go-ubiq/swarm/metrics" - "github.com/ubiq/go-ubiq/swarm/storage/mock" - mockrpc "github.com/ubiq/go-ubiq/swarm/storage/mock/rpc" - "github.com/ubiq/go-ubiq/swarm/tracing" - sv "github.com/ubiq/go-ubiq/swarm/version" - - cli "gopkg.in/urfave/cli.v1" -) - -const clientIdentifier = "swarm" -const helpTemplate = `NAME: -{{.HelpName}} - {{.Usage}} - -USAGE: -{{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} - -CATEGORY: -{{.Category}}{{end}}{{if .Description}} - -DESCRIPTION: -{{.Description}}{{end}}{{if .VisibleFlags}} - -OPTIONS: -{{range .VisibleFlags}}{{.}} -{{end}}{{end}} -` - -// Git SHA1 commit hash of the release (set via linker flags) -// this variable will be assigned if corresponding parameter is passed with install, but not with test -// e.g.: go install -ldflags "-X main.gitCommit=ed1312d01b19e04ef578946226e5d8069d5dfd5a" ./cmd/swarm -var gitCommit string - -//declare a few constant error messages, useful for later error check comparisons in test -var ( - SWARM_ERR_NO_BZZACCOUNT = "bzzaccount option is required but not set; check your config file, command line or environment variables" - SWARM_ERR_SWAP_SET_NO_API = "SWAP is enabled but --swap-api is not set" -) - -// this help command gets added to any subcommand that does not define it explicitly -var defaultSubcommandHelp = cli.Command{ - Action: func(ctx *cli.Context) { cli.ShowCommandHelpAndExit(ctx, "", 1) }, - CustomHelpTemplate: helpTemplate, - Name: "help", - Usage: "shows this help", - Hidden: true, -} - -var defaultNodeConfig = node.DefaultConfig - -// This init function sets defaults so cmd/swarm can run alongside gubiq. -func init() { - sv.GitCommit = gitCommit - defaultNodeConfig.Name = clientIdentifier - defaultNodeConfig.Version = sv.VersionWithCommit(gitCommit) - defaultNodeConfig.P2P.ListenAddr = ":30399" - defaultNodeConfig.IPCPath = "bzzd.ipc" - // Set flag defaults for --help display. - utils.ListenPortFlag.Value = 30399 -} - -var app = utils.NewApp("", "Ubiq Swarm") - -// This init function creates the cli.App. -func init() { - app.Action = bzzd - app.Version = sv.ArchiveVersion(gitCommit) - app.Copyright = "Copyright 2013-2016 The go-ubiq Authors" - app.Commands = []cli.Command{ - { - Action: version, - CustomHelpTemplate: helpTemplate, - Name: "version", - Usage: "Print version numbers", - Description: "The output of this command is supposed to be machine-readable", - }, - { - Action: keys, - CustomHelpTemplate: helpTemplate, - Name: "print-keys", - Flags: []cli.Flag{SwarmCompressedFlag}, - Usage: "Print public key information", - Description: "The output of this command is supposed to be machine-readable", - }, - // See upload.go - upCommand, - // See access.go - accessCommand, - // See feeds.go - feedCommand, - // See list.go - listCommand, - // See hash.go - hashCommand, - // See download.go - downloadCommand, - // See manifest.go - manifestCommand, - // See fs.go - fsCommand, - // See db.go - dbCommand, - // See config.go - DumpConfigCommand, - // hashesCommand - hashesCommand, - } - - // append a hidden help subcommand to all commands that have subcommands - // if a help command was already defined above, that one will take precedence. - addDefaultHelpSubcommands(app.Commands) - - sort.Sort(cli.CommandsByName(app.Commands)) - - app.Flags = []cli.Flag{ - utils.IdentityFlag, - utils.DataDirFlag, - utils.BootnodesFlag, - utils.KeyStoreDirFlag, - utils.ListenPortFlag, - utils.DiscoveryV5Flag, - utils.NetrestrictFlag, - utils.NodeKeyFileFlag, - utils.NodeKeyHexFlag, - utils.MaxPeersFlag, - utils.NATFlag, - utils.IPCDisabledFlag, - utils.IPCPathFlag, - utils.PasswordFileFlag, - // bzzd-specific flags - CorsStringFlag, - EnsAPIFlag, - SwarmTomlConfigPathFlag, - SwarmSwapEnabledFlag, - SwarmSwapAPIFlag, - SwarmSyncDisabledFlag, - SwarmSyncUpdateDelay, - SwarmMaxStreamPeerServersFlag, - SwarmLightNodeEnabled, - SwarmDeliverySkipCheckFlag, - SwarmListenAddrFlag, - SwarmPortFlag, - SwarmAccountFlag, - SwarmNetworkIdFlag, - ChequebookAddrFlag, - // upload flags - SwarmApiFlag, - SwarmRecursiveFlag, - SwarmWantManifestFlag, - SwarmUploadDefaultPath, - SwarmUpFromStdinFlag, - SwarmUploadMimeType, - // bootnode mode - SwarmBootnodeModeFlag, - // storage flags - SwarmStorePath, - SwarmStoreCapacity, - SwarmStoreCacheCapacity, - SwarmGlobalStoreAPIFlag, - } - rpcFlags := []cli.Flag{ - utils.WSEnabledFlag, - utils.WSListenAddrFlag, - utils.WSPortFlag, - utils.WSApiFlag, - utils.WSAllowedOriginsFlag, - } - app.Flags = append(app.Flags, rpcFlags...) - app.Flags = append(app.Flags, debug.Flags...) - app.Flags = append(app.Flags, swarmmetrics.Flags...) - app.Flags = append(app.Flags, tracing.Flags...) - app.Before = func(ctx *cli.Context) error { - runtime.GOMAXPROCS(runtime.NumCPU()) - if err := debug.Setup(ctx, ""); err != nil { - return err - } - swarmmetrics.Setup(ctx) - tracing.Setup(ctx) - return nil - } - app.After = func(ctx *cli.Context) error { - debug.Exit() - return nil - } -} - -func main() { - if err := app.Run(os.Args); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } -} - -func keys(ctx *cli.Context) error { - privateKey := getPrivKey(ctx) - pubkey := crypto.FromECDSAPub(&privateKey.PublicKey) - pubkeyhex := hex.EncodeToString(pubkey) - pubCompressed := hex.EncodeToString(crypto.CompressPubkey(&privateKey.PublicKey)) - bzzkey := crypto.Keccak256Hash(pubkey).Hex() - - if !ctx.Bool(SwarmCompressedFlag.Name) { - fmt.Println(fmt.Sprintf("bzzkey=%s", bzzkey[2:])) - fmt.Println(fmt.Sprintf("publicKey=%s", pubkeyhex)) - } - fmt.Println(fmt.Sprintf("publicKeyCompressed=%s", pubCompressed)) - - return nil -} - -func version(ctx *cli.Context) error { - fmt.Println(strings.Title(clientIdentifier)) - fmt.Println("Version:", sv.VersionWithMeta) - if gitCommit != "" { - fmt.Println("Git Commit:", gitCommit) - } - fmt.Println("Go Version:", runtime.Version()) - fmt.Println("OS:", runtime.GOOS) - return nil -} - -func bzzd(ctx *cli.Context) error { - //build a valid bzzapi.Config from all available sources: - //default config, file config, command line and env vars - - bzzconfig, err := buildConfig(ctx) - if err != nil { - utils.Fatalf("unable to configure swarm: %v", err) - } - - cfg := defaultNodeConfig - - //pss operates on ws - cfg.WSModules = append(cfg.WSModules, "pss") - - //gubiq only supports --datadir via command line - //in order to be consistent within swarm, if we pass --datadir via environment variable - //or via config file, we get the same directory for gubiq and swarm - if _, err := os.Stat(bzzconfig.Path); err == nil { - cfg.DataDir = bzzconfig.Path - } - - //optionally set the bootnodes before configuring the node - setSwarmBootstrapNodes(ctx, &cfg) - //setup the ethereum node - utils.SetNodeConfig(ctx, &cfg) - - //always disable discovery from p2p package - swarm discovery is done with the `hive` protocol - cfg.P2P.NoDiscovery = true - - stack, err := node.New(&cfg) - if err != nil { - utils.Fatalf("can't create node: %v", err) - } - - //a few steps need to be done after the config phase is completed, - //due to overriding behavior - initSwarmNode(bzzconfig, stack, ctx) - //register BZZ as node.Service in the ethereum node - registerBzzService(bzzconfig, stack) - //start the node - utils.StartNode(stack) - - go func() { - sigc := make(chan os.Signal, 1) - signal.Notify(sigc, syscall.SIGTERM) - defer signal.Stop(sigc) - <-sigc - log.Info("Got sigterm, shutting swarm down...") - stack.Stop() - }() - - // add swarm bootnodes, because swarm doesn't use p2p package's discovery discv5 - go func() { - s := stack.Server() - - for _, n := range cfg.P2P.BootstrapNodes { - s.AddPeer(n) - } - }() - - stack.Wait() - return nil -} - -func registerBzzService(bzzconfig *bzzapi.Config, stack *node.Node) { - //define the swarm service boot function - boot := func(_ *node.ServiceContext) (node.Service, error) { - var nodeStore *mock.NodeStore - if bzzconfig.GlobalStoreAPI != "" { - // connect to global store - client, err := rpc.Dial(bzzconfig.GlobalStoreAPI) - if err != nil { - return nil, fmt.Errorf("global store: %v", err) - } - globalStore := mockrpc.NewGlobalStore(client) - // create a node store for this swarm key on global store - nodeStore = globalStore.NewNodeStore(common.HexToAddress(bzzconfig.BzzKey)) - } - return swarm.NewSwarm(bzzconfig, nodeStore) - } - //register within the ethereum node - if err := stack.Register(boot); err != nil { - utils.Fatalf("Failed to register the Swarm service: %v", err) - } -} - -func getAccount(bzzaccount string, ctx *cli.Context, stack *node.Node) *ecdsa.PrivateKey { - //an account is mandatory - if bzzaccount == "" { - utils.Fatalf(SWARM_ERR_NO_BZZACCOUNT) - } - // Try to load the arg as a hex key file. - if key, err := crypto.LoadECDSA(bzzaccount); err == nil { - log.Info("Swarm account key loaded", "address", crypto.PubkeyToAddress(key.PublicKey)) - return key - } - // Otherwise try getting it from the keystore. - am := stack.AccountManager() - ks := am.Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore) - - return decryptStoreAccount(ks, bzzaccount, utils.MakePasswordList(ctx)) -} - -// getPrivKey returns the private key of the specified bzzaccount -// Used only by client commands, such as `feed` -func getPrivKey(ctx *cli.Context) *ecdsa.PrivateKey { - // booting up the swarm node just as we do in bzzd action - bzzconfig, err := buildConfig(ctx) - if err != nil { - utils.Fatalf("unable to configure swarm: %v", err) - } - cfg := defaultNodeConfig - if _, err := os.Stat(bzzconfig.Path); err == nil { - cfg.DataDir = bzzconfig.Path - } - utils.SetNodeConfig(ctx, &cfg) - stack, err := node.New(&cfg) - if err != nil { - utils.Fatalf("can't create node: %v", err) - } - return getAccount(bzzconfig.BzzAccount, ctx, stack) -} - -func decryptStoreAccount(ks *keystore.KeyStore, account string, passwords []string) *ecdsa.PrivateKey { - var a accounts.Account - var err error - if common.IsHexAddress(account) { - a, err = ks.Find(accounts.Account{Address: common.HexToAddress(account)}) - } else if ix, ixerr := strconv.Atoi(account); ixerr == nil && ix > 0 { - if accounts := ks.Accounts(); len(accounts) > ix { - a = accounts[ix] - } else { - err = fmt.Errorf("index %d higher than number of accounts %d", ix, len(accounts)) - } - } else { - utils.Fatalf("Can't find swarm account key %s", account) - } - if err != nil { - utils.Fatalf("Can't find swarm account key: %v - Is the provided bzzaccount(%s) from the right datadir/Path?", err, account) - } - keyjson, err := ioutil.ReadFile(a.URL.Path) - if err != nil { - utils.Fatalf("Can't load swarm account key: %v", err) - } - for i := 0; i < 3; i++ { - password := getPassPhrase(fmt.Sprintf("Unlocking swarm account %s [%d/3]", a.Address.Hex(), i+1), i, passwords) - key, err := keystore.DecryptKey(keyjson, password) - if err == nil { - return key.PrivateKey - } - } - utils.Fatalf("Can't decrypt swarm account key") - return nil -} - -// getPassPhrase retrieves the password associated with bzz account, either by fetching -// from a list of pre-loaded passwords, or by requesting it interactively from user. -func getPassPhrase(prompt string, i int, passwords []string) string { - // non-interactive - if len(passwords) > 0 { - if i < len(passwords) { - return passwords[i] - } - return passwords[len(passwords)-1] - } - - // fallback to interactive mode - if prompt != "" { - fmt.Println(prompt) - } - password, err := console.Stdin.PromptPassword("Passphrase: ") - if err != nil { - utils.Fatalf("Failed to read passphrase: %v", err) - } - return password -} - -// addDefaultHelpSubcommand scans through defined CLI commands and adds -// a basic help subcommand to each -// if a help command is already defined, it will take precedence over the default. -func addDefaultHelpSubcommands(commands []cli.Command) { - for i := range commands { - cmd := &commands[i] - if cmd.Subcommands != nil { - cmd.Subcommands = append(cmd.Subcommands, defaultSubcommandHelp) - addDefaultHelpSubcommands(cmd.Subcommands) - } - } -} - -func setSwarmBootstrapNodes(ctx *cli.Context, cfg *node.Config) { - if ctx.GlobalIsSet(utils.BootnodesFlag.Name) || ctx.GlobalIsSet(utils.BootnodesV4Flag.Name) { - return - } - - cfg.P2P.BootstrapNodes = []*enode.Node{} - - for _, url := range SwarmBootnodes { - node, err := enode.ParseV4(url) - if err != nil { - log.Error("Bootstrap URL invalid", "enode", url, "err", err) - } - cfg.P2P.BootstrapNodes = append(cfg.P2P.BootstrapNodes, node) - } - -} diff --git a/cmd/swarm/manifest.go b/cmd/swarm/manifest.go deleted file mode 100644 index b99b4c7bb6f9..000000000000 --- a/cmd/swarm/manifest.go +++ /dev/null @@ -1,353 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -// Command MANIFEST update -package main - -import ( - "fmt" - "os" - "strings" - - "github.com/ubiq/go-ubiq/cmd/utils" - "github.com/ubiq/go-ubiq/swarm/api" - swarm "github.com/ubiq/go-ubiq/swarm/api/client" - "gopkg.in/urfave/cli.v1" -) - -var manifestCommand = cli.Command{ - Name: "manifest", - CustomHelpTemplate: helpTemplate, - Usage: "perform operations on swarm manifests", - ArgsUsage: "COMMAND", - Description: "Updates a MANIFEST by adding/removing/updating the hash of a path.\nCOMMAND could be: add, update, remove", - Subcommands: []cli.Command{ - { - Action: manifestAdd, - CustomHelpTemplate: helpTemplate, - Name: "add", - Usage: "add a new path to the manifest", - ArgsUsage: " ", - Description: "Adds a new path to the manifest", - }, - { - Action: manifestUpdate, - CustomHelpTemplate: helpTemplate, - Name: "update", - Usage: "update the hash for an already existing path in the manifest", - ArgsUsage: " ", - Description: "Update the hash for an already existing path in the manifest", - }, - { - Action: manifestRemove, - CustomHelpTemplate: helpTemplate, - Name: "remove", - Usage: "removes a path from the manifest", - ArgsUsage: " ", - Description: "Removes a path from the manifest", - }, - }, -} - -// manifestAdd adds a new entry to the manifest at the given path. -// New entry hash, the last argument, must be the hash of a manifest -// with only one entry, which meta-data will be added to the original manifest. -// On success, this function will print new (updated) manifest's hash. -func manifestAdd(ctx *cli.Context) { - args := ctx.Args() - if len(args) != 3 { - utils.Fatalf("Need exactly three arguments ") - } - - var ( - mhash = args[0] - path = args[1] - hash = args[2] - ) - - bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/") - client := swarm.NewClient(bzzapi) - - m, _, err := client.DownloadManifest(hash) - if err != nil { - utils.Fatalf("Error downloading manifest to add: %v", err) - } - l := len(m.Entries) - if l == 0 { - utils.Fatalf("No entries in manifest %s", hash) - } else if l > 1 { - utils.Fatalf("Too many entries in manifest %s", hash) - } - - newManifest := addEntryToManifest(client, mhash, path, m.Entries[0]) - fmt.Println(newManifest) -} - -// manifestUpdate replaces an existing entry of the manifest at the given path. -// New entry hash, the last argument, must be the hash of a manifest -// with only one entry, which meta-data will be added to the original manifest. -// On success, this function will print hash of the updated manifest. -func manifestUpdate(ctx *cli.Context) { - args := ctx.Args() - if len(args) != 3 { - utils.Fatalf("Need exactly three arguments ") - } - - var ( - mhash = args[0] - path = args[1] - hash = args[2] - ) - - bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/") - client := swarm.NewClient(bzzapi) - - m, _, err := client.DownloadManifest(hash) - if err != nil { - utils.Fatalf("Error downloading manifest to update: %v", err) - } - l := len(m.Entries) - if l == 0 { - utils.Fatalf("No entries in manifest %s", hash) - } else if l > 1 { - utils.Fatalf("Too many entries in manifest %s", hash) - } - - newManifest, _, defaultEntryUpdated := updateEntryInManifest(client, mhash, path, m.Entries[0], true) - if defaultEntryUpdated { - // Print informational message to stderr - // allowing the user to get the new manifest hash from stdout - // without the need to parse the complete output. - fmt.Fprintln(os.Stderr, "Manifest default entry is updated, too") - } - fmt.Println(newManifest) -} - -// manifestRemove removes an existing entry of the manifest at the given path. -// On success, this function will print hash of the manifest which does not -// contain the path. -func manifestRemove(ctx *cli.Context) { - args := ctx.Args() - if len(args) != 2 { - utils.Fatalf("Need exactly two arguments ") - } - - var ( - mhash = args[0] - path = args[1] - ) - - bzzapi := strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/") - client := swarm.NewClient(bzzapi) - - newManifest := removeEntryFromManifest(client, mhash, path) - fmt.Println(newManifest) -} - -func addEntryToManifest(client *swarm.Client, mhash, path string, entry api.ManifestEntry) string { - var longestPathEntry = api.ManifestEntry{} - - mroot, isEncrypted, err := client.DownloadManifest(mhash) - if err != nil { - utils.Fatalf("Manifest download failed: %v", err) - } - - // See if we path is in this Manifest or do we have to dig deeper - for _, e := range mroot.Entries { - if path == e.Path { - utils.Fatalf("Path %s already present, not adding anything", path) - } else { - if e.ContentType == api.ManifestType { - prfxlen := strings.HasPrefix(path, e.Path) - if prfxlen && len(path) > len(longestPathEntry.Path) { - longestPathEntry = e - } - } - } - } - - if longestPathEntry.Path != "" { - // Load the child Manifest add the entry there - newPath := path[len(longestPathEntry.Path):] - newHash := addEntryToManifest(client, longestPathEntry.Hash, newPath, entry) - - // Replace the hash for parent Manifests - newMRoot := &api.Manifest{} - for _, e := range mroot.Entries { - if longestPathEntry.Path == e.Path { - e.Hash = newHash - } - newMRoot.Entries = append(newMRoot.Entries, e) - } - mroot = newMRoot - } else { - // Add the entry in the leaf Manifest - entry.Path = path - mroot.Entries = append(mroot.Entries, entry) - } - - newManifestHash, err := client.UploadManifest(mroot, isEncrypted) - if err != nil { - utils.Fatalf("Manifest upload failed: %v", err) - } - return newManifestHash -} - -// updateEntryInManifest updates an existing entry o path with a new one in the manifest with provided mhash -// finding the path recursively through all nested manifests. Argument isRoot is used for default -// entry update detection. If the updated entry has the same hash as the default entry, then the -// default entry in root manifest will be updated too. -// Returned values are the new manifest hash, hash of the entry that was replaced by the new entry and -// a a bool that is true if default entry is updated. -func updateEntryInManifest(client *swarm.Client, mhash, path string, entry api.ManifestEntry, isRoot bool) (newManifestHash, oldHash string, defaultEntryUpdated bool) { - var ( - newEntry = api.ManifestEntry{} - longestPathEntry = api.ManifestEntry{} - ) - - mroot, isEncrypted, err := client.DownloadManifest(mhash) - if err != nil { - utils.Fatalf("Manifest download failed: %v", err) - } - - // See if we path is in this Manifest or do we have to dig deeper - for _, e := range mroot.Entries { - if path == e.Path { - newEntry = e - // keep the reference of the hash of the entry that should be replaced - // for default entry detection - oldHash = e.Hash - } else { - if e.ContentType == api.ManifestType { - prfxlen := strings.HasPrefix(path, e.Path) - if prfxlen && len(path) > len(longestPathEntry.Path) { - longestPathEntry = e - } - } - } - } - - if longestPathEntry.Path == "" && newEntry.Path == "" { - utils.Fatalf("Path %s not present in the Manifest, not setting anything", path) - } - - if longestPathEntry.Path != "" { - // Load the child Manifest add the entry there - newPath := path[len(longestPathEntry.Path):] - var newHash string - newHash, oldHash, _ = updateEntryInManifest(client, longestPathEntry.Hash, newPath, entry, false) - - // Replace the hash for parent Manifests - newMRoot := &api.Manifest{} - for _, e := range mroot.Entries { - if longestPathEntry.Path == e.Path { - e.Hash = newHash - } - newMRoot.Entries = append(newMRoot.Entries, e) - - } - mroot = newMRoot - } - - // update the manifest if the new entry is found and - // check if default entry should be updated - if newEntry.Path != "" || isRoot { - // Replace the hash for leaf Manifest - newMRoot := &api.Manifest{} - for _, e := range mroot.Entries { - if newEntry.Path == e.Path { - entry.Path = e.Path - newMRoot.Entries = append(newMRoot.Entries, entry) - } else if isRoot && e.Path == "" && e.Hash == oldHash { - entry.Path = e.Path - newMRoot.Entries = append(newMRoot.Entries, entry) - defaultEntryUpdated = true - } else { - newMRoot.Entries = append(newMRoot.Entries, e) - } - } - mroot = newMRoot - } - - newManifestHash, err = client.UploadManifest(mroot, isEncrypted) - if err != nil { - utils.Fatalf("Manifest upload failed: %v", err) - } - return newManifestHash, oldHash, defaultEntryUpdated -} - -func removeEntryFromManifest(client *swarm.Client, mhash, path string) string { - var ( - entryToRemove = api.ManifestEntry{} - longestPathEntry = api.ManifestEntry{} - ) - - mroot, isEncrypted, err := client.DownloadManifest(mhash) - if err != nil { - utils.Fatalf("Manifest download failed: %v", err) - } - - // See if we path is in this Manifest or do we have to dig deeper - for _, entry := range mroot.Entries { - if path == entry.Path { - entryToRemove = entry - } else { - if entry.ContentType == api.ManifestType { - prfxlen := strings.HasPrefix(path, entry.Path) - if prfxlen && len(path) > len(longestPathEntry.Path) { - longestPathEntry = entry - } - } - } - } - - if longestPathEntry.Path == "" && entryToRemove.Path == "" { - utils.Fatalf("Path %s not present in the Manifest, not removing anything", path) - } - - if longestPathEntry.Path != "" { - // Load the child Manifest remove the entry there - newPath := path[len(longestPathEntry.Path):] - newHash := removeEntryFromManifest(client, longestPathEntry.Hash, newPath) - - // Replace the hash for parent Manifests - newMRoot := &api.Manifest{} - for _, entry := range mroot.Entries { - if longestPathEntry.Path == entry.Path { - entry.Hash = newHash - } - newMRoot.Entries = append(newMRoot.Entries, entry) - } - mroot = newMRoot - } - - if entryToRemove.Path != "" { - // remove the entry in this Manifest - newMRoot := &api.Manifest{} - for _, entry := range mroot.Entries { - if entryToRemove.Path != entry.Path { - newMRoot.Entries = append(newMRoot.Entries, entry) - } - } - mroot = newMRoot - } - - newManifestHash, err := client.UploadManifest(mroot, isEncrypted) - if err != nil { - utils.Fatalf("Manifest upload failed: %v", err) - } - return newManifestHash -} diff --git a/cmd/swarm/manifest_test.go b/cmd/swarm/manifest_test.go deleted file mode 100644 index d9405c80b4f1..000000000000 --- a/cmd/swarm/manifest_test.go +++ /dev/null @@ -1,597 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "bytes" - "io/ioutil" - "os" - "path/filepath" - "runtime" - "testing" - - "github.com/ubiq/go-ubiq/swarm/api" - swarm "github.com/ubiq/go-ubiq/swarm/api/client" - swarmhttp "github.com/ubiq/go-ubiq/swarm/api/http" -) - -// TestManifestChange tests manifest add, update and remove -// cli commands without encryption. -func TestManifestChange(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip() - } - - testManifestChange(t, false) -} - -// TestManifestChange tests manifest add, update and remove -// cli commands with encryption enabled. -func TestManifestChangeEncrypted(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip() - } - - testManifestChange(t, true) -} - -// testManifestChange performs cli commands: -// - manifest add -// - manifest update -// - manifest remove -// on a manifest, testing the functionality of this -// comands on paths that are in root manifest or a nested one. -// Argument encrypt controls whether to use encryption or not. -func testManifestChange(t *testing.T, encrypt bool) { - t.Parallel() - srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - tmp, err := ioutil.TempDir("", "swarm-manifest-test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmp) - - origDir := filepath.Join(tmp, "orig") - if err := os.Mkdir(origDir, 0777); err != nil { - t.Fatal(err) - } - - indexDataFilename := filepath.Join(origDir, "index.html") - err = ioutil.WriteFile(indexDataFilename, []byte("

Test

"), 0666) - if err != nil { - t.Fatal(err) - } - // Files paths robots.txt and robots.html share the same prefix "robots." - // which will result a manifest with a nested manifest under path "robots.". - // This will allow testing manifest changes on both root and nested manifest. - err = ioutil.WriteFile(filepath.Join(origDir, "robots.txt"), []byte("Disallow: /"), 0666) - if err != nil { - t.Fatal(err) - } - err = ioutil.WriteFile(filepath.Join(origDir, "robots.html"), []byte("No Robots Allowed"), 0666) - if err != nil { - t.Fatal(err) - } - err = ioutil.WriteFile(filepath.Join(origDir, "mutants.txt"), []byte("Frank\nMarcus"), 0666) - if err != nil { - t.Fatal(err) - } - - args := []string{ - "--bzzapi", - srv.URL, - "--recursive", - "--defaultpath", - indexDataFilename, - "up", - origDir, - } - if encrypt { - args = append(args, "--encrypt") - } - - origManifestHash := runSwarmExpectHash(t, args...) - - checkHashLength(t, origManifestHash, encrypt) - - client := swarm.NewClient(srv.URL) - - // upload a new file and use its manifest to add it the original manifest. - t.Run("add", func(t *testing.T) { - humansData := []byte("Ann\nBob") - humansDataFilename := filepath.Join(tmp, "humans.txt") - err = ioutil.WriteFile(humansDataFilename, humansData, 0666) - if err != nil { - t.Fatal(err) - } - - humansManifestHash := runSwarmExpectHash(t, - "--bzzapi", - srv.URL, - "up", - humansDataFilename, - ) - - newManifestHash := runSwarmExpectHash(t, - "--bzzapi", - srv.URL, - "manifest", - "add", - origManifestHash, - "humans.txt", - humansManifestHash, - ) - - checkHashLength(t, newManifestHash, encrypt) - - newManifest := downloadManifest(t, client, newManifestHash, encrypt) - - var found bool - for _, e := range newManifest.Entries { - if e.Path == "humans.txt" { - found = true - if e.Size != int64(len(humansData)) { - t.Errorf("expected humans.txt size %v, got %v", len(humansData), e.Size) - } - if e.ModTime.IsZero() { - t.Errorf("got zero mod time for humans.txt") - } - ct := "text/plain; charset=utf-8" - if e.ContentType != ct { - t.Errorf("expected content type %q, got %q", ct, e.ContentType) - } - break - } - } - if !found { - t.Fatal("no humans.txt in new manifest") - } - - checkFile(t, client, newManifestHash, "humans.txt", humansData) - }) - - // upload a new file and use its manifest to add it the original manifest, - // but ensure that the file will be in the nested manifest of the original one. - t.Run("add nested", func(t *testing.T) { - robotsData := []byte(`{"disallow": "/"}`) - robotsDataFilename := filepath.Join(tmp, "robots.json") - err = ioutil.WriteFile(robotsDataFilename, robotsData, 0666) - if err != nil { - t.Fatal(err) - } - - robotsManifestHash := runSwarmExpectHash(t, - "--bzzapi", - srv.URL, - "up", - robotsDataFilename, - ) - - newManifestHash := runSwarmExpectHash(t, - "--bzzapi", - srv.URL, - "manifest", - "add", - origManifestHash, - "robots.json", - robotsManifestHash, - ) - - checkHashLength(t, newManifestHash, encrypt) - - newManifest := downloadManifest(t, client, newManifestHash, encrypt) - - var found bool - loop: - for _, e := range newManifest.Entries { - if e.Path == "robots." { - nestedManifest := downloadManifest(t, client, e.Hash, encrypt) - for _, e := range nestedManifest.Entries { - if e.Path == "json" { - found = true - if e.Size != int64(len(robotsData)) { - t.Errorf("expected robots.json size %v, got %v", len(robotsData), e.Size) - } - if e.ModTime.IsZero() { - t.Errorf("got zero mod time for robots.json") - } - ct := "application/json" - if e.ContentType != ct { - t.Errorf("expected content type %q, got %q", ct, e.ContentType) - } - break loop - } - } - } - } - if !found { - t.Fatal("no robots.json in new manifest") - } - - checkFile(t, client, newManifestHash, "robots.json", robotsData) - }) - - // upload a new file and use its manifest to change the file it the original manifest. - t.Run("update", func(t *testing.T) { - indexData := []byte("

Ethereum Swarm

") - indexDataFilename := filepath.Join(tmp, "index.html") - err = ioutil.WriteFile(indexDataFilename, indexData, 0666) - if err != nil { - t.Fatal(err) - } - - indexManifestHash := runSwarmExpectHash(t, - "--bzzapi", - srv.URL, - "up", - indexDataFilename, - ) - - newManifestHash := runSwarmExpectHash(t, - "--bzzapi", - srv.URL, - "manifest", - "update", - origManifestHash, - "index.html", - indexManifestHash, - ) - - checkHashLength(t, newManifestHash, encrypt) - - newManifest := downloadManifest(t, client, newManifestHash, encrypt) - - var found bool - for _, e := range newManifest.Entries { - if e.Path == "index.html" { - found = true - if e.Size != int64(len(indexData)) { - t.Errorf("expected index.html size %v, got %v", len(indexData), e.Size) - } - if e.ModTime.IsZero() { - t.Errorf("got zero mod time for index.html") - } - ct := "text/html; charset=utf-8" - if e.ContentType != ct { - t.Errorf("expected content type %q, got %q", ct, e.ContentType) - } - break - } - } - if !found { - t.Fatal("no index.html in new manifest") - } - - checkFile(t, client, newManifestHash, "index.html", indexData) - - // check default entry change - checkFile(t, client, newManifestHash, "", indexData) - }) - - // upload a new file and use its manifest to change the file it the original manifest, - // but ensure that the file is in the nested manifest of the original one. - t.Run("update nested", func(t *testing.T) { - robotsData := []byte(`Only humans allowed!!!`) - robotsDataFilename := filepath.Join(tmp, "robots.html") - err = ioutil.WriteFile(robotsDataFilename, robotsData, 0666) - if err != nil { - t.Fatal(err) - } - - humansManifestHash := runSwarmExpectHash(t, - "--bzzapi", - srv.URL, - "up", - robotsDataFilename, - ) - - newManifestHash := runSwarmExpectHash(t, - "--bzzapi", - srv.URL, - "manifest", - "update", - origManifestHash, - "robots.html", - humansManifestHash, - ) - - checkHashLength(t, newManifestHash, encrypt) - - newManifest := downloadManifest(t, client, newManifestHash, encrypt) - - var found bool - loop: - for _, e := range newManifest.Entries { - if e.Path == "robots." { - nestedManifest := downloadManifest(t, client, e.Hash, encrypt) - for _, e := range nestedManifest.Entries { - if e.Path == "html" { - found = true - if e.Size != int64(len(robotsData)) { - t.Errorf("expected robots.html size %v, got %v", len(robotsData), e.Size) - } - if e.ModTime.IsZero() { - t.Errorf("got zero mod time for robots.html") - } - ct := "text/html; charset=utf-8" - if e.ContentType != ct { - t.Errorf("expected content type %q, got %q", ct, e.ContentType) - } - break loop - } - } - } - } - if !found { - t.Fatal("no robots.html in new manifest") - } - - checkFile(t, client, newManifestHash, "robots.html", robotsData) - }) - - // remove a file from the manifest. - t.Run("remove", func(t *testing.T) { - newManifestHash := runSwarmExpectHash(t, - "--bzzapi", - srv.URL, - "manifest", - "remove", - origManifestHash, - "mutants.txt", - ) - - checkHashLength(t, newManifestHash, encrypt) - - newManifest := downloadManifest(t, client, newManifestHash, encrypt) - - var found bool - for _, e := range newManifest.Entries { - if e.Path == "mutants.txt" { - found = true - break - } - } - if found { - t.Fatal("mutants.txt is not removed") - } - }) - - // remove a file from the manifest, but ensure that the file is in - // the nested manifest of the original one. - t.Run("remove nested", func(t *testing.T) { - newManifestHash := runSwarmExpectHash(t, - "--bzzapi", - srv.URL, - "manifest", - "remove", - origManifestHash, - "robots.html", - ) - - checkHashLength(t, newManifestHash, encrypt) - - newManifest := downloadManifest(t, client, newManifestHash, encrypt) - - var found bool - loop: - for _, e := range newManifest.Entries { - if e.Path == "robots." { - nestedManifest := downloadManifest(t, client, e.Hash, encrypt) - for _, e := range nestedManifest.Entries { - if e.Path == "html" { - found = true - break loop - } - } - } - } - if found { - t.Fatal("robots.html in not removed") - } - }) -} - -// TestNestedDefaultEntryUpdate tests if the default entry is updated -// if the file in nested manifest used for it is also updated. -func TestNestedDefaultEntryUpdate(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip() - } - - testNestedDefaultEntryUpdate(t, false) -} - -// TestNestedDefaultEntryUpdateEncrypted tests if the default entry -// of encrypted upload is updated if the file in nested manifest -// used for it is also updated. -func TestNestedDefaultEntryUpdateEncrypted(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip() - } - - testNestedDefaultEntryUpdate(t, true) -} - -func testNestedDefaultEntryUpdate(t *testing.T, encrypt bool) { - t.Parallel() - srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - tmp, err := ioutil.TempDir("", "swarm-manifest-test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmp) - - origDir := filepath.Join(tmp, "orig") - if err := os.Mkdir(origDir, 0777); err != nil { - t.Fatal(err) - } - - indexData := []byte("

Test

") - indexDataFilename := filepath.Join(origDir, "index.html") - err = ioutil.WriteFile(indexDataFilename, indexData, 0666) - if err != nil { - t.Fatal(err) - } - // Add another file with common prefix as the default entry to test updates of - // default entry with nested manifests. - err = ioutil.WriteFile(filepath.Join(origDir, "index.txt"), []byte("Test"), 0666) - if err != nil { - t.Fatal(err) - } - - args := []string{ - "--bzzapi", - srv.URL, - "--recursive", - "--defaultpath", - indexDataFilename, - "up", - origDir, - } - if encrypt { - args = append(args, "--encrypt") - } - - origManifestHash := runSwarmExpectHash(t, args...) - - checkHashLength(t, origManifestHash, encrypt) - - client := swarm.NewClient(srv.URL) - - newIndexData := []byte("

Ethereum Swarm

") - newIndexDataFilename := filepath.Join(tmp, "index.html") - err = ioutil.WriteFile(newIndexDataFilename, newIndexData, 0666) - if err != nil { - t.Fatal(err) - } - - newIndexManifestHash := runSwarmExpectHash(t, - "--bzzapi", - srv.URL, - "up", - newIndexDataFilename, - ) - - newManifestHash := runSwarmExpectHash(t, - "--bzzapi", - srv.URL, - "manifest", - "update", - origManifestHash, - "index.html", - newIndexManifestHash, - ) - - checkHashLength(t, newManifestHash, encrypt) - - newManifest := downloadManifest(t, client, newManifestHash, encrypt) - - var found bool - for _, e := range newManifest.Entries { - if e.Path == "index." { - found = true - newManifest = downloadManifest(t, client, e.Hash, encrypt) - break - } - } - if !found { - t.Fatal("no index. path in new manifest") - } - - found = false - for _, e := range newManifest.Entries { - if e.Path == "html" { - found = true - if e.Size != int64(len(newIndexData)) { - t.Errorf("expected index.html size %v, got %v", len(newIndexData), e.Size) - } - if e.ModTime.IsZero() { - t.Errorf("got zero mod time for index.html") - } - ct := "text/html; charset=utf-8" - if e.ContentType != ct { - t.Errorf("expected content type %q, got %q", ct, e.ContentType) - } - break - } - } - if !found { - t.Fatal("no html in new manifest") - } - - checkFile(t, client, newManifestHash, "index.html", newIndexData) - - // check default entry change - checkFile(t, client, newManifestHash, "", newIndexData) -} - -func runSwarmExpectHash(t *testing.T, args ...string) (hash string) { - t.Helper() - hashRegexp := `[a-f\d]{64,128}` - up := runSwarm(t, args...) - _, matches := up.ExpectRegexp(hashRegexp) - up.ExpectExit() - - if len(matches) < 1 { - t.Fatal("no matches found") - } - return matches[0] -} - -func checkHashLength(t *testing.T, hash string, encrypted bool) { - t.Helper() - l := len(hash) - if encrypted && l != 128 { - t.Errorf("expected hash length 128, got %v", l) - } - if !encrypted && l != 64 { - t.Errorf("expected hash length 64, got %v", l) - } -} - -func downloadManifest(t *testing.T, client *swarm.Client, hash string, encrypted bool) (manifest *api.Manifest) { - t.Helper() - m, isEncrypted, err := client.DownloadManifest(hash) - if err != nil { - t.Fatal(err) - } - - if encrypted != isEncrypted { - t.Error("new manifest encryption flag is not correct") - } - return m -} - -func checkFile(t *testing.T, client *swarm.Client, hash, path string, expected []byte) { - t.Helper() - f, err := client.Download(hash, path) - if err != nil { - t.Fatal(err) - } - - got, err := ioutil.ReadAll(f) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(got, expected) { - t.Errorf("expected file content %q, got %q", expected, got) - } -} diff --git a/cmd/swarm/mimegen/generator.go b/cmd/swarm/mimegen/generator.go deleted file mode 100644 index bcd5ccedfc74..000000000000 --- a/cmd/swarm/mimegen/generator.go +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . -package main - -// Standard "mime" package rely on system-settings, see mime.osInitMime -// Swarm will run on many OS/Platform/Docker and must behave similar -// This command generates code to add common mime types based on mime.types file -// -// mime.types file provided by mailcap, which follow https://www.iana.org/assignments/media-types/media-types.xhtml -// -// Get last version of mime.types file by: -// docker run --rm -v $(pwd):/tmp alpine:edge /bin/sh -c "apk add -U mailcap; mv /etc/mime.types /tmp" - -import ( - "bufio" - "bytes" - "flag" - "html/template" - "io/ioutil" - "strings" - - "log" -) - -var ( - typesFlag = flag.String("types", "", "Input mime.types file") - packageFlag = flag.String("package", "", "Golang package in output file") - outFlag = flag.String("out", "", "Output file name for the generated mime types") -) - -type mime struct { - Name string - Exts []string -} - -type templateParams struct { - PackageName string - Mimes []mime -} - -func main() { - // Parse and ensure all needed inputs are specified - flag.Parse() - if *typesFlag == "" { - log.Fatalf("--types is required") - } - if *packageFlag == "" { - log.Fatalf("--types is required") - } - if *outFlag == "" { - log.Fatalf("--out is required") - } - - params := templateParams{ - PackageName: *packageFlag, - } - - types, err := ioutil.ReadFile(*typesFlag) - if err != nil { - log.Fatal(err) - } - - scanner := bufio.NewScanner(bytes.NewReader(types)) - for scanner.Scan() { - txt := scanner.Text() - if strings.HasPrefix(txt, "#") || len(txt) == 0 { - continue - } - parts := strings.Fields(txt) - if len(parts) == 1 { - continue - } - params.Mimes = append(params.Mimes, mime{parts[0], parts[1:]}) - } - - if err = scanner.Err(); err != nil { - log.Fatal(err) - } - - result := bytes.NewBuffer([]byte{}) - - if err := template.Must(template.New("_").Parse(tpl)).Execute(result, params); err != nil { - log.Fatal(err) - } - - if err := ioutil.WriteFile(*outFlag, result.Bytes(), 0600); err != nil { - log.Fatal(err) - } -} - -var tpl = `// Code generated by github.com/ubiq/go-ubiq/cmd/swarm/mimegen. DO NOT EDIT. - -package {{ .PackageName }} - -import "mime" -func init() { - var mimeTypes = map[string]string{ -{{- range .Mimes -}} - {{ $name := .Name -}} - {{- range .Exts }} - ".{{ . }}": "{{ $name | html }}", - {{- end }} -{{- end }} - } - for ext, name := range mimeTypes { - if err := mime.AddExtensionType(ext, name); err != nil { - panic(err) - } - } -} -` diff --git a/cmd/swarm/mimegen/mime.types b/cmd/swarm/mimegen/mime.types deleted file mode 100644 index 1bdf211490d9..000000000000 --- a/cmd/swarm/mimegen/mime.types +++ /dev/null @@ -1,1828 +0,0 @@ -# This is a comment. I love comments. -*- indent-tabs-mode: t -*- - -# This file controls what Internet media types are sent to the client for -# given file extension(s). Sending the correct media type to the client -# is important so they know how to handle the content of the file. -# Extra types can either be added here or by using an AddType directive -# in your config files. For more information about Internet media types, -# please read RFC 2045, 2046, 2047, 2048, and 2077. The Internet media type -# registry is at . - -# IANA types - -# MIME type Extensions -application/1d-interleaved-parityfec -application/3gpdash-qoe-report+xml -application/3gpp-ims+xml -application/A2L a2l -application/activemessage -application/alto-costmap+json -application/alto-costmapfilter+json -application/alto-directory+json -application/alto-endpointcost+json -application/alto-endpointcostparams+json -application/alto-endpointprop+json -application/alto-endpointpropparams+json -application/alto-error+json -application/alto-networkmap+json -application/alto-networkmapfilter+json -application/AML aml -application/andrew-inset ez -application/applefile -application/ATF atf -application/ATFX atfx -application/ATXML atxml -application/atom+xml atom -application/atomcat+xml atomcat -application/atomdeleted+xml atomdeleted -application/atomicmail -application/atomsvc+xml atomsvc -application/auth-policy+xml apxml -application/bacnet-xdd+zip xdd -application/batch-SMTP -application/beep+xml -application/calendar+json -application/calendar+xml xcs -application/call-completion -application/cals-1840 -application/cbor cbor -application/ccmp+xml ccmp -application/ccxml+xml ccxml -application/CDFX+XML cdfx -application/cdmi-capability cdmia -application/cdmi-container cdmic -application/cdmi-domain cdmid -application/cdmi-object cdmio -application/cdmi-queue cdmiq -application/cdni -application/CEA cea -application/cea-2018+xml -application/cellml+xml cellml cml -application/cfw -application/clue_info+xml clue -application/cms cmsc -application/cnrp+xml -application/coap-group+json -application/coap-payload -application/commonground -application/conference-info+xml -application/cpl+xml cpl -application/cose -application/cose-key -application/cose-key-set -application/csrattrs csrattrs -application/csta+xml -application/CSTAdata+xml -application/csvm+json -application/cybercash -application/dash+xml mpd -application/dashdelta mpdd -application/davmount+xml davmount -application/dca-rft -application/DCD dcd -application/dec-dx -application/dialog-info+xml -application/dicom dcm -application/dicom+json -application/dicom+xml -application/DII dii -application/DIT dit -application/dns -application/dskpp+xml xmls -application/dssc+der dssc -application/dssc+xml xdssc -application/dvcs dvc -application/ecmascript es -application/EDI-Consent -application/EDI-X12 -application/EDIFACT -application/efi efi -application/EmergencyCallData.Comment+xml -application/EmergencyCallData.Control+xml -application/EmergencyCallData.DeviceInfo+xml -application/EmergencyCallData.eCall.MSD -application/EmergencyCallData.ProviderInfo+xml -application/EmergencyCallData.ServiceInfo+xml -application/EmergencyCallData.SubscriberInfo+xml -application/EmergencyCallData.VEDS+xml -application/emma+xml emma -application/emotionml+xml emotionml -application/encaprtp -application/epp+xml -application/epub+zip epub -application/eshop -application/exi exi -application/fastinfoset finf -application/fastsoap -application/fdt+xml fdt -# fits, fit, fts: image/fits -application/fits -# application/font-sfnt deprecated in favor of font/sfnt -application/font-tdpfr pfr -# application/font-woff deprecated in favor of font/woff -application/framework-attributes+xml -application/geo+json geojson -application/geo+json-seq -application/gml+xml gml -application/gzip gz tgz -application/H224 -application/held+xml -application/http -application/hyperstudio stk -application/ibe-key-request+xml -application/ibe-pkg-reply+xml -application/ibe-pp-data -application/iges -application/im-iscomposing+xml -application/index -application/index.cmd -application/index.obj -application/index.response -application/index.vnd -application/inkml+xml ink inkml -application/iotp -application/ipfix ipfix -application/ipp -application/isup -application/its+xml its -application/javascript js -application/jose -application/jose+json -application/jrd+json jrd -application/json json -application/json-patch+json json-patch -application/json-seq -application/jwk+json -application/jwk-set+json -application/jwt -application/kpml-request+xml -application/kpml-response+xml -application/ld+json jsonld -application/lgr+xml lgr -application/link-format wlnk -application/load-control+xml -application/lost+xml lostxml -application/lostsync+xml lostsyncxml -application/LXF lxf -application/mac-binhex40 hqx -application/macwriteii -application/mads+xml mads -application/marc mrc -application/marcxml+xml mrcx -application/mathematica nb ma mb -application/mathml-content+xml -application/mathml-presentation+xml -application/mathml+xml mml -application/mbms-associated-procedure-description+xml -application/mbms-deregister+xml -application/mbms-envelope+xml -application/mbms-msk-response+xml -application/mbms-msk+xml -application/mbms-protection-description+xml -application/mbms-reception-report+xml -application/mbms-register-response+xml -application/mbms-register+xml -application/mbms-schedule+xml -application/mbms-user-service-description+xml -application/mbox mbox -application/media_control+xml -# mpf: text/vnd.ms-mediapackage -application/media-policy-dataset+xml -application/mediaservercontrol+xml -application/merge-patch+json -application/metalink4+xml meta4 -application/mets+xml mets -application/MF4 mf4 -application/mikey -application/mods+xml mods -application/moss-keys -application/moss-signature -application/mosskey-data -application/mosskey-request -application/mp21 m21 mp21 -# mp4, mpg4: video/mp4, see RFC 4337 -application/mp4 -application/mpeg4-generic -application/mpeg4-iod -application/mpeg4-iod-xmt -# xdf: application/xcap-diff+xml -application/mrb-consumer+xml -application/mrb-publish+xml -application/msc-ivr+xml -application/msc-mixer+xml -application/msword doc -application/mud+json -application/mxf mxf -application/n-quads nq -application/n-triples nt -application/nasdata -application/news-checkgroups -application/news-groupinfo -application/news-transmission -application/nlsml+xml -application/nss -application/ocsp-request orq -application/ocsp-response ors -application/octet-stream bin lha lzh exe class so dll img iso -application/oda oda -application/ODX odx -application/oebps-package+xml opf -application/ogg ogx -application/oxps oxps -application/p2p-overlay+xml relo -application/parityfec -# xer: application/xcap-error+xml -application/patch-ops-error+xml -application/pdf pdf -application/PDX pdx -application/pgp-encrypted pgp -application/pgp-keys -application/pgp-signature sig -application/pidf-diff+xml -application/pidf+xml -application/pkcs10 p10 -application/pkcs12 p12 pfx -application/pkcs7-mime p7m p7c -application/pkcs7-signature p7s -application/pkcs8 p8 -# ac: application/vnd.nokia.n-gage.ac+xml -application/pkix-attr-cert -application/pkix-cert cer -application/pkix-crl crl -application/pkix-pkipath pkipath -application/pkixcmp pki -application/pls+xml pls -application/poc-settings+xml -application/postscript ps eps ai -application/ppsp-tracker+json -application/problem+json -application/problem+xml -application/provenance+xml provx -application/prs.alvestrand.titrax-sheet -application/prs.cww cw cww -application/prs.hpub+zip hpub -application/prs.nprend rnd rct -application/prs.plucker -application/prs.rdf-xml-crypt rdf-crypt -application/prs.xsf+xml xsf -application/pskc+xml pskcxml -application/qsig -application/raptorfec -application/rdap+json -application/rdf+xml rdf -application/reginfo+xml rif -application/relax-ng-compact-syntax rnc -application/remote-printing -application/reputon+json -application/resource-lists-diff+xml rld -application/resource-lists+xml rl -application/rfc+xml rfcxml -application/riscos -application/rlmi+xml -application/rls-services+xml rs -application/rpki-ghostbusters gbr -application/rpki-manifest mft -application/rpki-publication -application/rpki-roa roa -application/rpki-updown -application/rtf rtf -application/rtploopback -application/rtx -application/samlassertion+xml -application/samlmetadata+xml -application/sbml+xml -application/scaip+xml -# scm: application/vnd.lotus-screencam -application/scim+json scim -application/scvp-cv-request scq -application/scvp-cv-response scs -application/scvp-vp-request spq -application/scvp-vp-response spp -application/sdp sdp -application/sep+xml -application/sep-exi -application/session-info -application/set-payment -application/set-payment-initiation -application/set-registration -application/set-registration-initiation -application/sgml -application/sgml-open-catalog soc -application/shf+xml shf -application/sieve siv sieve -application/simple-filter+xml cl -application/simple-message-summary -application/simpleSymbolContainer -application/slate -# application/smil obsoleted by application/smil+xml -application/smil+xml smil smi sml -application/smpte336m -application/soap+fastinfoset -application/soap+xml -application/sparql-query rq -application/sparql-results+xml srx -application/spirits-event+xml -application/sql sql -application/srgs gram -application/srgs+xml grxml -application/sru+xml sru -application/ssml+xml ssml -application/tamp-apex-update tau -application/tamp-apex-update-confirm auc -application/tamp-community-update tcu -application/tamp-community-update-confirm cuc -application/tamp-error ter -application/tamp-sequence-adjust tsa -application/tamp-sequence-adjust-confirm sac -# tsq: application/timestamp-query -application/tamp-status-query -# tsr: application/timestamp-reply -application/tamp-status-response -application/tamp-update tur -application/tamp-update-confirm tuc -application/tei+xml tei teiCorpus odd -application/thraud+xml tfi -application/timestamp-query tsq -application/timestamp-reply tsr -application/timestamped-data tsd -application/trig trig -application/ttml+xml ttml -application/tve-trigger -application/ulpfec -application/urc-grpsheet+xml gsheet -application/urc-ressheet+xml rsheet -application/urc-targetdesc+xml td -application/urc-uisocketdesc+xml uis -application/vcard+json -application/vcard+xml -application/vemmi -application/vnd.3gpp.access-transfer-events+xml -application/vnd.3gpp.bsf+xml -application/vnd.3gpp.mid-call+xml -application/vnd.3gpp.pic-bw-large plb -application/vnd.3gpp.pic-bw-small psb -application/vnd.3gpp.pic-bw-var pvb -application/vnd.3gpp-prose+xml -application/vnd.3gpp-prose-pc3ch+xml -# sms: application/vnd.3gpp2.sms -application/vnd.3gpp.sms -application/vnd.3gpp.sms+xml -application/vnd.3gpp.srvcc-ext+xml -application/vnd.3gpp.SRVCC-info+xml -application/vnd.3gpp.state-and-event-info+xml -application/vnd.3gpp.ussd+xml -application/vnd.3gpp2.bcmcsinfo+xml -application/vnd.3gpp2.sms sms -application/vnd.3gpp2.tcap tcap -application/vnd.3lightssoftware.imagescal imgcal -application/vnd.3M.Post-it-Notes pwn -application/vnd.accpac.simply.aso aso -application/vnd.accpac.simply.imp imp -application/vnd.acucobol acu -application/vnd.acucorp atc acutc -application/vnd.adobe.flash.movie swf -application/vnd.adobe.formscentral.fcdt fcdt -application/vnd.adobe.fxp fxp fxpl -application/vnd.adobe.partial-upload -application/vnd.adobe.xdp+xml xdp -application/vnd.adobe.xfdf xfdf -application/vnd.aether.imp -application/vnd.ah-barcode -application/vnd.ahead.space ahead -application/vnd.airzip.filesecure.azf azf -application/vnd.airzip.filesecure.azs azs -application/vnd.amazon.mobi8-ebook azw3 -application/vnd.americandynamics.acc acc -application/vnd.amiga.ami ami -application/vnd.amundsen.maze+xml -application/vnd.anki apkg -application/vnd.anser-web-certificate-issue-initiation cii -# Not in IANA listing, but is on FTP site? -application/vnd.anser-web-funds-transfer-initiation fti -# atx: audio/ATRAC-X -application/vnd.antix.game-component -application/vnd.apache.thrift.binary -application/vnd.apache.thrift.compact -application/vnd.apache.thrift.json -application/vnd.api+json -application/vnd.apothekende.reservation+json -application/vnd.apple.installer+xml dist distz pkg mpkg -# m3u: audio/x-mpegurl for now -application/vnd.apple.mpegurl m3u8 -# application/vnd.arastra.swi obsoleted by application/vnd.aristanetworks.swi -application/vnd.aristanetworks.swi swi -application/vnd.artsquare -application/vnd.astraea-software.iota iota -application/vnd.audiograph aep -application/vnd.autopackage package -application/vnd.avistar+xml -application/vnd.balsamiq.bmml+xml bmml -application/vnd.balsamiq.bmpr bmpr -application/vnd.bekitzur-stech+json -application/vnd.bint.med-content -application/vnd.biopax.rdf+xml -application/vnd.blueice.multipass mpm -application/vnd.bluetooth.ep.oob ep -application/vnd.bluetooth.le.oob le -application/vnd.bmi bmi -application/vnd.businessobjects rep -application/vnd.cab-jscript -application/vnd.canon-cpdl -application/vnd.canon-lips -application/vnd.capasystems-pg+json -application/vnd.cendio.thinlinc.clientconf tlclient -application/vnd.century-systems.tcp_stream -application/vnd.chemdraw+xml cdxml -application/vnd.chess-pgn pgn -application/vnd.chipnuts.karaoke-mmd mmd -application/vnd.cinderella cdy -application/vnd.cirpack.isdn-ext -application/vnd.citationstyles.style+xml csl -application/vnd.claymore cla -application/vnd.cloanto.rp9 rp9 -application/vnd.clonk.c4group c4g c4d c4f c4p c4u -application/vnd.cluetrust.cartomobile-config c11amc -application/vnd.cluetrust.cartomobile-config-pkg c11amz -application/vnd.coffeescript coffee -application/vnd.collection+json -application/vnd.collection.doc+json -application/vnd.collection.next+json -application/vnd.comicbook+zip cbz -# icc: application/vnd.iccprofile -application/vnd.commerce-battelle ica icf icd ic0 ic1 ic2 ic3 ic4 ic5 ic6 ic7 ic8 -application/vnd.commonspace csp cst -application/vnd.contact.cmsg cdbcmsg -application/vnd.coreos.ignition+json ign ignition -application/vnd.cosmocaller cmc -application/vnd.crick.clicker clkx -application/vnd.crick.clicker.keyboard clkk -application/vnd.crick.clicker.palette clkp -application/vnd.crick.clicker.template clkt -application/vnd.crick.clicker.wordbank clkw -application/vnd.criticaltools.wbs+xml wbs -application/vnd.ctc-posml pml -application/vnd.ctct.ws+xml -application/vnd.cups-pdf -application/vnd.cups-postscript -application/vnd.cups-ppd ppd -application/vnd.cups-raster -application/vnd.cups-raw -application/vnd.curl curl -application/vnd.cyan.dean.root+xml -application/vnd.cybank -application/vnd.d2l.coursepackage1p0+zip -application/vnd.dart dart -application/vnd.data-vision.rdz rdz -application/vnd.datapackage+json -application/vnd.dataresource+json -application/vnd.debian.binary-package deb udeb -application/vnd.dece.data uvf uvvf uvd uvvd -application/vnd.dece.ttml+xml uvt uvvt -application/vnd.dece.unspecified uvx uvvx -application/vnd.dece.zip uvz uvvz -application/vnd.denovo.fcselayout-link fe_launch -application/vnd.desmume.movie dsm -application/vnd.dir-bi.plate-dl-nosuffix -application/vnd.dm.delegation+xml -application/vnd.dna dna -application/vnd.document+json docjson -application/vnd.dolby.mobile.1 -application/vnd.dolby.mobile.2 -application/vnd.doremir.scorecloud-binary-document scld -application/vnd.dpgraph dpg mwc dpgraph -application/vnd.dreamfactory dfac -application/vnd.drive+json -application/vnd.dtg.local -application/vnd.dtg.local.flash fla -application/vnd.dtg.local.html -application/vnd.dvb.ait ait -# class: application/octet-stream -application/vnd.dvb.dvbj -application/vnd.dvb.esgcontainer -application/vnd.dvb.ipdcdftnotifaccess -application/vnd.dvb.ipdcesgaccess -application/vnd.dvb.ipdcesgaccess2 -application/vnd.dvb.ipdcesgpdd -application/vnd.dvb.ipdcroaming -application/vnd.dvb.iptv.alfec-base -application/vnd.dvb.iptv.alfec-enhancement -application/vnd.dvb.notif-aggregate-root+xml -application/vnd.dvb.notif-container+xml -application/vnd.dvb.notif-generic+xml -application/vnd.dvb.notif-ia-msglist+xml -application/vnd.dvb.notif-ia-registration-request+xml -application/vnd.dvb.notif-ia-registration-response+xml -application/vnd.dvb.notif-init+xml -# pfr: application/font-tdpfr -application/vnd.dvb.pfr -application/vnd.dvb.service svc -# dxr: application/x-director -application/vnd.dxr -application/vnd.dynageo geo -application/vnd.dzr dzr -application/vnd.easykaraoke.cdgdownload -application/vnd.ecdis-update -application/vnd.ecowin.chart mag -application/vnd.ecowin.filerequest -application/vnd.ecowin.fileupdate -application/vnd.ecowin.series -application/vnd.ecowin.seriesrequest -application/vnd.ecowin.seriesupdate -# img: application/octet-stream -application/vnd.efi-img -# iso: application/octet-stream -application/vnd.efi-iso -application/vnd.enliven nml -application/vnd.enphase.envoy -application/vnd.eprints.data+xml -application/vnd.epson.esf esf -application/vnd.epson.msf msf -application/vnd.epson.quickanime qam -application/vnd.epson.salt slt -application/vnd.epson.ssf ssf -application/vnd.ericsson.quickcall qcall qca -application/vnd.espass-espass+zip espass -application/vnd.eszigno3+xml es3 et3 -application/vnd.etsi.aoc+xml -application/vnd.etsi.asic-e+zip asice sce -# scs: application/scvp-cv-response -application/vnd.etsi.asic-s+zip asics -application/vnd.etsi.cug+xml -application/vnd.etsi.iptvcommand+xml -application/vnd.etsi.iptvdiscovery+xml -application/vnd.etsi.iptvprofile+xml -application/vnd.etsi.iptvsad-bc+xml -application/vnd.etsi.iptvsad-cod+xml -application/vnd.etsi.iptvsad-npvr+xml -application/vnd.etsi.iptvservice+xml -application/vnd.etsi.iptvsync+xml -application/vnd.etsi.iptvueprofile+xml -application/vnd.etsi.mcid+xml -application/vnd.etsi.mheg5 -application/vnd.etsi.overload-control-policy-dataset+xml -application/vnd.etsi.pstn+xml -application/vnd.etsi.sci+xml -application/vnd.etsi.simservs+xml -application/vnd.etsi.timestamp-token tst -application/vnd.etsi.tsl.der -application/vnd.etsi.tsl+xml -application/vnd.eudora.data -application/vnd.ezpix-album ez2 -application/vnd.ezpix-package ez3 -application/vnd.f-secure.mobile -application/vnd.fastcopy-disk-image dim -application/vnd.fdf fdf -application/vnd.fdsn.mseed msd mseed -application/vnd.fdsn.seed seed dataless -application/vnd.ffsns -application/vnd.filmit.zfc zfc -# all extensions: application/vnd.hbci -application/vnd.fints -application/vnd.firemonkeys.cloudcell -application/vnd.FloGraphIt gph -application/vnd.fluxtime.clip ftc -application/vnd.font-fontforge-sfd sfd -application/vnd.framemaker fm -application/vnd.frogans.fnc fnc -application/vnd.frogans.ltf ltf -application/vnd.fsc.weblaunch fsc -application/vnd.fujitsu.oasys oas -application/vnd.fujitsu.oasys2 oa2 -application/vnd.fujitsu.oasys3 oa3 -application/vnd.fujitsu.oasysgp fg5 -application/vnd.fujitsu.oasysprs bh2 -application/vnd.fujixerox.ART-EX -application/vnd.fujixerox.ART4 -application/vnd.fujixerox.ddd ddd -application/vnd.fujixerox.docuworks xdw -application/vnd.fujixerox.docuworks.binder xbd -application/vnd.fujixerox.docuworks.container xct -application/vnd.fujixerox.HBPL -application/vnd.fut-misnet -application/vnd.fuzzysheet fzs -application/vnd.genomatix.tuxedo txd -# application/vnd.geo+json obsoleted by application/geo+json -application/vnd.geocube+xml g3 g³ -application/vnd.geogebra.file ggb -application/vnd.geogebra.tool ggt -application/vnd.geometry-explorer gex gre -application/vnd.geonext gxt -application/vnd.geoplan g2w -application/vnd.geospace g3w -# gbr: application/rpki-ghostbusters -application/vnd.gerber -application/vnd.globalplatform.card-content-mgt -application/vnd.globalplatform.card-content-mgt-response -application/vnd.gmx gmx -application/vnd.google-earth.kml+xml kml -application/vnd.google-earth.kmz kmz -application/vnd.gov.sk.e-form+xml -application/vnd.gov.sk.e-form+zip -application/vnd.gov.sk.xmldatacontainer+xml -application/vnd.grafeq gqf gqs -application/vnd.gridmp -application/vnd.groove-account gac -application/vnd.groove-help ghf -application/vnd.groove-identity-message gim -application/vnd.groove-injector grv -application/vnd.groove-tool-message gtm -application/vnd.groove-tool-template tpl -application/vnd.groove-vcard vcg -application/vnd.hal+json -application/vnd.hal+xml hal -application/vnd.HandHeld-Entertainment+xml zmm -application/vnd.hbci hbci hbc kom upa pkd bpd -application/vnd.hc+json -# rep: application/vnd.businessobjects -application/vnd.hcl-bireports -application/vnd.hdt hdt -application/vnd.heroku+json -application/vnd.hhe.lesson-player les -application/vnd.hp-HPGL hpgl -application/vnd.hp-hpid hpi hpid -application/vnd.hp-hps hps -application/vnd.hp-jlyt jlt -application/vnd.hp-PCL pcl -application/vnd.hp-PCLXL -application/vnd.httphone -application/vnd.hydrostatix.sof-data sfd-hdstx -application/vnd.hyperdrive+json -application/vnd.hzn-3d-crossword x3d -application/vnd.ibm.afplinedata -application/vnd.ibm.electronic-media emm -application/vnd.ibm.MiniPay mpy -application/vnd.ibm.modcap list3820 listafp afp pseg3820 -application/vnd.ibm.rights-management irm -application/vnd.ibm.secure-container sc -application/vnd.iccprofile icc icm -application/vnd.ieee.1905 1905.1 -application/vnd.igloader igl -application/vnd.imagemeter.folder+zip imf -application/vnd.imagemeter.image+zip imi -application/vnd.immervision-ivp ivp -application/vnd.immervision-ivu ivu -application/vnd.ims.imsccv1p1 imscc -application/vnd.ims.imsccv1p2 -application/vnd.ims.imsccv1p3 -application/vnd.ims.lis.v2.result+json -application/vnd.ims.lti.v2.toolconsumerprofile+json -application/vnd.ims.lti.v2.toolproxy.id+json -application/vnd.ims.lti.v2.toolproxy+json -application/vnd.ims.lti.v2.toolsettings+json -application/vnd.ims.lti.v2.toolsettings.simple+json -application/vnd.informedcontrol.rms+xml -# application/vnd.informix-visionary obsoleted by application/vnd.visionary -application/vnd.infotech.project -application/vnd.infotech.project+xml -application/vnd.innopath.wamp.notification -application/vnd.insors.igm igm -application/vnd.intercon.formnet xpw xpx -application/vnd.intergeo i2g -application/vnd.intertrust.digibox -application/vnd.intertrust.nncp -application/vnd.intu.qbo qbo -application/vnd.intu.qfx qfx -application/vnd.iptc.g2.catalogitem+xml -application/vnd.iptc.g2.conceptitem+xml -application/vnd.iptc.g2.knowledgeitem+xml -application/vnd.iptc.g2.newsitem+xml -application/vnd.iptc.g2.newsmessage+xml -application/vnd.iptc.g2.packageitem+xml -application/vnd.iptc.g2.planningitem+xml -application/vnd.ipunplugged.rcprofile rcprofile -application/vnd.irepository.package+xml irp -application/vnd.is-xpr xpr -application/vnd.isac.fcs fcs -application/vnd.jam jam -application/vnd.japannet-directory-service -application/vnd.japannet-jpnstore-wakeup -application/vnd.japannet-payment-wakeup -application/vnd.japannet-registration -application/vnd.japannet-registration-wakeup -application/vnd.japannet-setstore-wakeup -application/vnd.japannet-verification -application/vnd.japannet-verification-wakeup -application/vnd.jcp.javame.midlet-rms rms -application/vnd.jisp jisp -application/vnd.joost.joda-archive joda -application/vnd.jsk.isdn-ngn -application/vnd.kahootz ktz ktr -application/vnd.kde.karbon karbon -application/vnd.kde.kchart chrt -application/vnd.kde.kformula kfo -application/vnd.kde.kivio flw -application/vnd.kde.kontour kon -application/vnd.kde.kpresenter kpr kpt -application/vnd.kde.kspread ksp -application/vnd.kde.kword kwd kwt -application/vnd.kenameaapp htke -application/vnd.kidspiration kia -application/vnd.Kinar kne knp sdf -application/vnd.koan skp skd skm skt -application/vnd.kodak-descriptor sse -application/vnd.las.las+json lasjson -application/vnd.las.las+xml lasxml -application/vnd.liberty-request+xml -application/vnd.llamagraphics.life-balance.desktop lbd -application/vnd.llamagraphics.life-balance.exchange+xml lbe -application/vnd.lotus-1-2-3 123 wk4 wk3 wk1 -application/vnd.lotus-approach apr vew -application/vnd.lotus-freelance prz pre -application/vnd.lotus-notes nsf ntf ndl ns4 ns3 ns2 nsh nsg -application/vnd.lotus-organizer or3 or2 org -application/vnd.lotus-screencam scm -application/vnd.lotus-wordpro lwp sam -application/vnd.macports.portpkg portpkg -application/vnd.mapbox-vector-tile mvt -application/vnd.marlin.drm.actiontoken+xml -application/vnd.marlin.drm.conftoken+xml -application/vnd.marlin.drm.license+xml -application/vnd.marlin.drm.mdcf mdc -application/vnd.mason+json -application/vnd.maxmind.maxmind-db mmdb -application/vnd.mcd mcd -application/vnd.medcalcdata mc1 -application/vnd.mediastation.cdkey cdkey -application/vnd.meridian-slingshot -application/vnd.MFER mwf -application/vnd.mfmp mfm -application/vnd.micro+json -application/vnd.micrografx.flo flo -application/vnd.micrografx.igx igx -application/vnd.microsoft.portable-executable -application/vnd.microsoft.windows.thumbnail-cache -application/vnd.miele+json -application/vnd.mif mif -application/vnd.minisoft-hp3000-save -application/vnd.mitsubishi.misty-guard.trustweb -application/vnd.Mobius.DAF daf -application/vnd.Mobius.DIS dis -application/vnd.Mobius.MBK mbk -application/vnd.Mobius.MQY mqy -application/vnd.Mobius.MSL msl -application/vnd.Mobius.PLC plc -application/vnd.Mobius.TXF txf -application/vnd.mophun.application mpn -application/vnd.mophun.certificate mpc -application/vnd.motorola.flexsuite -application/vnd.motorola.flexsuite.adsi -application/vnd.motorola.flexsuite.fis -application/vnd.motorola.flexsuite.gotap -application/vnd.motorola.flexsuite.kmr -application/vnd.motorola.flexsuite.ttc -application/vnd.motorola.flexsuite.wem -application/vnd.motorola.iprm -application/vnd.mozilla.xul+xml xul -application/vnd.ms-3mfdocument 3mf -application/vnd.ms-artgalry cil -application/vnd.ms-asf asf -application/vnd.ms-cab-compressed cab -application/vnd.ms-excel xls xlm xla xlc xlt xlw -application/vnd.ms-excel.template.macroEnabled.12 xltm -application/vnd.ms-excel.addin.macroEnabled.12 xlam -application/vnd.ms-excel.sheet.binary.macroEnabled.12 xlsb -application/vnd.ms-excel.sheet.macroEnabled.12 xlsm -application/vnd.ms-fontobject eot -application/vnd.ms-htmlhelp chm -application/vnd.ms-ims ims -application/vnd.ms-lrm lrm -application/vnd.ms-office.activeX+xml -application/vnd.ms-officetheme thmx -application/vnd.ms-playready.initiator+xml -application/vnd.ms-powerpoint ppt pps pot -application/vnd.ms-powerpoint.addin.macroEnabled.12 ppam -application/vnd.ms-powerpoint.presentation.macroEnabled.12 pptm -application/vnd.ms-powerpoint.slide.macroEnabled.12 sldm -application/vnd.ms-powerpoint.slideshow.macroEnabled.12 ppsm -application/vnd.ms-powerpoint.template.macroEnabled.12 potm -application/vnd.ms-PrintDeviceCapabilities+xml -application/vnd.ms-PrintSchemaTicket+xml -application/vnd.ms-project mpp mpt -application/vnd.ms-tnef tnef tnf -application/vnd.ms-windows.devicepairing -application/vnd.ms-windows.nwprinting.oob -application/vnd.ms-windows.printerpairing -application/vnd.ms-windows.wsd.oob -application/vnd.ms-wmdrm.lic-chlg-req -application/vnd.ms-wmdrm.lic-resp -application/vnd.ms-wmdrm.meter-chlg-req -application/vnd.ms-wmdrm.meter-resp -application/vnd.ms-word.document.macroEnabled.12 docm -application/vnd.ms-word.template.macroEnabled.12 dotm -application/vnd.ms-works wcm wdb wks wps -application/vnd.ms-wpl wpl -application/vnd.ms-xpsdocument xps -application/vnd.msa-disk-image msa -application/vnd.mseq mseq -application/vnd.msign -application/vnd.multiad.creator crtr -application/vnd.multiad.creator.cif cif -application/vnd.music-niff -application/vnd.musician mus -application/vnd.muvee.style msty -application/vnd.mynfc taglet -application/vnd.ncd.control -application/vnd.ncd.reference -application/vnd.nearst.inv+json -application/vnd.nervana entity request bkm kcm -application/vnd.netfpx -# ntf: application/vnd.lotus-notes -application/vnd.nitf nitf -application/vnd.neurolanguage.nlu nlu -application/vnd.nintendo.nitro.rom nds -application/vnd.nintendo.snes.rom sfc smc -application/vnd.noblenet-directory nnd -application/vnd.noblenet-sealer nns -application/vnd.noblenet-web nnw -application/vnd.nokia.catalogs -application/vnd.nokia.conml+wbxml -application/vnd.nokia.conml+xml -application/vnd.nokia.iptv.config+xml -application/vnd.nokia.iSDS-radio-presets -application/vnd.nokia.landmark+wbxml -application/vnd.nokia.landmark+xml -application/vnd.nokia.landmarkcollection+xml -application/vnd.nokia.n-gage.ac+xml ac -application/vnd.nokia.n-gage.data ngdat -application/vnd.nokia.n-gage.symbian.install n-gage -application/vnd.nokia.ncd -application/vnd.nokia.pcd+wbxml -application/vnd.nokia.pcd+xml -application/vnd.nokia.radio-preset rpst -application/vnd.nokia.radio-presets rpss -application/vnd.novadigm.EDM edm -application/vnd.novadigm.EDX edx -application/vnd.novadigm.EXT ext -application/vnd.ntt-local.content-share -application/vnd.ntt-local.file-transfer -application/vnd.ntt-local.ogw_remote-access -application/vnd.ntt-local.sip-ta_remote -application/vnd.ntt-local.sip-ta_tcp_stream -application/vnd.oasis.opendocument.chart odc -application/vnd.oasis.opendocument.chart-template otc -application/vnd.oasis.opendocument.database odb -application/vnd.oasis.opendocument.formula odf -# otf: font/otf -application/vnd.oasis.opendocument.formula-template -application/vnd.oasis.opendocument.graphics odg -application/vnd.oasis.opendocument.graphics-template otg -application/vnd.oasis.opendocument.image odi -application/vnd.oasis.opendocument.image-template oti -application/vnd.oasis.opendocument.presentation odp -application/vnd.oasis.opendocument.presentation-template otp -application/vnd.oasis.opendocument.spreadsheet ods -application/vnd.oasis.opendocument.spreadsheet-template ots -application/vnd.oasis.opendocument.text odt -application/vnd.oasis.opendocument.text-master odm -application/vnd.oasis.opendocument.text-template ott -application/vnd.oasis.opendocument.text-web oth -application/vnd.obn -application/vnd.ocf+cbor -application/vnd.oftn.l10n+json -application/vnd.oipf.contentaccessdownload+xml -application/vnd.oipf.contentaccessstreaming+xml -application/vnd.oipf.cspg-hexbinary -application/vnd.oipf.dae.svg+xml -application/vnd.oipf.dae.xhtml+xml -application/vnd.oipf.mippvcontrolmessage+xml -application/vnd.oipf.pae.gem -application/vnd.oipf.spdiscovery+xml -application/vnd.oipf.spdlist+xml -application/vnd.oipf.ueprofile+xml -application/vnd.olpc-sugar xo -application/vnd.oma.bcast.associated-procedure-parameter+xml -application/vnd.oma.bcast.drm-trigger+xml -application/vnd.oma.bcast.imd+xml -application/vnd.oma.bcast.ltkm -application/vnd.oma.bcast.notification+xml -application/vnd.oma.bcast.provisioningtrigger -application/vnd.oma.bcast.sgboot -application/vnd.oma.bcast.sgdd+xml -application/vnd.oma.bcast.sgdu -application/vnd.oma.bcast.simple-symbol-container -application/vnd.oma.bcast.smartcard-trigger+xml -application/vnd.oma.bcast.sprov+xml -application/vnd.oma.bcast.stkm -application/vnd.oma.cab-address-book+xml -application/vnd.oma.cab-feature-handler+xml -application/vnd.oma.cab-pcc+xml -application/vnd.oma.cab-subs-invite+xml -application/vnd.oma.cab-user-prefs+xml -application/vnd.oma.dcd -application/vnd.oma.dcdc -application/vnd.oma.dd2+xml dd2 -application/vnd.oma.drm.risd+xml -application/vnd.oma.group-usage-list+xml -application/vnd.oma.lwm2m+json -application/vnd.oma.lwm2m+tlv -application/vnd.oma.pal+xml -application/vnd.oma.poc.detailed-progress-report+xml -application/vnd.oma.poc.final-report+xml -application/vnd.oma.poc.groups+xml -application/vnd.oma.poc.invocation-descriptor+xml -application/vnd.oma.poc.optimized-progress-report+xml -application/vnd.oma.push -application/vnd.oma.scidm.messages+xml -application/vnd.oma.xcap-directory+xml -application/vnd.oma-scws-config -application/vnd.oma-scws-http-request -application/vnd.oma-scws-http-response -application/vnd.omads-email+xml -application/vnd.omads-file+xml -application/vnd.omads-folder+xml -application/vnd.omaloc-supl-init -application/vnd.onepager tam -application/vnd.onepagertamp tamp -application/vnd.onepagertamx tamx -application/vnd.onepagertat tat -application/vnd.onepagertatp tatp -application/vnd.onepagertatx tatx -application/vnd.openblox.game+xml obgx -application/vnd.openblox.game-binary obg -application/vnd.openeye.oeb oeb -application/vnd.openofficeorg.extension oxt -application/vnd.openstreetmap.data+xml osm -application/vnd.openxmlformats-officedocument.custom-properties+xml -application/vnd.openxmlformats-officedocument.customXmlProperties+xml -application/vnd.openxmlformats-officedocument.drawing+xml -application/vnd.openxmlformats-officedocument.drawingml.chart+xml -application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml -application/vnd.openxmlformats-officedocument.drawingml.diagramColors+xml -application/vnd.openxmlformats-officedocument.drawingml.diagramData+xml -application/vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml -application/vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml -application/vnd.openxmlformats-officedocument.extended-properties+xml -application/vnd.openxmlformats-officedocument.presentationml.commentAuthors+xml -application/vnd.openxmlformats-officedocument.presentationml.comments+xml -application/vnd.openxmlformats-officedocument.presentationml.handoutMaster+xml -application/vnd.openxmlformats-officedocument.presentationml.notesMaster+xml -application/vnd.openxmlformats-officedocument.presentationml.notesSlide+xml -application/vnd.openxmlformats-officedocument.presentationml.presProps+xml -application/vnd.openxmlformats-officedocument.presentationml.presentation pptx -application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml -application/vnd.openxmlformats-officedocument.presentationml.slide sldx -application/vnd.openxmlformats-officedocument.presentationml.slide+xml -application/vnd.openxmlformats-officedocument.presentationml.slideLayout+xml -application/vnd.openxmlformats-officedocument.presentationml.slideMaster+xml -application/vnd.openxmlformats-officedocument.presentationml.slideUpdateInfo+xml -application/vnd.openxmlformats-officedocument.presentationml.slideshow ppsx -application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml -application/vnd.openxmlformats-officedocument.presentationml.tableStyles+xml -application/vnd.openxmlformats-officedocument.presentationml.tags+xml -application/vnd.openxmlformats-officedocument.presentationml.template potx -application/vnd.openxmlformats-officedocument.presentationml.template.main+xml -application/vnd.openxmlformats-officedocument.presentationml.viewProps+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.queryTable+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.revisionHeaders+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.revisionLog+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx -application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.tableSingleCells+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.template xltx -application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.userNames+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml -application/vnd.openxmlformats-officedocument.theme+xml -application/vnd.openxmlformats-officedocument.themeOverride+xml -application/vnd.openxmlformats-officedocument.vmlDrawing -application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml -application/vnd.openxmlformats-officedocument.wordprocessingml.document docx -application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml -application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml -application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml -application/vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml -application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml -application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml -application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml -application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml -application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml -application/vnd.openxmlformats-officedocument.wordprocessingml.template dotx -application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml -application/vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml -application/vnd.openxmlformats-package.core-properties+xml -application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml -application/vnd.openxmlformats-package.relationships+xml -application/vnd.oracle.resource+json -application/vnd.orange.indata -application/vnd.osa.netdeploy ndc -application/vnd.osgeo.mapguide.package mgp -# jar: application/x-java-archive -application/vnd.osgi.bundle -application/vnd.osgi.dp dp -application/vnd.osgi.subsystem esa -application/vnd.otps.ct-kip+xml -application/vnd.oxli.countgraph oxlicg -application/vnd.pagerduty+json -application/vnd.palm prc pdb pqa oprc -application/vnd.panoply plp -application/vnd.paos+xml -application/vnd.pawaafile paw -application/vnd.pcos -application/vnd.pg.format str -application/vnd.pg.osasli ei6 -application/vnd.piaccess.application-license pil -application/vnd.picsel efif -application/vnd.pmi.widget wg -application/vnd.poc.group-advertisement+xml -application/vnd.pocketlearn plf -application/vnd.powerbuilder6 pbd -application/vnd.powerbuilder6-s -application/vnd.powerbuilder7 -application/vnd.powerbuilder7-s -application/vnd.powerbuilder75 -application/vnd.powerbuilder75-s -application/vnd.preminet preminet -application/vnd.previewsystems.box box vbox -application/vnd.proteus.magazine mgz -application/vnd.publishare-delta-tree qps -# pti: image/prs.pti -application/vnd.pvi.ptid1 ptid -application/vnd.pwg-multiplexed -application/vnd.pwg-xhtml-print+xml -application/vnd.qualcomm.brew-app-res bar -application/vnd.quarantainenet -application/vnd.Quark.QuarkXPress qxd qxt qwd qwt qxl qxb -application/vnd.quobject-quoxdocument quox quiz -application/vnd.radisys.moml+xml -application/vnd.radisys.msml-audit-conf+xml -application/vnd.radisys.msml-audit-conn+xml -application/vnd.radisys.msml-audit-dialog+xml -application/vnd.radisys.msml-audit-stream+xml -application/vnd.radisys.msml-audit+xml -application/vnd.radisys.msml-conf+xml -application/vnd.radisys.msml-dialog-base+xml -application/vnd.radisys.msml-dialog-fax-detect+xml -application/vnd.radisys.msml-dialog-fax-sendrecv+xml -application/vnd.radisys.msml-dialog-group+xml -application/vnd.radisys.msml-dialog-speech+xml -application/vnd.radisys.msml-dialog-transform+xml -application/vnd.radisys.msml-dialog+xml -application/vnd.radisys.msml+xml -application/vnd.rainstor.data tree -application/vnd.rapid -application/vnd.rar rar -application/vnd.realvnc.bed bed -application/vnd.recordare.musicxml mxl -application/vnd.recordare.musicxml+xml -application/vnd.RenLearn.rlprint -application/vnd.rig.cryptonote cryptonote -application/vnd.route66.link66+xml link66 -# gbr: application/rpki-ghostbusters -application/vnd.rs-274x -application/vnd.ruckus.download -application/vnd.s3sms -application/vnd.sailingtracker.track st -application/vnd.sbm.cid -application/vnd.sbm.mid2 -application/vnd.scribus scd sla slaz -application/vnd.sealed.3df s3df -application/vnd.sealed.csf scsf -application/vnd.sealed.doc sdoc sdo s1w -application/vnd.sealed.eml seml sem -application/vnd.sealed.mht smht smh -application/vnd.sealed.net -# spp: application/scvp-vp-response -application/vnd.sealed.ppt sppt s1p -application/vnd.sealed.tiff stif -application/vnd.sealed.xls sxls sxl s1e -# stm: audio/x-stm -application/vnd.sealedmedia.softseal.html stml s1h -application/vnd.sealedmedia.softseal.pdf spdf spd s1a -application/vnd.seemail see -application/vnd.sema sema -application/vnd.semd semd -application/vnd.semf semf -application/vnd.shana.informed.formdata ifm -application/vnd.shana.informed.formtemplate itp -application/vnd.shana.informed.interchange iif -application/vnd.shana.informed.package ipk -application/vnd.SimTech-MindMapper twd twds -application/vnd.siren+json -application/vnd.smaf mmf -application/vnd.smart.notebook notebook -application/vnd.smart.teacher teacher -application/vnd.software602.filler.form+xml fo -application/vnd.software602.filler.form-xml-zip zfo -application/vnd.solent.sdkm+xml sdkm sdkd -application/vnd.spotfire.dxp dxp -application/vnd.spotfire.sfs sfs -application/vnd.sss-cod -application/vnd.sss-dtf -application/vnd.sss-ntf -application/vnd.stepmania.package smzip -application/vnd.stepmania.stepchart sm -application/vnd.street-stream -application/vnd.sun.wadl+xml wadl -application/vnd.sus-calendar sus susp -application/vnd.svd -application/vnd.swiftview-ics -application/vnd.syncml+xml xsm -application/vnd.syncml.dm+wbxml bdm -application/vnd.syncml.dm+xml xdm -application/vnd.syncml.dm.notification -application/vnd.syncml.dmddf+wbxml -application/vnd.syncml.dmddf+xml ddf -application/vnd.syncml.dmtnds+wbxml -application/vnd.syncml.dmtnds+xml -application/vnd.syncml.ds.notification -application/vnd.tableschema+json -application/vnd.tao.intent-module-archive tao -application/vnd.tcpdump.pcap pcap cap dmp -application/vnd.theqvd qvd -application/vnd.tmd.mediaflex.api+xml -application/vnd.tml vfr viaframe -application/vnd.tmobile-livetv tmo -application/vnd.tri.onesource -application/vnd.trid.tpt tpt -application/vnd.triscape.mxs mxs -application/vnd.trueapp tra -application/vnd.truedoc -# cab: application/vnd.ms-cab-compressed -application/vnd.ubisoft.webplayer -application/vnd.ufdl ufdl ufd frm -application/vnd.uiq.theme utz -application/vnd.umajin umj -application/vnd.unity unityweb -application/vnd.uoml+xml uoml uo -application/vnd.uplanet.alert -application/vnd.uplanet.alert-wbxml -application/vnd.uplanet.bearer-choice -application/vnd.uplanet.bearer-choice-wbxml -application/vnd.uplanet.cacheop -application/vnd.uplanet.cacheop-wbxml -application/vnd.uplanet.channel -application/vnd.uplanet.channel-wbxml -application/vnd.uplanet.list -application/vnd.uplanet.list-wbxml -application/vnd.uplanet.listcmd -application/vnd.uplanet.listcmd-wbxml -application/vnd.uplanet.signal -application/vnd.uri-map urim urimap -application/vnd.valve.source.material vmt -application/vnd.vcx vcx -# sxi: application/vnd.sun.xml.impress -application/vnd.vd-study mxi study-inter model-inter -# mcd: application/vnd.mcd -application/vnd.vectorworks vwx -application/vnd.vel+json -application/vnd.verimatrix.vcas -application/vnd.vidsoft.vidconference vsc -application/vnd.visio vsd vst vsw vss -application/vnd.visionary vis -# vsc: application/vnd.vidsoft.vidconference -application/vnd.vividence.scriptfile -application/vnd.vsf vsf -application/vnd.wap.sic sic -application/vnd.wap.slc slc -application/vnd.wap.wbxml wbxml -application/vnd.wap.wmlc wmlc -application/vnd.wap.wmlscriptc wmlsc -application/vnd.webturbo wtb -application/vnd.wfa.p2p p2p -application/vnd.wfa.wsc wsc -application/vnd.windows.devicepairing -application/vnd.wmc wmc -application/vnd.wmf.bootstrap -# nb: application/mathematica for now -application/vnd.wolfram.mathematica -application/vnd.wolfram.mathematica.package m -application/vnd.wolfram.player nbp -application/vnd.wordperfect wpd -application/vnd.wqd wqd -application/vnd.wrq-hp3000-labelled -application/vnd.wt.stf stf -application/vnd.wv.csp+xml -application/vnd.wv.csp+wbxml wv -application/vnd.wv.ssp+xml -application/vnd.xacml+json -application/vnd.xara xar -application/vnd.xfdl xfdl xfd -application/vnd.xfdl.webform -application/vnd.xmi+xml -application/vnd.xmpie.cpkg cpkg -application/vnd.xmpie.dpkg dpkg -# dpkg: application/vnd.xmpie.dpkg -application/vnd.xmpie.plan -application/vnd.xmpie.ppkg ppkg -application/vnd.xmpie.xlim xlim -application/vnd.yamaha.hv-dic hvd -application/vnd.yamaha.hv-script hvs -application/vnd.yamaha.hv-voice hvp -application/vnd.yamaha.openscoreformat osf -application/vnd.yamaha.openscoreformat.osfpvg+xml -application/vnd.yamaha.remote-setup -application/vnd.yamaha.smaf-audio saf -application/vnd.yamaha.smaf-phrase spf -application/vnd.yamaha.through-ngn -application/vnd.yamaha.tunnel-udpencap -application/vnd.yaoweme yme -application/vnd.yellowriver-custom-menu cmp -application/vnd.zul zir zirz -application/vnd.zzazz.deck+xml zaz -application/voicexml+xml vxml -application/vq-rtcp-xr -application/watcherinfo+xml wif -application/whoispp-query -application/whoispp-response -application/widget wgt -application/wita -application/wordperfect5.1 -application/wsdl+xml wsdl -application/wspolicy+xml wspolicy -# yes, this *is* IANA registered despite of x- -application/x-www-form-urlencoded -application/x400-bp -application/xacml+xml -application/xcap-att+xml xav -application/xcap-caps+xml xca -application/xcap-diff+xml xdf -application/xcap-el+xml xel -application/xcap-error+xml xer -application/xcap-ns+xml xns -application/xcon-conference-info-diff+xml -application/xcon-conference-info+xml -application/xenc+xml -application/xhtml+xml xhtml xhtm xht -# xml, xsd, rng: text/xml -application/xml -# mod: audio/x-mod -application/xml-dtd dtd -# ent: text/xml-external-parsed-entity -application/xml-external-parsed-entity -application/xml-patch+xml -application/xmpp+xml -application/xop+xml xop -application/xslt+xml xsl xslt -application/xv+xml mxml xhvml xvml xvm -application/yang yang -application/yang-data+json -application/yang-data+xml -application/yang-patch+json -application/yang-patch+xml -application/yin+xml yin -application/zip zip -application/zlib -audio/1d-interleaved-parityfec -audio/32kadpcm 726 -# 3gp, 3gpp: video/3gpp -audio/3gpp -# 3g2, 3gpp2: video/3gpp2 -audio/3gpp2 -audio/ac3 ac3 -audio/AMR amr -audio/AMR-WB awb -audio/amr-wb+ -audio/aptx -audio/asc acn -# aa3, omg: audio/ATRAC3 -audio/ATRAC-ADVANCED-LOSSLESS aal -# aa3, omg: audio/ATRAC3 -audio/ATRAC-X atx -audio/ATRAC3 at3 aa3 omg -audio/basic au snd -audio/BV16 -audio/BV32 -audio/clearmode -audio/CN -audio/DAT12 -audio/dls dls -audio/dsr-es201108 -audio/dsr-es202050 -audio/dsr-es202211 -audio/dsr-es202212 -audio/DV -audio/DVI4 -audio/eac3 -audio/encaprtp -audio/EVRC evc -# qcp: audio/qcelp -audio/EVRC-QCP -audio/EVRC0 -audio/EVRC1 -audio/EVRCB evb -audio/EVRCB0 -audio/EVRCB1 -audio/EVRCNW enw -audio/EVRCNW0 -audio/EVRCNW1 -audio/EVRCWB evw -audio/EVRCWB0 -audio/EVRCWB1 -audio/EVS -audio/example -audio/fwdred -audio/G711-0 -audio/G719 -audio/G722 -audio/G7221 -audio/G723 -audio/G726-16 -audio/G726-24 -audio/G726-32 -audio/G726-40 -audio/G728 -audio/G729 -audio/G7291 -audio/G729D -audio/G729E -audio/GSM -audio/GSM-EFR -audio/GSM-HR-08 -audio/iLBC lbc -audio/ip-mr_v2.5 -# wav: audio/x-wav -audio/L16 l16 -audio/L20 -audio/L24 -audio/L8 -audio/LPC -audio/MELP -audio/MELP600 -audio/MELP1200 -audio/MELP2400 -audio/mobile-xmf mxmf -# mp4, mpg4: video/mp4, see RFC 4337 -audio/mp4 m4a -audio/MP4A-LATM -audio/MPA -audio/mpa-robust -audio/mpeg mp3 mpga mp1 mp2 -audio/mpeg4-generic -audio/ogg oga ogg opus spx -audio/opus -audio/parityfec -audio/PCMA -audio/PCMA-WB -audio/PCMU -audio/PCMU-WB -audio/prs.sid sid psid -audio/qcelp qcp -audio/raptorfec -audio/RED -audio/rtp-enc-aescm128 -audio/rtp-midi -audio/rtploopback -audio/rtx -audio/SMV smv -# qcp: audio/qcelp, see RFC 3625 -audio/SMV-QCP -audio/SMV0 -# mid: audio/midi -audio/sp-midi -audio/speex -audio/t140c -audio/t38 -audio/telephone-event -audio/tone -audio/UEMCLIP -audio/ulpfec -audio/VDVI -audio/VMR-WB -audio/vnd.3gpp.iufp -audio/vnd.4SB -audio/vnd.audikoz koz -audio/vnd.CELP -audio/vnd.cisco.nse -audio/vnd.cmles.radio-events -audio/vnd.cns.anp1 -audio/vnd.cns.inf1 -audio/vnd.dece.audio uva uvva -audio/vnd.digital-winds eol -audio/vnd.dlna.adts -audio/vnd.dolby.heaac.1 -audio/vnd.dolby.heaac.2 -audio/vnd.dolby.mlp mlp -audio/vnd.dolby.mps -audio/vnd.dolby.pl2 -audio/vnd.dolby.pl2x -audio/vnd.dolby.pl2z -audio/vnd.dolby.pulse.1 -audio/vnd.dra -# wav: audio/x-wav, cpt: application/mac-compactpro -audio/vnd.dts dts -audio/vnd.dts.hd dtshd -# dvb: video/vnd.dvb.file -audio/vnd.dvb.file -audio/vnd.everad.plj plj -# rm: audio/x-pn-realaudio -audio/vnd.hns.audio -audio/vnd.lucent.voice lvp -audio/vnd.ms-playready.media.pya pya -# mxmf: audio/mobile-xmf -audio/vnd.nokia.mobile-xmf -audio/vnd.nortel.vbk vbk -audio/vnd.nuera.ecelp4800 ecelp4800 -audio/vnd.nuera.ecelp7470 ecelp7470 -audio/vnd.nuera.ecelp9600 ecelp9600 -audio/vnd.octel.sbc -# audio/vnd.qcelp deprecated in favour of audio/qcelp -audio/vnd.rhetorex.32kadpcm -audio/vnd.rip rip -audio/vnd.sealedmedia.softseal.mpeg smp3 smp s1m -audio/vnd.vmx.cvsd -audio/vorbis -audio/vorbis-config -font/collection ttc -font/otf otf -font/sfnt -font/ttf ttf -font/woff woff -font/woff2 woff2 -image/bmp bmp dib -image/cgm cgm -image/dicom-rle drle -image/emf emf -image/example -image/fits fits fit fts -image/g3fax -image/gif gif -image/ief ief -image/jls jls -image/jp2 jp2 jpg2 -image/jpeg jpg jpeg jpe jfif -image/jpm jpm jpgm -image/jpx jpx jpf -image/ktx ktx -image/naplps -image/png png -image/prs.btif btif btf -image/prs.pti pti -image/pwg-raster -image/svg+xml svg svgz -image/t38 t38 -image/tiff tiff tif -image/tiff-fx tfx -image/vnd.adobe.photoshop psd -image/vnd.airzip.accelerator.azv azv -image/vnd.cns.inf2 -image/vnd.dece.graphic uvi uvvi uvg uvvg -image/vnd.djvu djvu djv -# sub: text/vnd.dvb.subtitle -image/vnd.dvb.subtitle -image/vnd.dwg dwg -image/vnd.dxf dxf -image/vnd.fastbidsheet fbs -image/vnd.fpx fpx -image/vnd.fst fst -image/vnd.fujixerox.edmics-mmr mmr -image/vnd.fujixerox.edmics-rlc rlc -image/vnd.globalgraphics.pgb pgb -image/vnd.microsoft.icon ico -image/vnd.mix -image/vnd.mozilla.apng apng -image/vnd.ms-modi mdi -image/vnd.net-fpx -image/vnd.radiance hdr rgbe xyze -image/vnd.sealed.png spng spn s1n -image/vnd.sealedmedia.softseal.gif sgif sgi s1g -image/vnd.sealedmedia.softseal.jpg sjpg sjp s1j -image/vnd.svf -image/vnd.tencent.tap tap -image/vnd.valve.source.texture vtf -image/vnd.wap.wbmp wbmp -image/vnd.xiff xif -image/vnd.zbrush.pcx pcx -image/wmf wmf -message/CPIM -message/delivery-status -message/disposition-notification -message/example -message/external-body -message/feedback-report -message/global u8msg -message/global-delivery-status u8dsn -message/global-disposition-notification u8mdn -message/global-headers u8hdr -message/http -# cl: application/simple-filter+xml -message/imdn+xml -# message/news obsoleted by message/rfc822 -message/partial -message/rfc822 eml mail art -message/s-http -message/sip -message/sipfrag -message/tracking-status -message/vnd.si.simp -# wsc: application/vnd.wfa.wsc -message/vnd.wfa.wsc -model/example -model/gltf+json gltf -model/iges igs iges -model/mesh msh mesh silo -model/vnd.collada+xml dae -model/vnd.dwf dwf -# 3dml, 3dm: text/vnd.in3d.3dml -model/vnd.flatland.3dml -model/vnd.gdl gdl gsm win dor lmp rsm msm ism -model/vnd.gs-gdl -model/vnd.gtw gtw -model/vnd.moml+xml moml -model/vnd.mts mts -model/vnd.opengex ogex -model/vnd.parasolid.transmit.binary x_b xmt_bin -model/vnd.parasolid.transmit.text x_t xmt_txt -model/vnd.rosette.annotated-data-model -model/vnd.valve.source.compiled-map bsp -model/vnd.vtu vtu -model/vrml wrl vrml -# x3db: model/x3d+xml -model/x3d+fastinfoset -# x3d: application/vnd.hzn-3d-crossword -model/x3d+xml x3db -model/x3d-vrml x3dv x3dvz -multipart/alternative -multipart/appledouble -multipart/byteranges -multipart/digest -multipart/encrypted -multipart/form-data -multipart/header-set -multipart/mixed -multipart/parallel -multipart/related -multipart/report -multipart/signed -multipart/vnd.bint.med-plus bmed -multipart/voice-message vpm -multipart/x-mixed-replace -text/1d-interleaved-parityfec -text/cache-manifest appcache manifest -text/calendar ics ifb -text/css css -text/csv csv -text/csv-schema csvs -text/directory -text/dns soa zone -text/encaprtp -# text/ecmascript obsoleted by application/ecmascript -text/enriched -text/example -text/fwdred -text/grammar-ref-list -text/html html htm -# text/javascript obsoleted by application/javascript -text/jcr-cnd cnd -text/markdown markdown md -text/mizar miz -text/n3 n3 -text/parameters -text/parityfec -text/plain txt asc text pm el c h cc hh cxx hxx f90 conf log -text/provenance-notation provn -text/prs.fallenstein.rst rst -text/prs.lines.tag tag dsc -text/prs.prop.logic -text/raptorfec -text/RED -text/rfc822-headers -text/richtext rtx -# rtf: application/rtf -text/rtf -text/rtp-enc-aescm128 -text/rtploopback -text/rtx -text/sgml sgml sgm -text/strings -text/t140 -text/tab-separated-values tsv -text/troff t tr roff -text/turtle ttl -text/ulpfec -text/uri-list uris uri -text/vcard vcf vcard -text/vnd.a a -text/vnd.abc abc -text/vnd.ascii-art ascii -# curl: application/vnd.curl -text/vnd.curl -text/vnd.debian.copyright copyright -text/vnd.DMClientScript dms -text/vnd.dvb.subtitle sub -text/vnd.esmertec.theme-descriptor jtd -text/vnd.fly fly -text/vnd.fmi.flexstor flx -text/vnd.graphviz gv dot -text/vnd.in3d.3dml 3dml 3dm -text/vnd.in3d.spot spot spo -text/vnd.IPTC.NewsML -text/vnd.IPTC.NITF -text/vnd.latex-z -text/vnd.motorola.reflex -text/vnd.ms-mediapackage mpf -text/vnd.net2phone.commcenter.command ccc -text/vnd.radisys.msml-basic-layout -text/vnd.si.uricatalogue uric -text/vnd.sun.j2me.app-descriptor jad -text/vnd.trolltech.linguist ts -text/vnd.wap.si si -text/vnd.wap.sl sl -text/vnd.wap.wml wml -text/vnd.wap.wmlscript wmls -text/xml xml xsd rng -text/xml-external-parsed-entity ent -video/1d-interleaved-parityfec -video/3gpp 3gp 3gpp -video/3gpp2 3g2 3gpp2 -video/3gpp-tt -video/BMPEG -video/BT656 -video/CelB -video/DV -video/encaprtp -video/example -video/H261 -video/H263 -video/H263-1998 -video/H263-2000 -video/H264 -video/H264-RCDO -video/H264-SVC -video/H265 -video/iso.segment m4s -video/JPEG -video/jpeg2000 -video/mj2 mj2 mjp2 -video/MP1S -video/MP2P -video/MP2T -video/mp4 mp4 mpg4 m4v -video/MP4V-ES -video/mpeg mpeg mpg mpe m1v m2v -video/mpeg4-generic -video/MPV -video/nv -video/ogg ogv -video/parityfec -video/pointer -video/quicktime mov qt -video/raptorfec -video/raw -video/rtp-enc-aescm128 -video/rtploopback -video/rtx -video/SMPTE292M -video/ulpfec -video/vc1 -video/vnd.CCTV -video/vnd.dece.hd uvh uvvh -video/vnd.dece.mobile uvm uvvm -video/vnd.dece.mp4 uvu uvvu -video/vnd.dece.pd uvp uvvp -video/vnd.dece.sd uvs uvvs -video/vnd.dece.video uvv uvvv -video/vnd.directv.mpeg -video/vnd.directv.mpeg-tts -video/vnd.dlna.mpeg-tts -video/vnd.dvb.file dvb -video/vnd.fvt fvt -# rm: audio/x-pn-realaudio -video/vnd.hns.video -video/vnd.iptvforum.1dparityfec-1010 -video/vnd.iptvforum.1dparityfec-2005 -video/vnd.iptvforum.2dparityfec-1010 -video/vnd.iptvforum.2dparityfec-2005 -video/vnd.iptvforum.ttsavc -video/vnd.iptvforum.ttsmpeg2 -video/vnd.motorola.video -video/vnd.motorola.videop -video/vnd.mpegurl mxu m4u -video/vnd.ms-playready.media.pyv pyv -video/vnd.nokia.interleaved-multimedia nim -video/vnd.nokia.videovoip -# mp4: video/mp4 -video/vnd.objectvideo -video/vnd.radgamettools.bink bik bk2 -video/vnd.radgamettools.smacker smk -video/vnd.sealed.mpeg1 smpg s11 -# smpg: video/vnd.sealed.mpeg1 -video/vnd.sealed.mpeg4 s14 -video/vnd.sealed.swf sswf ssw -video/vnd.sealedmedia.softseal.mov smov smo s1q -# uvu, uvvu: video/vnd.dece.mp4 -video/vnd.uvvu.mp4 -video/vnd.vivo viv -video/VP8 - -# Non-IANA types - -application/mac-compactpro cpt -application/metalink+xml metalink -application/owl+xml owx -application/rss+xml rss -application/vnd.android.package-archive apk -application/vnd.oma.dd+xml dd -application/vnd.oma.drm.content dcf -# odf: application/vnd.oasis.opendocument.formula -application/vnd.oma.drm.dcf o4a o4v -application/vnd.oma.drm.message dm -application/vnd.oma.drm.rights+wbxml drc -application/vnd.oma.drm.rights+xml dr -application/vnd.sun.xml.calc sxc -application/vnd.sun.xml.calc.template stc -application/vnd.sun.xml.draw sxd -application/vnd.sun.xml.draw.template std -application/vnd.sun.xml.impress sxi -application/vnd.sun.xml.impress.template sti -application/vnd.sun.xml.math sxm -application/vnd.sun.xml.writer sxw -application/vnd.sun.xml.writer.global sxg -application/vnd.sun.xml.writer.template stw -application/vnd.symbian.install sis -application/vnd.wap.mms-message mms -application/x-annodex anx -application/x-bcpio bcpio -application/x-bittorrent torrent -application/x-bzip2 bz2 -application/x-cdlink vcd -application/x-chrome-extension crx -application/x-cpio cpio -application/x-csh csh -application/x-director dcr dir dxr -application/x-dvi dvi -application/x-futuresplash spl -application/x-gtar gtar -application/x-hdf hdf -application/x-java-archive jar -application/x-java-jnlp-file jnlp -application/x-java-pack200 pack -application/x-killustrator kil -application/x-latex latex -application/x-netcdf nc cdf -application/x-perl pl -application/x-rpm rpm -application/x-sh sh -application/x-shar shar -application/x-stuffit sit -application/x-sv4cpio sv4cpio -application/x-sv4crc sv4crc -application/x-tar tar -application/x-tcl tcl -application/x-tex tex -application/x-texinfo texinfo texi -application/x-troff-man man 1 2 3 4 5 6 7 8 -application/x-troff-me me -application/x-troff-ms ms -application/x-ustar ustar -application/x-wais-source src -application/x-xpinstall xpi -application/x-xspf+xml xspf -application/x-xz xz -audio/midi mid midi kar -audio/x-aiff aif aiff aifc -audio/x-annodex axa -audio/x-flac flac -audio/x-matroska mka -audio/x-mod mod ult uni m15 mtm 669 med -audio/x-mpegurl m3u -audio/x-ms-wax wax -audio/x-ms-wma wma -audio/x-pn-realaudio ram rm -audio/x-realaudio ra -audio/x-s3m s3m -audio/x-stm stm -audio/x-wav wav -chemical/x-xyz xyz -image/webp webp -image/x-cmu-raster ras -image/x-portable-anymap pnm -image/x-portable-bitmap pbm -image/x-portable-graymap pgm -image/x-portable-pixmap ppm -image/x-rgb rgb -image/x-targa tga -image/x-xbitmap xbm -image/x-xpixmap xpm -image/x-xwindowdump xwd -text/html-sandboxed sandboxed -text/x-pod pod -text/x-setext etx -video/webm webm -video/x-annodex axv -video/x-flv flv -video/x-javafx fxm -video/x-matroska mkv -video/x-matroska-3d mk3d -video/x-ms-asf asx -video/x-ms-wm wm -video/x-ms-wmv wmv -video/x-ms-wmx wmx -video/x-ms-wvx wvx -video/x-msvideo avi -video/x-sgi-movie movie -x-conference/x-cooltalk ice -x-epoc/x-sisx-app sisx diff --git a/cmd/swarm/run_test.go b/cmd/swarm/run_test.go deleted file mode 100644 index e75e21b69734..000000000000 --- a/cmd/swarm/run_test.go +++ /dev/null @@ -1,480 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "context" - "crypto/ecdsa" - "flag" - "fmt" - "io/ioutil" - "net" - "os" - "path" - "path/filepath" - "runtime" - "sync" - "syscall" - "testing" - "time" - - "github.com/docker/docker/pkg/reexec" - "github.com/ubiq/go-ubiq/accounts" - "github.com/ubiq/go-ubiq/accounts/keystore" - "github.com/ubiq/go-ubiq/internal/cmdtest" - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/rpc" - "github.com/ubiq/go-ubiq/swarm" - "github.com/ubiq/go-ubiq/swarm/api" - swarmhttp "github.com/ubiq/go-ubiq/swarm/api/http" -) - -var loglevel = flag.Int("loglevel", 3, "verbosity of logs") - -func init() { - // Run the app if we've been exec'd as "swarm-test" in runSwarm. - reexec.Register("swarm-test", func() { - if err := app.Run(os.Args); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - os.Exit(0) - }) -} - -const clusterSize = 3 - -var clusteronce sync.Once -var cluster *testCluster - -func initCluster(t *testing.T) { - clusteronce.Do(func() { - cluster = newTestCluster(t, clusterSize) - }) -} - -func serverFunc(api *api.API) swarmhttp.TestServer { - return swarmhttp.NewServer(api, "") -} -func TestMain(m *testing.M) { - // check if we have been reexec'd - if reexec.Init() { - return - } - os.Exit(m.Run()) -} - -func runSwarm(t *testing.T, args ...string) *cmdtest.TestCmd { - tt := cmdtest.NewTestCmd(t, nil) - - // Boot "swarm". This actually runs the test binary but the TestMain - // function will prevent any tests from running. - tt.Run("swarm-test", args...) - - return tt -} - -type testCluster struct { - Nodes []*testNode - TmpDir string -} - -// newTestCluster starts a test swarm cluster of the given size. -// -// A temporary directory is created and each node gets a data directory inside -// it. -// -// Each node listens on 127.0.0.1 with random ports for both the HTTP and p2p -// ports (assigned by first listening on 127.0.0.1:0 and then passing the ports -// as flags). -// -// When starting more than one node, they are connected together using the -// admin SetPeer RPC method. - -func newTestCluster(t *testing.T, size int) *testCluster { - cluster := &testCluster{} - defer func() { - if t.Failed() { - cluster.Shutdown() - } - }() - - tmpdir, err := ioutil.TempDir("", "swarm-test") - if err != nil { - t.Fatal(err) - } - cluster.TmpDir = tmpdir - - // start the nodes - cluster.StartNewNodes(t, size) - - if size == 1 { - return cluster - } - - // connect the nodes together - for _, node := range cluster.Nodes { - if err := node.Client.Call(nil, "admin_addPeer", cluster.Nodes[0].Enode); err != nil { - t.Fatal(err) - } - } - - // wait until all nodes have the correct number of peers -outer: - for _, node := range cluster.Nodes { - var peers []*p2p.PeerInfo - for start := time.Now(); time.Since(start) < time.Minute; time.Sleep(50 * time.Millisecond) { - if err := node.Client.Call(&peers, "admin_peers"); err != nil { - t.Fatal(err) - } - if len(peers) == len(cluster.Nodes)-1 { - continue outer - } - } - t.Fatalf("%s only has %d / %d peers", node.Name, len(peers), len(cluster.Nodes)-1) - } - - return cluster -} - -func (c *testCluster) Shutdown() { - for _, node := range c.Nodes { - node.Shutdown() - } - os.RemoveAll(c.TmpDir) -} - -func (c *testCluster) Stop() { - for _, node := range c.Nodes { - node.Shutdown() - } -} - -func (c *testCluster) StartNewNodes(t *testing.T, size int) { - c.Nodes = make([]*testNode, 0, size) - for i := 0; i < size; i++ { - dir := filepath.Join(c.TmpDir, fmt.Sprintf("swarm%02d", i)) - if err := os.Mkdir(dir, 0700); err != nil { - t.Fatal(err) - } - - node := newTestNode(t, dir) - node.Name = fmt.Sprintf("swarm%02d", i) - - c.Nodes = append(c.Nodes, node) - } -} - -func (c *testCluster) StartExistingNodes(t *testing.T, size int, bzzaccount string) { - c.Nodes = make([]*testNode, 0, size) - for i := 0; i < size; i++ { - dir := filepath.Join(c.TmpDir, fmt.Sprintf("swarm%02d", i)) - node := existingTestNode(t, dir, bzzaccount) - node.Name = fmt.Sprintf("swarm%02d", i) - - c.Nodes = append(c.Nodes, node) - } -} - -func (c *testCluster) Cleanup() { - os.RemoveAll(c.TmpDir) -} - -type testNode struct { - Name string - Addr string - URL string - Enode string - Dir string - IpcPath string - PrivateKey *ecdsa.PrivateKey - Client *rpc.Client - Cmd *cmdtest.TestCmd -} - -const testPassphrase = "swarm-test-passphrase" - -func getTestAccount(t *testing.T, dir string) (conf *node.Config, account accounts.Account) { - // create key - conf = &node.Config{ - DataDir: dir, - IPCPath: "bzzd.ipc", - NoUSB: true, - } - n, err := node.New(conf) - if err != nil { - t.Fatal(err) - } - account, err = n.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore).NewAccount(testPassphrase) - if err != nil { - t.Fatal(err) - } - - // use a unique IPCPath when running tests on Windows - if runtime.GOOS == "windows" { - conf.IPCPath = fmt.Sprintf("bzzd-%s.ipc", account.Address.String()) - } - - return conf, account -} - -func existingTestNode(t *testing.T, dir string, bzzaccount string) *testNode { - conf, _ := getTestAccount(t, dir) - node := &testNode{Dir: dir} - - // use a unique IPCPath when running tests on Windows - if runtime.GOOS == "windows" { - conf.IPCPath = fmt.Sprintf("bzzd-%s.ipc", bzzaccount) - } - - // assign ports - ports, err := getAvailableTCPPorts(2) - if err != nil { - t.Fatal(err) - } - p2pPort := ports[0] - httpPort := ports[1] - - // start the node - node.Cmd = runSwarm(t, - "--port", p2pPort, - "--nat", "extip:127.0.0.1", - "--datadir", dir, - "--ipcpath", conf.IPCPath, - "--ens-api", "", - "--bzzaccount", bzzaccount, - "--bzznetworkid", "321", - "--bzzport", httpPort, - "--verbosity", fmt.Sprint(*loglevel), - ) - node.Cmd.InputLine(testPassphrase) - defer func() { - if t.Failed() { - node.Shutdown() - } - }() - - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - // ensure that all ports have active listeners - // so that the next node will not get the same - // when calling getAvailableTCPPorts - err = waitTCPPorts(ctx, ports...) - if err != nil { - t.Fatal(err) - } - - // wait for the node to start - for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) { - node.Client, err = rpc.Dial(conf.IPCEndpoint()) - if err == nil { - break - } - } - if node.Client == nil { - t.Fatal(err) - } - - // load info - var info swarm.Info - if err := node.Client.Call(&info, "bzz_info"); err != nil { - t.Fatal(err) - } - node.Addr = net.JoinHostPort("127.0.0.1", info.Port) - node.URL = "http://" + node.Addr - - var nodeInfo p2p.NodeInfo - if err := node.Client.Call(&nodeInfo, "admin_nodeInfo"); err != nil { - t.Fatal(err) - } - node.Enode = nodeInfo.Enode - node.IpcPath = conf.IPCPath - return node -} - -func newTestNode(t *testing.T, dir string) *testNode { - - conf, account := getTestAccount(t, dir) - ks := keystore.NewKeyStore(path.Join(dir, "keystore"), 1<<18, 1) - - pk := decryptStoreAccount(ks, account.Address.Hex(), []string{testPassphrase}) - - node := &testNode{Dir: dir, PrivateKey: pk} - - // assign ports - ports, err := getAvailableTCPPorts(2) - if err != nil { - t.Fatal(err) - } - p2pPort := ports[0] - httpPort := ports[1] - - // start the node - node.Cmd = runSwarm(t, - "--port", p2pPort, - "--nat", "extip:127.0.0.1", - "--datadir", dir, - "--ipcpath", conf.IPCPath, - "--ens-api", "", - "--bzzaccount", account.Address.String(), - "--bzznetworkid", "321", - "--bzzport", httpPort, - "--verbosity", fmt.Sprint(*loglevel), - ) - node.Cmd.InputLine(testPassphrase) - defer func() { - if t.Failed() { - node.Shutdown() - } - }() - - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - // ensure that all ports have active listeners - // so that the next node will not get the same - // when calling getAvailableTCPPorts - err = waitTCPPorts(ctx, ports...) - if err != nil { - t.Fatal(err) - } - - // wait for the node to start - for start := time.Now(); time.Since(start) < 10*time.Second; time.Sleep(50 * time.Millisecond) { - node.Client, err = rpc.Dial(conf.IPCEndpoint()) - if err == nil { - break - } - } - if node.Client == nil { - t.Fatal(err) - } - - // load info - var info swarm.Info - if err := node.Client.Call(&info, "bzz_info"); err != nil { - t.Fatal(err) - } - node.Addr = net.JoinHostPort("127.0.0.1", info.Port) - node.URL = "http://" + node.Addr - - var nodeInfo p2p.NodeInfo - if err := node.Client.Call(&nodeInfo, "admin_nodeInfo"); err != nil { - t.Fatal(err) - } - node.Enode = nodeInfo.Enode - node.IpcPath = conf.IPCPath - return node -} - -func (n *testNode) Shutdown() { - if n.Cmd != nil { - n.Cmd.Kill() - } -} - -// getAvailableTCPPorts returns a set of ports that -// nothing is listening on at the time. -// -// Function assignTCPPort cannot be called in sequence -// and guardantee that the same port will be returned in -// different calls as the listener is closed within the function, -// not after all listeners are started and selected unique -// available ports. -func getAvailableTCPPorts(count int) (ports []string, err error) { - for i := 0; i < count; i++ { - l, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - return nil, err - } - // defer close in the loop to be sure the same port will not - // be selected in the next iteration - defer l.Close() - - _, port, err := net.SplitHostPort(l.Addr().String()) - if err != nil { - return nil, err - } - ports = append(ports, port) - } - return ports, nil -} - -// waitTCPPorts blocks until tcp connections can be -// established on all provided ports. It runs all -// ports dialers in parallel, and returns the first -// encountered error. -// See waitTCPPort also. -func waitTCPPorts(ctx context.Context, ports ...string) error { - var err error - // mu locks err variable that is assigned in - // other goroutines - var mu sync.Mutex - - // cancel is canceling all goroutines - // when the firs error is returned - // to prevent unnecessary waiting - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - var wg sync.WaitGroup - for _, port := range ports { - wg.Add(1) - go func(port string) { - defer wg.Done() - - e := waitTCPPort(ctx, port) - - mu.Lock() - defer mu.Unlock() - if e != nil && err == nil { - err = e - cancel() - } - }(port) - } - wg.Wait() - - return err -} - -// waitTCPPort blocks until tcp connection can be established -// ona provided port. It has a 3 minute timeout as maximum, -// to prevent long waiting, but it can be shortened with -// a provided context instance. Dialer has a 10 second timeout -// in every iteration, and connection refused error will be -// retried in 100 milliseconds periods. -func waitTCPPort(ctx context.Context, port string) error { - ctx, cancel := context.WithTimeout(ctx, 3*time.Minute) - defer cancel() - - for { - c, err := (&net.Dialer{Timeout: 10 * time.Second}).DialContext(ctx, "tcp", "127.0.0.1:"+port) - if err != nil { - if operr, ok := err.(*net.OpError); ok { - if syserr, ok := operr.Err.(*os.SyscallError); ok && syserr.Err == syscall.ECONNREFUSED { - time.Sleep(100 * time.Millisecond) - continue - } - } - return err - } - return c.Close() - } -} diff --git a/cmd/swarm/swarm-smoke/feed_upload_and_sync.go b/cmd/swarm/swarm-smoke/feed_upload_and_sync.go deleted file mode 100644 index fdfeec46189a..000000000000 --- a/cmd/swarm/swarm-smoke/feed_upload_and_sync.go +++ /dev/null @@ -1,291 +0,0 @@ -package main - -import ( - "bytes" - "crypto/md5" - "fmt" - "io" - "io/ioutil" - "os" - "os/exec" - "strings" - "sync" - "time" - - "github.com/ubiq/go-ubiq/common/hexutil" - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/swarm/storage/feed" - "github.com/ubiq/go-ubiq/swarm/testutil" - "github.com/pborman/uuid" - cli "gopkg.in/urfave/cli.v1" -) - -const ( - feedRandomDataLength = 8 -) - -func feedUploadAndSyncCmd(ctx *cli.Context, tuid string) error { - errc := make(chan error) - - go func() { - errc <- feedUploadAndSync(ctx, tuid) - }() - - select { - case err := <-errc: - if err != nil { - metrics.GetOrRegisterCounter(fmt.Sprintf("%s.fail", commandName), nil).Inc(1) - } - return err - case <-time.After(time.Duration(timeout) * time.Second): - metrics.GetOrRegisterCounter(fmt.Sprintf("%s.timeout", commandName), nil).Inc(1) - - return fmt.Errorf("timeout after %v sec", timeout) - } -} - -func feedUploadAndSync(c *cli.Context, tuid string) error { - log.Info("generating and uploading feeds to " + httpEndpoint(hosts[0]) + " and syncing") - - // create a random private key to sign updates with and derive the address - pkFile, err := ioutil.TempFile("", "swarm-feed-smoke-test") - if err != nil { - return err - } - defer pkFile.Close() - defer os.Remove(pkFile.Name()) - - privkeyHex := "0000000000000000000000000000000000000000000000000000000000001976" - privKey, err := crypto.HexToECDSA(privkeyHex) - if err != nil { - return err - } - user := crypto.PubkeyToAddress(privKey.PublicKey) - userHex := hexutil.Encode(user.Bytes()) - - // save the private key to a file - _, err = io.WriteString(pkFile, privkeyHex) - if err != nil { - return err - } - - // keep hex strings for topic and subtopic - var topicHex string - var subTopicHex string - - // and create combination hex topics for bzz-feed retrieval - // xor'ed with topic (zero-value topic if no topic) - var subTopicOnlyHex string - var mergedSubTopicHex string - - // generate random topic and subtopic and put a hex on them - topicBytes, err := generateRandomData(feed.TopicLength) - topicHex = hexutil.Encode(topicBytes) - subTopicBytes, err := generateRandomData(8) - subTopicHex = hexutil.Encode(subTopicBytes) - if err != nil { - return err - } - mergedSubTopic, err := feed.NewTopic(subTopicHex, topicBytes) - if err != nil { - return err - } - mergedSubTopicHex = hexutil.Encode(mergedSubTopic[:]) - subTopicOnlyBytes, err := feed.NewTopic(subTopicHex, nil) - if err != nil { - return err - } - subTopicOnlyHex = hexutil.Encode(subTopicOnlyBytes[:]) - - // create feed manifest, topic only - var out bytes.Buffer - cmd := exec.Command("swarm", "--bzzapi", httpEndpoint(hosts[0]), "feed", "create", "--topic", topicHex, "--user", userHex) - cmd.Stdout = &out - log.Debug("create feed manifest topic cmd", "cmd", cmd) - err = cmd.Run() - if err != nil { - return err - } - manifestWithTopic := strings.TrimRight(out.String(), string([]byte{0x0a})) - if len(manifestWithTopic) != 64 { - return fmt.Errorf("unknown feed create manifest hash format (topic): (%d) %s", len(out.String()), manifestWithTopic) - } - log.Debug("create topic feed", "manifest", manifestWithTopic) - out.Reset() - - // create feed manifest, subtopic only - cmd = exec.Command("swarm", "--bzzapi", httpEndpoint(hosts[0]), "feed", "create", "--name", subTopicHex, "--user", userHex) - cmd.Stdout = &out - log.Debug("create feed manifest subtopic cmd", "cmd", cmd) - err = cmd.Run() - if err != nil { - return err - } - manifestWithSubTopic := strings.TrimRight(out.String(), string([]byte{0x0a})) - if len(manifestWithSubTopic) != 64 { - return fmt.Errorf("unknown feed create manifest hash format (subtopic): (%d) %s", len(out.String()), manifestWithSubTopic) - } - log.Debug("create subtopic feed", "manifest", manifestWithTopic) - out.Reset() - - // create feed manifest, merged topic - cmd = exec.Command("swarm", "--bzzapi", httpEndpoint(hosts[0]), "feed", "create", "--topic", topicHex, "--name", subTopicHex, "--user", userHex) - cmd.Stdout = &out - log.Debug("create feed manifest mergetopic cmd", "cmd", cmd) - err = cmd.Run() - if err != nil { - log.Error(err.Error()) - return err - } - manifestWithMergedTopic := strings.TrimRight(out.String(), string([]byte{0x0a})) - if len(manifestWithMergedTopic) != 64 { - return fmt.Errorf("unknown feed create manifest hash format (mergedtopic): (%d) %s", len(out.String()), manifestWithMergedTopic) - } - log.Debug("create mergedtopic feed", "manifest", manifestWithMergedTopic) - out.Reset() - - // create test data - data, err := generateRandomData(feedRandomDataLength) - if err != nil { - return err - } - h := md5.New() - h.Write(data) - dataHash := h.Sum(nil) - dataHex := hexutil.Encode(data) - - // update with topic - cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", httpEndpoint(hosts[0]), "feed", "update", "--topic", topicHex, dataHex) - cmd.Stdout = &out - log.Debug("update feed manifest topic cmd", "cmd", cmd) - err = cmd.Run() - if err != nil { - return err - } - log.Debug("feed update topic", "out", out) - out.Reset() - - // update with subtopic - cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", httpEndpoint(hosts[0]), "feed", "update", "--name", subTopicHex, dataHex) - cmd.Stdout = &out - log.Debug("update feed manifest subtopic cmd", "cmd", cmd) - err = cmd.Run() - if err != nil { - return err - } - log.Debug("feed update subtopic", "out", out) - out.Reset() - - // update with merged topic - cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", httpEndpoint(hosts[0]), "feed", "update", "--topic", topicHex, "--name", subTopicHex, dataHex) - cmd.Stdout = &out - log.Debug("update feed manifest merged topic cmd", "cmd", cmd) - err = cmd.Run() - if err != nil { - return err - } - log.Debug("feed update mergedtopic", "out", out) - out.Reset() - - time.Sleep(3 * time.Second) - - // retrieve the data - wg := sync.WaitGroup{} - for _, host := range hosts { - // raw retrieve, topic only - for _, hex := range []string{topicHex, subTopicOnlyHex, mergedSubTopicHex} { - wg.Add(1) - ruid := uuid.New()[:8] - go func(hex string, endpoint string, ruid string) { - for { - err := fetchFeed(hex, userHex, httpEndpoint(host), dataHash, ruid) - if err != nil { - continue - } - - wg.Done() - return - } - }(hex, httpEndpoint(host), ruid) - } - } - wg.Wait() - log.Info("all endpoints synced random data successfully") - - // upload test file - log.Info("feed uploading to "+httpEndpoint(hosts[0])+" and syncing", "seed", seed) - - randomBytes := testutil.RandomBytes(seed, filesize*1000) - - hash, err := upload(randomBytes, httpEndpoint(hosts[0])) - if err != nil { - return err - } - hashBytes, err := hexutil.Decode("0x" + hash) - if err != nil { - return err - } - multihashHex := hexutil.Encode(hashBytes) - fileHash := h.Sum(nil) - - log.Info("uploaded successfully", "hash", hash, "digest", fmt.Sprintf("%x", fileHash)) - - // update file with topic - cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", httpEndpoint(hosts[0]), "feed", "update", "--topic", topicHex, multihashHex) - cmd.Stdout = &out - err = cmd.Run() - if err != nil { - return err - } - log.Debug("feed update topic", "out", out) - out.Reset() - - // update file with subtopic - cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", httpEndpoint(hosts[0]), "feed", "update", "--name", subTopicHex, multihashHex) - cmd.Stdout = &out - err = cmd.Run() - if err != nil { - return err - } - log.Debug("feed update subtopic", "out", out) - out.Reset() - - // update file with merged topic - cmd = exec.Command("swarm", "--bzzaccount", pkFile.Name(), "--bzzapi", httpEndpoint(hosts[0]), "feed", "update", "--topic", topicHex, "--name", subTopicHex, multihashHex) - cmd.Stdout = &out - err = cmd.Run() - if err != nil { - return err - } - log.Debug("feed update mergedtopic", "out", out) - out.Reset() - - time.Sleep(3 * time.Second) - - for _, host := range hosts { - - // manifest retrieve, topic only - for _, url := range []string{manifestWithTopic, manifestWithSubTopic, manifestWithMergedTopic} { - wg.Add(1) - ruid := uuid.New()[:8] - go func(url string, endpoint string, ruid string) { - for { - err := fetch(url, endpoint, fileHash, ruid, "") - if err != nil { - continue - } - - wg.Done() - return - } - }(url, httpEndpoint(host), ruid) - } - - } - wg.Wait() - log.Info("all endpoints synced random file successfully") - - return nil -} diff --git a/cmd/swarm/swarm-smoke/main.go b/cmd/swarm/swarm-smoke/main.go deleted file mode 100644 index 2cc2eeed823d..000000000000 --- a/cmd/swarm/swarm-smoke/main.go +++ /dev/null @@ -1,190 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "fmt" - "os" - "sort" - - "github.com/ubiq/go-ubiq/cmd/utils" - gubiqmetrics "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/metrics/influxdb" - swarmmetrics "github.com/ubiq/go-ubiq/swarm/metrics" - "github.com/ubiq/go-ubiq/swarm/tracing" - - "github.com/ubiq/go-ubiq/log" - - cli "gopkg.in/urfave/cli.v1" -) - -var ( - gitCommit string // Git SHA1 commit hash of the release (set via linker flags) -) - -var ( - allhosts string - hosts []string - filesize int - syncDelay int - httpPort int - wsPort int - verbosity int - timeout int - single bool - trackTimeout int -) - -func main() { - - app := cli.NewApp() - app.Name = "smoke-test" - app.Usage = "" - - app.Flags = []cli.Flag{ - cli.StringFlag{ - Name: "hosts", - Value: "", - Usage: "comma-separated list of swarm hosts", - Destination: &allhosts, - }, - cli.IntFlag{ - Name: "http-port", - Value: 80, - Usage: "http port", - Destination: &httpPort, - }, - cli.IntFlag{ - Name: "ws-port", - Value: 8546, - Usage: "ws port", - Destination: &wsPort, - }, - cli.IntFlag{ - Name: "filesize", - Value: 1024, - Usage: "file size for generated random file in KB", - Destination: &filesize, - }, - cli.IntFlag{ - Name: "sync-delay", - Value: 5, - Usage: "duration of delay in seconds to wait for content to be synced", - Destination: &syncDelay, - }, - cli.IntFlag{ - Name: "verbosity", - Value: 1, - Usage: "verbosity", - Destination: &verbosity, - }, - cli.IntFlag{ - Name: "timeout", - Value: 120, - Usage: "timeout in seconds after which kill the process", - Destination: &timeout, - }, - cli.BoolFlag{ - Name: "single", - Usage: "whether to fetch content from a single node or from all nodes", - Destination: &single, - }, - cli.IntFlag{ - Name: "track-timeout", - Value: 5, - Usage: "timeout in seconds to wait for GetAllReferences to return", - Destination: &trackTimeout, - }, - } - - app.Flags = append(app.Flags, []cli.Flag{ - utils.MetricsEnabledFlag, - swarmmetrics.MetricsInfluxDBEndpointFlag, - swarmmetrics.MetricsInfluxDBDatabaseFlag, - swarmmetrics.MetricsInfluxDBUsernameFlag, - swarmmetrics.MetricsInfluxDBPasswordFlag, - swarmmetrics.MetricsInfluxDBTagsFlag, - }...) - - app.Flags = append(app.Flags, tracing.Flags...) - - app.Commands = []cli.Command{ - { - Name: "upload_and_sync", - Aliases: []string{"c"}, - Usage: "upload and sync", - Action: wrapCliCommand("upload-and-sync", uploadAndSyncCmd), - }, - { - Name: "feed_sync", - Aliases: []string{"f"}, - Usage: "feed update generate, upload and sync", - Action: wrapCliCommand("feed-and-sync", feedUploadAndSyncCmd), - }, - { - Name: "upload_speed", - Aliases: []string{"u"}, - Usage: "measure upload speed", - Action: wrapCliCommand("upload-speed", uploadSpeedCmd), - }, - { - Name: "sliding_window", - Aliases: []string{"s"}, - Usage: "measure network aggregate capacity", - Action: wrapCliCommand("sliding-window", slidingWindowCmd), - }, - } - - sort.Sort(cli.FlagsByName(app.Flags)) - sort.Sort(cli.CommandsByName(app.Commands)) - - app.Before = func(ctx *cli.Context) error { - tracing.Setup(ctx) - return nil - } - - app.After = func(ctx *cli.Context) error { - return emitMetrics(ctx) - } - - err := app.Run(os.Args) - if err != nil { - log.Error(err.Error()) - - os.Exit(1) - } -} - -func emitMetrics(ctx *cli.Context) error { - if gubiqmetrics.Enabled { - var ( - endpoint = ctx.GlobalString(swarmmetrics.MetricsInfluxDBEndpointFlag.Name) - database = ctx.GlobalString(swarmmetrics.MetricsInfluxDBDatabaseFlag.Name) - username = ctx.GlobalString(swarmmetrics.MetricsInfluxDBUsernameFlag.Name) - password = ctx.GlobalString(swarmmetrics.MetricsInfluxDBPasswordFlag.Name) - tags = ctx.GlobalString(swarmmetrics.MetricsInfluxDBTagsFlag.Name) - ) - - tagsMap := utils.SplitTagsFlag(tags) - tagsMap["version"] = gitCommit - tagsMap["filesize"] = fmt.Sprintf("%v", filesize) - - return influxdb.InfluxDBWithTagsOnce(gubiqmetrics.DefaultRegistry, endpoint, database, username, password, "swarm-smoke.", tagsMap) - } - - return nil -} diff --git a/cmd/swarm/swarm-smoke/sliding_window.go b/cmd/swarm/swarm-smoke/sliding_window.go deleted file mode 100644 index 1c253be49063..000000000000 --- a/cmd/swarm/swarm-smoke/sliding_window.go +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "bytes" - "fmt" - "math/rand" - "time" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/swarm/testutil" - "github.com/pborman/uuid" - - cli "gopkg.in/urfave/cli.v1" -) - -type uploadResult struct { - hash string - digest []byte -} - -func slidingWindowCmd(ctx *cli.Context, tuid string) error { - errc := make(chan error) - - go func() { - errc <- slidingWindow(ctx, tuid) - }() - - select { - case err := <-errc: - if err != nil { - metrics.GetOrRegisterCounter(fmt.Sprintf("%s.fail", commandName), nil).Inc(1) - } - return err - case <-time.After(time.Duration(timeout) * time.Second): - metrics.GetOrRegisterCounter(fmt.Sprintf("%s.timeout", commandName), nil).Inc(1) - - return fmt.Errorf("timeout after %v sec", timeout) - } -} - -func slidingWindow(ctx *cli.Context, tuid string) error { - hashes := []uploadResult{} //swarm hashes of the uploads - nodes := len(hosts) - const iterationTimeout = 30 * time.Second - log.Info("sliding window test started", "tuid", tuid, "nodes", nodes, "filesize(kb)", filesize, "timeout", timeout) - uploadedBytes := 0 - networkDepth := 0 - errored := false - -outer: - for { - log.Info("uploading to "+httpEndpoint(hosts[0])+" and syncing", "seed", seed) - - t1 := time.Now() - - randomBytes := testutil.RandomBytes(seed, filesize*1000) - - hash, err := upload(randomBytes, httpEndpoint(hosts[0])) - if err != nil { - log.Error(err.Error()) - return err - } - - metrics.GetOrRegisterResettingTimer("sliding-window.upload-time", nil).UpdateSince(t1) - - fhash, err := digest(bytes.NewReader(randomBytes)) - if err != nil { - log.Error(err.Error()) - return err - } - - log.Info("uploaded successfully", "hash", hash, "digest", fmt.Sprintf("%x", fhash), "sleeping", syncDelay) - hashes = append(hashes, uploadResult{hash: hash, digest: fhash}) - time.Sleep(time.Duration(syncDelay) * time.Second) - uploadedBytes += filesize * 1000 - - for i, v := range hashes { - timeout := time.After(time.Duration(timeout) * time.Second) - errored = false - - inner: - for { - select { - case <-timeout: - errored = true - log.Error("error retrieving hash. timeout", "hash idx", i, "err", err) - metrics.GetOrRegisterCounter("sliding-window.single.error", nil).Inc(1) - break inner - default: - idx := 1 + rand.Intn(len(hosts)-1) - ruid := uuid.New()[:8] - start := time.Now() - err := fetch(v.hash, httpEndpoint(hosts[idx]), v.digest, ruid, "") - if err != nil { - continue inner - } - metrics.GetOrRegisterResettingTimer("sliding-window.single.fetch-time", nil).UpdateSince(start) - break inner - } - } - - if errored { - break outer - } - networkDepth = i - metrics.GetOrRegisterGauge("sliding-window.network-depth", nil).Update(int64(networkDepth)) - } - } - - log.Info("sliding window test finished", "errored?", errored, "networkDepth", networkDepth, "networkDepth(kb)", networkDepth*filesize) - log.Info("stats", "uploadedFiles", len(hashes), "uploadedKb", uploadedBytes/1000, "filesizeKb", filesize) - - return nil -} diff --git a/cmd/swarm/swarm-smoke/upload_and_sync.go b/cmd/swarm/swarm-smoke/upload_and_sync.go deleted file mode 100644 index baa8e50a343c..000000000000 --- a/cmd/swarm/swarm-smoke/upload_and_sync.go +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "bytes" - "context" - "fmt" - "io/ioutil" - "math/rand" - "os" - "sync" - "time" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/rpc" - "github.com/ubiq/go-ubiq/swarm/api" - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/ubiq/go-ubiq/swarm/testutil" - "github.com/pborman/uuid" - - cli "gopkg.in/urfave/cli.v1" -) - -func uploadAndSyncCmd(ctx *cli.Context, tuid string) error { - randomBytes := testutil.RandomBytes(seed, filesize*1000) - - errc := make(chan error) - - go func() { - errc <- uplaodAndSync(ctx, randomBytes, tuid) - }() - - select { - case err := <-errc: - if err != nil { - metrics.GetOrRegisterCounter(fmt.Sprintf("%s.fail", commandName), nil).Inc(1) - } - return err - case <-time.After(time.Duration(timeout) * time.Second): - metrics.GetOrRegisterCounter(fmt.Sprintf("%s.timeout", commandName), nil).Inc(1) - - e := fmt.Errorf("timeout after %v sec", timeout) - // trigger debug functionality on randomBytes - err := trackChunks(randomBytes[:]) - if err != nil { - e = fmt.Errorf("%v; triggerChunkDebug failed: %v", e, err) - } - - return e - } -} - -func trackChunks(testData []byte) error { - log.Warn("Test timed out; running chunk debug sequence") - - addrs, err := getAllRefs(testData) - if err != nil { - return err - } - log.Trace("All references retrieved") - - // has-chunks - for _, host := range hosts { - httpHost := fmt.Sprintf("ws://%s:%d", host, 8546) - log.Trace("Calling `Has` on host", "httpHost", httpHost) - rpcClient, err := rpc.Dial(httpHost) - if err != nil { - log.Trace("Error dialing host", "err", err) - return err - } - log.Trace("rpc dial ok") - var hasInfo []api.HasInfo - err = rpcClient.Call(&hasInfo, "bzz_has", addrs) - if err != nil { - log.Trace("Error calling host", "err", err) - return err - } - log.Trace("rpc call ok") - count := 0 - for _, info := range hasInfo { - if !info.Has { - count++ - log.Error("Host does not have chunk", "host", httpHost, "chunk", info.Addr) - } - } - if count == 0 { - log.Info("Host reported to have all chunks", "host", httpHost) - } - } - return nil -} - -func getAllRefs(testData []byte) (storage.AddressCollection, error) { - log.Trace("Getting all references for given root hash") - datadir, err := ioutil.TempDir("", "chunk-debug") - if err != nil { - return nil, fmt.Errorf("unable to create temp dir: %v", err) - } - defer os.RemoveAll(datadir) - fileStore, err := storage.NewLocalFileStore(datadir, make([]byte, 32)) - if err != nil { - return nil, err - } - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(trackTimeout)*time.Second) - defer cancel() - - reader := bytes.NewReader(testData) - return fileStore.GetAllReferences(ctx, reader, false) -} - -func uplaodAndSync(c *cli.Context, randomBytes []byte, tuid string) error { - log.Info("uploading to "+httpEndpoint(hosts[0])+" and syncing", "tuid", tuid, "seed", seed) - - t1 := time.Now() - hash, err := upload(randomBytes, httpEndpoint(hosts[0])) - if err != nil { - log.Error(err.Error()) - return err - } - t2 := time.Since(t1) - metrics.GetOrRegisterResettingTimer("upload-and-sync.upload-time", nil).Update(t2) - - fhash, err := digest(bytes.NewReader(randomBytes)) - if err != nil { - log.Error(err.Error()) - return err - } - - log.Info("uploaded successfully", "tuid", tuid, "hash", hash, "took", t2, "digest", fmt.Sprintf("%x", fhash)) - - time.Sleep(time.Duration(syncDelay) * time.Second) - - wg := sync.WaitGroup{} - if single { - randIndex := 1 + rand.Intn(len(hosts)-1) - ruid := uuid.New()[:8] - wg.Add(1) - go func(endpoint string, ruid string) { - for { - start := time.Now() - err := fetch(hash, endpoint, fhash, ruid, tuid) - if err != nil { - continue - } - ended := time.Since(start) - - metrics.GetOrRegisterResettingTimer("upload-and-sync.single.fetch-time", nil).Update(ended) - log.Info("fetch successful", "tuid", tuid, "ruid", ruid, "took", ended, "endpoint", endpoint) - wg.Done() - return - } - }(httpEndpoint(hosts[randIndex]), ruid) - } else { - for _, endpoint := range hosts[1:] { - ruid := uuid.New()[:8] - wg.Add(1) - go func(endpoint string, ruid string) { - for { - start := time.Now() - err := fetch(hash, endpoint, fhash, ruid, tuid) - if err != nil { - continue - } - ended := time.Since(start) - - metrics.GetOrRegisterResettingTimer("upload-and-sync.each.fetch-time", nil).Update(ended) - log.Info("fetch successful", "tuid", tuid, "ruid", ruid, "took", ended, "endpoint", endpoint) - wg.Done() - return - } - }(httpEndpoint(endpoint), ruid) - } - } - wg.Wait() - log.Info("all hosts synced random file successfully") - - return nil -} diff --git a/cmd/swarm/swarm-smoke/upload_speed.go b/cmd/swarm/swarm-smoke/upload_speed.go deleted file mode 100644 index 3769abe315f3..000000000000 --- a/cmd/swarm/swarm-smoke/upload_speed.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "bytes" - "fmt" - "time" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/swarm/testutil" - - cli "gopkg.in/urfave/cli.v1" -) - -func uploadSpeedCmd(ctx *cli.Context, tuid string) error { - log.Info("uploading to "+hosts[0], "tuid", tuid, "seed", seed) - randomBytes := testutil.RandomBytes(seed, filesize*1000) - - errc := make(chan error) - - go func() { - errc <- uploadSpeed(ctx, tuid, randomBytes) - }() - - select { - case err := <-errc: - if err != nil { - metrics.GetOrRegisterCounter(fmt.Sprintf("%s.fail", commandName), nil).Inc(1) - } - return err - case <-time.After(time.Duration(timeout) * time.Second): - metrics.GetOrRegisterCounter(fmt.Sprintf("%s.timeout", commandName), nil).Inc(1) - - // trigger debug functionality on randomBytes - - return fmt.Errorf("timeout after %v sec", timeout) - } -} - -func uploadSpeed(c *cli.Context, tuid string, data []byte) error { - t1 := time.Now() - hash, err := upload(data, hosts[0]) - if err != nil { - log.Error(err.Error()) - return err - } - metrics.GetOrRegisterCounter("upload-speed.upload-time", nil).Inc(int64(time.Since(t1))) - - fhash, err := digest(bytes.NewReader(data)) - if err != nil { - log.Error(err.Error()) - return err - } - - log.Info("uploaded successfully", "hash", hash, "digest", fmt.Sprintf("%x", fhash)) - return nil -} diff --git a/cmd/swarm/swarm-smoke/util.go b/cmd/swarm/swarm-smoke/util.go deleted file mode 100644 index b42ce900bb77..000000000000 --- a/cmd/swarm/swarm-smoke/util.go +++ /dev/null @@ -1,235 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "bytes" - "context" - "crypto/md5" - crand "crypto/rand" - "errors" - "fmt" - "io" - "io/ioutil" - "math/rand" - "net/http" - "net/http/httptrace" - "os" - "strings" - "time" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/swarm/api" - "github.com/ubiq/go-ubiq/swarm/api/client" - "github.com/ubiq/go-ubiq/swarm/spancontext" - opentracing "github.com/opentracing/opentracing-go" - "github.com/pborman/uuid" - cli "gopkg.in/urfave/cli.v1" -) - -var ( - commandName = "" - seed = int(time.Now().UTC().UnixNano()) -) - -func init() { - rand.Seed(int64(seed)) -} - -func httpEndpoint(host string) string { - return fmt.Sprintf("http://%s:%d", host, httpPort) -} - -func wsEndpoint(host string) string { - return fmt.Sprintf("ws://%s:%d", host, wsPort) -} - -func wrapCliCommand(name string, command func(*cli.Context, string) error) func(*cli.Context) error { - return func(ctx *cli.Context) error { - log.PrintOrigins(true) - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(verbosity), log.StreamHandler(os.Stdout, log.TerminalFormat(false)))) - - // test uuid - tuid := uuid.New()[:8] - - commandName = name - - hosts = strings.Split(allhosts, ",") - - defer func(now time.Time) { - totalTime := time.Since(now) - log.Info("total time", "tuid", tuid, "time", totalTime, "kb", filesize) - metrics.GetOrRegisterResettingTimer(name+".total-time", nil).Update(totalTime) - }(time.Now()) - - log.Info("smoke test starting", "tuid", tuid, "task", name, "timeout", timeout) - metrics.GetOrRegisterCounter(name, nil).Inc(1) - - return command(ctx, tuid) - } -} - -func fetchFeed(topic string, user string, endpoint string, original []byte, ruid string) error { - ctx, sp := spancontext.StartSpan(context.Background(), "feed-and-sync.fetch") - defer sp.Finish() - - log.Trace("sleeping", "ruid", ruid) - time.Sleep(3 * time.Second) - - log.Trace("http get request (feed)", "ruid", ruid, "api", endpoint, "topic", topic, "user", user) - - var tn time.Time - reqUri := endpoint + "/bzz-feed:/?topic=" + topic + "&user=" + user - req, _ := http.NewRequest("GET", reqUri, nil) - - opentracing.GlobalTracer().Inject( - sp.Context(), - opentracing.HTTPHeaders, - opentracing.HTTPHeadersCarrier(req.Header)) - - trace := client.GetClientTrace("feed-and-sync - http get", "feed-and-sync", ruid, &tn) - - req = req.WithContext(httptrace.WithClientTrace(ctx, trace)) - transport := http.DefaultTransport - - //transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - - tn = time.Now() - res, err := transport.RoundTrip(req) - if err != nil { - log.Error(err.Error(), "ruid", ruid) - return err - } - - log.Trace("http get response (feed)", "ruid", ruid, "api", endpoint, "topic", topic, "user", user, "code", res.StatusCode, "len", res.ContentLength) - - if res.StatusCode != 200 { - return fmt.Errorf("expected status code %d, got %v (ruid %v)", 200, res.StatusCode, ruid) - } - - defer res.Body.Close() - - rdigest, err := digest(res.Body) - if err != nil { - log.Warn(err.Error(), "ruid", ruid) - return err - } - - if !bytes.Equal(rdigest, original) { - err := fmt.Errorf("downloaded imported file md5=%x is not the same as the generated one=%x", rdigest, original) - log.Warn(err.Error(), "ruid", ruid) - return err - } - - log.Trace("downloaded file matches random file", "ruid", ruid, "len", res.ContentLength) - - return nil -} - -// fetch is getting the requested `hash` from the `endpoint` and compares it with the `original` file -func fetch(hash string, endpoint string, original []byte, ruid string, tuid string) error { - ctx, sp := spancontext.StartSpan(context.Background(), "upload-and-sync.fetch") - defer sp.Finish() - - log.Info("http get request", "tuid", tuid, "ruid", ruid, "endpoint", endpoint, "hash", hash) - - var tn time.Time - reqUri := endpoint + "/bzz:/" + hash + "/" - req, _ := http.NewRequest("GET", reqUri, nil) - - opentracing.GlobalTracer().Inject( - sp.Context(), - opentracing.HTTPHeaders, - opentracing.HTTPHeadersCarrier(req.Header)) - - trace := client.GetClientTrace(commandName+" - http get", commandName, ruid, &tn) - - req = req.WithContext(httptrace.WithClientTrace(ctx, trace)) - transport := http.DefaultTransport - - //transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} - - tn = time.Now() - res, err := transport.RoundTrip(req) - if err != nil { - log.Error(err.Error(), "ruid", ruid) - return err - } - log.Info("http get response", "tuid", tuid, "ruid", ruid, "endpoint", endpoint, "hash", hash, "code", res.StatusCode, "len", res.ContentLength) - - if res.StatusCode != 200 { - err := fmt.Errorf("expected status code %d, got %v", 200, res.StatusCode) - log.Warn(err.Error(), "ruid", ruid) - return err - } - - defer res.Body.Close() - - rdigest, err := digest(res.Body) - if err != nil { - log.Warn(err.Error(), "ruid", ruid) - return err - } - - if !bytes.Equal(rdigest, original) { - err := fmt.Errorf("downloaded imported file md5=%x is not the same as the generated one=%x", rdigest, original) - log.Warn(err.Error(), "ruid", ruid) - return err - } - - log.Trace("downloaded file matches random file", "ruid", ruid, "len", res.ContentLength) - - return nil -} - -// upload an arbitrary byte as a plaintext file to `endpoint` using the api client -func upload(data []byte, endpoint string) (string, error) { - swarm := client.NewClient(endpoint) - f := &client.File{ - ReadCloser: ioutil.NopCloser(bytes.NewReader(data)), - ManifestEntry: api.ManifestEntry{ - ContentType: "text/plain", - Mode: 0660, - Size: int64(len(data)), - }, - } - - // upload data to bzz:// and retrieve the content-addressed manifest hash, hex-encoded. - return swarm.Upload(f, "", false) -} - -func digest(r io.Reader) ([]byte, error) { - h := md5.New() - _, err := io.Copy(h, r) - if err != nil { - return nil, err - } - return h.Sum(nil), nil -} - -// generates random data in heap buffer -func generateRandomData(datasize int) ([]byte, error) { - b := make([]byte, datasize) - c, err := crand.Read(b) - if err != nil { - return nil, err - } else if c != datasize { - return nil, errors.New("short read") - } - return b, nil -} diff --git a/cmd/swarm/swarm-snapshot/create.go b/cmd/swarm/swarm-snapshot/create.go deleted file mode 100644 index 9fb7299c0bd1..000000000000 --- a/cmd/swarm/swarm-snapshot/create.go +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "io/ioutil" - "os" - "path" - "path/filepath" - "strings" - "sync" - "time" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p/simulations" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" - "github.com/ubiq/go-ubiq/swarm/network" - "github.com/ubiq/go-ubiq/swarm/network/simulation" - cli "gopkg.in/urfave/cli.v1" -) - -// create is used as the entry function for "create" app command. -func create(ctx *cli.Context) error { - log.PrintOrigins(true) - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(ctx.Int("verbosity")), log.StreamHandler(os.Stdout, log.TerminalFormat(true)))) - - if len(ctx.Args()) < 1 { - return errors.New("argument should be the filename to verify or write-to") - } - filename, err := touchPath(ctx.Args()[0]) - if err != nil { - return err - } - return createSnapshot(filename, ctx.Int("nodes"), strings.Split(ctx.String("services"), ",")) -} - -// createSnapshot creates a new snapshot on filesystem with provided filename, -// number of nodes and service names. -func createSnapshot(filename string, nodes int, services []string) (err error) { - log.Debug("create snapshot", "filename", filename, "nodes", nodes, "services", services) - - sim := simulation.New(map[string]simulation.ServiceFunc{ - "bzz": func(ctx *adapters.ServiceContext, b *sync.Map) (node.Service, func(), error) { - addr := network.NewAddr(ctx.Config.Node()) - kad := network.NewKademlia(addr.Over(), network.NewKadParams()) - hp := network.NewHiveParams() - hp.KeepAliveInterval = time.Duration(200) * time.Millisecond - hp.Discovery = true // discovery must be enabled when creating a snapshot - - config := &network.BzzConfig{ - OverlayAddr: addr.Over(), - UnderlayAddr: addr.Under(), - HiveParams: hp, - } - return network.NewBzz(config, kad, nil, nil, nil), nil, nil - }, - }) - defer sim.Close() - - _, err = sim.AddNodes(nodes) - if err != nil { - return fmt.Errorf("add nodes: %v", err) - } - - err = sim.Net.ConnectNodesRing(nil) - if err != nil { - return fmt.Errorf("connect nodes: %v", err) - } - - ctx, cancelSimRun := context.WithTimeout(context.Background(), 2*time.Minute) - defer cancelSimRun() - if _, err := sim.WaitTillHealthy(ctx); err != nil { - return fmt.Errorf("wait for healthy kademlia: %v", err) - } - - var snap *simulations.Snapshot - if len(services) > 0 { - // If service names are provided, include them in the snapshot. - // But, check if "bzz" service is not among them to remove it - // form the snapshot as it exists on snapshot creation. - var removeServices []string - var wantBzz bool - for _, s := range services { - if s == "bzz" { - wantBzz = true - break - } - } - if !wantBzz { - removeServices = []string{"bzz"} - } - snap, err = sim.Net.SnapshotWithServices(services, removeServices) - } else { - snap, err = sim.Net.Snapshot() - } - if err != nil { - return fmt.Errorf("create snapshot: %v", err) - } - jsonsnapshot, err := json.Marshal(snap) - if err != nil { - return fmt.Errorf("json encode snapshot: %v", err) - } - return ioutil.WriteFile(filename, jsonsnapshot, 0666) -} - -// touchPath creates an empty file and all subdirectories -// that are missing. -func touchPath(filename string) (string, error) { - if path.IsAbs(filename) { - if _, err := os.Stat(filename); err == nil { - // path exists, overwrite - return filename, nil - } - } - - d, f := path.Split(filename) - dir, err := filepath.Abs(filepath.Dir(os.Args[0])) - if err != nil { - return "", err - } - - _, err = os.Stat(path.Join(dir, filename)) - if err == nil { - // path exists, overwrite - return filename, nil - } - - dirPath := path.Join(dir, d) - filePath := path.Join(dirPath, f) - if d != "" { - err = os.MkdirAll(dirPath, os.ModeDir) - if err != nil { - return "", err - } - } - - return filePath, nil -} diff --git a/cmd/swarm/swarm-snapshot/create_test.go b/cmd/swarm/swarm-snapshot/create_test.go deleted file mode 100644 index fc02b2729242..000000000000 --- a/cmd/swarm/swarm-snapshot/create_test.go +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "os" - "runtime" - "sort" - "strconv" - "strings" - "testing" - - "github.com/ubiq/go-ubiq/p2p/simulations" -) - -// TestSnapshotCreate is a high level e2e test that tests for snapshot generation. -// It runs a few "create" commands with different flag values and loads generated -// snapshot files to validate their content. -func TestSnapshotCreate(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip() - } - - for _, v := range []struct { - name string - nodes int - services string - }{ - { - name: "defaults", - }, - { - name: "more nodes", - nodes: defaultNodes + 5, - }, - { - name: "services", - services: "stream,pss,zorglub", - }, - { - name: "services with bzz", - services: "bzz,pss", - }, - } { - t.Run(v.name, func(t *testing.T) { - t.Parallel() - - file, err := ioutil.TempFile("", "swarm-snapshot") - if err != nil { - t.Fatal(err) - } - defer os.Remove(file.Name()) - - if err = file.Close(); err != nil { - t.Error(err) - } - - args := []string{"create"} - if v.nodes > 0 { - args = append(args, "--nodes", strconv.Itoa(v.nodes)) - } - if v.services != "" { - args = append(args, "--services", v.services) - } - testCmd := runSnapshot(t, append(args, file.Name())...) - - testCmd.ExpectExit() - if code := testCmd.ExitStatus(); code != 0 { - t.Fatalf("command exit code %v, expected 0", code) - } - - f, err := os.Open(file.Name()) - if err != nil { - t.Fatal(err) - } - defer func() { - err := f.Close() - if err != nil { - t.Error("closing snapshot file", "err", err) - } - }() - - b, err := ioutil.ReadAll(f) - if err != nil { - t.Fatal(err) - } - var snap simulations.Snapshot - err = json.Unmarshal(b, &snap) - if err != nil { - t.Fatal(err) - } - - wantNodes := v.nodes - if wantNodes == 0 { - wantNodes = defaultNodes - } - gotNodes := len(snap.Nodes) - if gotNodes != wantNodes { - t.Errorf("got %v nodes, want %v", gotNodes, wantNodes) - } - - if len(snap.Conns) == 0 { - t.Error("no connections in a snapshot") - } - - var wantServices []string - if v.services != "" { - wantServices = strings.Split(v.services, ",") - } else { - wantServices = []string{"bzz"} - } - // sort service names so they can be comparable - // as strings to every node sorted services - sort.Strings(wantServices) - - for i, n := range snap.Nodes { - gotServices := n.Node.Config.Services - sort.Strings(gotServices) - if fmt.Sprint(gotServices) != fmt.Sprint(wantServices) { - t.Errorf("got services %v for node %v, want %v", gotServices, i, wantServices) - } - } - - }) - } -} diff --git a/cmd/swarm/swarm-snapshot/main.go b/cmd/swarm/swarm-snapshot/main.go deleted file mode 100644 index 7ab6fa8d2304..000000000000 --- a/cmd/swarm/swarm-snapshot/main.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "os" - - "github.com/ubiq/go-ubiq/cmd/utils" - "github.com/ubiq/go-ubiq/log" - cli "gopkg.in/urfave/cli.v1" -) - -var gitCommit string // Git SHA1 commit hash of the release (set via linker flags) - -// default value for "create" command --nodes flag -const defaultNodes = 10 - -func main() { - err := newApp().Run(os.Args) - if err != nil { - log.Error(err.Error()) - os.Exit(1) - } -} - -// newApp construct a new instance of Swarm Snapshot Utility. -// Method Run is called on it in the main function and in tests. -func newApp() (app *cli.App) { - app = utils.NewApp(gitCommit, "Swarm Snapshot Utility") - - app.Name = "swarm-snapshot" - app.Usage = "" - - // app flags (for all commands) - app.Flags = []cli.Flag{ - cli.IntFlag{ - Name: "verbosity", - Value: 1, - Usage: "verbosity level", - }, - } - - app.Commands = []cli.Command{ - { - Name: "create", - Aliases: []string{"c"}, - Usage: "create a swarm snapshot", - Action: create, - // Flags only for "create" command. - // Allow app flags to be specified after the - // command argument. - Flags: append(app.Flags, - cli.IntFlag{ - Name: "nodes", - Value: defaultNodes, - Usage: "number of nodes", - }, - cli.StringFlag{ - Name: "services", - Value: "bzz", - Usage: "comma separated list of services to boot the nodes with", - }, - ), - }, - } - - return app -} diff --git a/cmd/swarm/swarm-snapshot/run_test.go b/cmd/swarm/swarm-snapshot/run_test.go deleted file mode 100644 index c1c4f7f22833..000000000000 --- a/cmd/swarm/swarm-snapshot/run_test.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "fmt" - "os" - "testing" - - "github.com/docker/docker/pkg/reexec" - "github.com/ubiq/go-ubiq/internal/cmdtest" -) - -func init() { - reexec.Register("swarm-snapshot", func() { - if err := newApp().Run(os.Args); err != nil { - fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } - os.Exit(0) - }) -} - -func runSnapshot(t *testing.T, args ...string) *cmdtest.TestCmd { - tt := cmdtest.NewTestCmd(t, nil) - tt.Run("swarm-snapshot", args...) - return tt -} - -func TestMain(m *testing.M) { - if reexec.Init() { - return - } - os.Exit(m.Run()) -} diff --git a/cmd/swarm/upload.go b/cmd/swarm/upload.go deleted file mode 100644 index b50977a5efba..000000000000 --- a/cmd/swarm/upload.go +++ /dev/null @@ -1,188 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -// Command bzzup uploads files to the swarm HTTP API. -package main - -import ( - "errors" - "fmt" - "io" - "io/ioutil" - "os" - "os/user" - "path" - "path/filepath" - "strconv" - "strings" - - "github.com/ubiq/go-ubiq/log" - swarm "github.com/ubiq/go-ubiq/swarm/api/client" - - "github.com/ubiq/go-ubiq/cmd/utils" - "gopkg.in/urfave/cli.v1" -) - -var upCommand = cli.Command{ - Action: upload, - CustomHelpTemplate: helpTemplate, - Name: "up", - Usage: "uploads a file or directory to swarm using the HTTP API", - ArgsUsage: "", - Flags: []cli.Flag{SwarmEncryptedFlag}, - Description: "uploads a file or directory to swarm using the HTTP API and prints the root hash", -} - -func upload(ctx *cli.Context) { - args := ctx.Args() - var ( - bzzapi = strings.TrimRight(ctx.GlobalString(SwarmApiFlag.Name), "/") - recursive = ctx.GlobalBool(SwarmRecursiveFlag.Name) - wantManifest = ctx.GlobalBoolT(SwarmWantManifestFlag.Name) - defaultPath = ctx.GlobalString(SwarmUploadDefaultPath.Name) - fromStdin = ctx.GlobalBool(SwarmUpFromStdinFlag.Name) - mimeType = ctx.GlobalString(SwarmUploadMimeType.Name) - client = swarm.NewClient(bzzapi) - toEncrypt = ctx.Bool(SwarmEncryptedFlag.Name) - autoDefaultPath = false - file string - ) - if autoDefaultPathString := os.Getenv(SWARM_AUTO_DEFAULTPATH); autoDefaultPathString != "" { - b, err := strconv.ParseBool(autoDefaultPathString) - if err != nil { - utils.Fatalf("invalid environment variable %s: %v", SWARM_AUTO_DEFAULTPATH, err) - } - autoDefaultPath = b - } - if len(args) != 1 { - if fromStdin { - tmp, err := ioutil.TempFile("", "swarm-stdin") - if err != nil { - utils.Fatalf("error create tempfile: %s", err) - } - defer os.Remove(tmp.Name()) - n, err := io.Copy(tmp, os.Stdin) - if err != nil { - utils.Fatalf("error copying stdin to tempfile: %s", err) - } else if n == 0 { - utils.Fatalf("error reading from stdin: zero length") - } - file = tmp.Name() - } else { - utils.Fatalf("Need filename as the first and only argument") - } - } else { - file = expandPath(args[0]) - } - - if !wantManifest { - f, err := swarm.Open(file) - if err != nil { - utils.Fatalf("Error opening file: %s", err) - } - defer f.Close() - hash, err := client.UploadRaw(f, f.Size, toEncrypt) - if err != nil { - utils.Fatalf("Upload failed: %s", err) - } - fmt.Println(hash) - return - } - - stat, err := os.Stat(file) - if err != nil { - utils.Fatalf("Error opening file: %s", err) - } - - // define a function which either uploads a directory or single file - // based on the type of the file being uploaded - var doUpload func() (hash string, err error) - if stat.IsDir() { - doUpload = func() (string, error) { - if !recursive { - return "", errors.New("Argument is a directory and recursive upload is disabled") - } - if autoDefaultPath && defaultPath == "" { - defaultEntryCandidate := path.Join(file, "index.html") - log.Debug("trying to find default path", "path", defaultEntryCandidate) - defaultEntryStat, err := os.Stat(defaultEntryCandidate) - if err == nil && !defaultEntryStat.IsDir() { - log.Debug("setting auto detected default path", "path", defaultEntryCandidate) - defaultPath = defaultEntryCandidate - } - } - if defaultPath != "" { - // construct absolute default path - absDefaultPath, _ := filepath.Abs(defaultPath) - absFile, _ := filepath.Abs(file) - // make sure absolute directory ends with only one "/" - // to trim it from absolute default path and get relative default path - absFile = strings.TrimRight(absFile, "/") + "/" - if absDefaultPath != "" && absFile != "" && strings.HasPrefix(absDefaultPath, absFile) { - defaultPath = strings.TrimPrefix(absDefaultPath, absFile) - } - } - return client.UploadDirectory(file, defaultPath, "", toEncrypt) - } - } else { - doUpload = func() (string, error) { - f, err := swarm.Open(file) - if err != nil { - return "", fmt.Errorf("error opening file: %s", err) - } - defer f.Close() - if mimeType != "" { - f.ContentType = mimeType - } - return client.Upload(f, "", toEncrypt) - } - } - hash, err := doUpload() - if err != nil { - utils.Fatalf("Upload failed: %s", err) - } - fmt.Println(hash) -} - -// Expands a file path -// 1. replace tilde with users home dir -// 2. expands embedded environment variables -// 3. cleans the path, e.g. /a/b/../c -> /a/c -// Note, it has limitations, e.g. ~someuser/tmp will not be expanded -func expandPath(p string) string { - if i := strings.Index(p, ":"); i > 0 { - return p - } - if i := strings.Index(p, "@"); i > 0 { - return p - } - if strings.HasPrefix(p, "~/") || strings.HasPrefix(p, "~\\") { - if home := homeDir(); home != "" { - p = home + p[1:] - } - } - return path.Clean(os.ExpandEnv(p)) -} - -func homeDir() string { - if home := os.Getenv("HOME"); home != "" { - return home - } - if usr, err := user.Current(); err == nil { - return usr.HomeDir - } - return "" -} diff --git a/cmd/swarm/upload_test.go b/cmd/swarm/upload_test.go deleted file mode 100644 index 210126d7b1dc..000000000000 --- a/cmd/swarm/upload_test.go +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package main - -import ( - "bytes" - "fmt" - "io" - "io/ioutil" - "net/http" - "os" - "path" - "path/filepath" - "runtime" - "strings" - "testing" - "time" - - "github.com/ubiq/go-ubiq/log" - swarmapi "github.com/ubiq/go-ubiq/swarm/api/client" - "github.com/ubiq/go-ubiq/swarm/testutil" - "github.com/mattn/go-colorable" -) - -func init() { - log.PrintOrigins(true) - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))) -} - -func TestSwarmUp(t *testing.T) { - if runtime.GOOS == "windows" { - t.Skip() - } - - initCluster(t) - - cases := []struct { - name string - f func(t *testing.T) - }{ - {"NoEncryption", testNoEncryption}, - {"Encrypted", testEncrypted}, - {"RecursiveNoEncryption", testRecursiveNoEncryption}, - {"RecursiveEncrypted", testRecursiveEncrypted}, - {"DefaultPathAll", testDefaultPathAll}, - } - - for _, tc := range cases { - t.Run(tc.name, tc.f) - } -} - -// testNoEncryption tests that running 'swarm up' makes the resulting file -// available from all nodes via the HTTP API -func testNoEncryption(t *testing.T) { - testDefault(false, t) -} - -// testEncrypted tests that running 'swarm up --encrypted' makes the resulting file -// available from all nodes via the HTTP API -func testEncrypted(t *testing.T) { - testDefault(true, t) -} - -func testRecursiveNoEncryption(t *testing.T) { - testRecursive(false, t) -} - -func testRecursiveEncrypted(t *testing.T) { - testRecursive(true, t) -} - -func testDefault(toEncrypt bool, t *testing.T) { - tmpFileName := testutil.TempFileWithContent(t, data) - defer os.Remove(tmpFileName) - - // write data to file - hashRegexp := `[a-f\d]{64}` - flags := []string{ - "--bzzapi", cluster.Nodes[0].URL, - "up", - tmpFileName} - if toEncrypt { - hashRegexp = `[a-f\d]{128}` - flags = []string{ - "--bzzapi", cluster.Nodes[0].URL, - "up", - "--encrypt", - tmpFileName} - } - // upload the file with 'swarm up' and expect a hash - log.Info(fmt.Sprintf("uploading file with 'swarm up'")) - up := runSwarm(t, flags...) - _, matches := up.ExpectRegexp(hashRegexp) - up.ExpectExit() - hash := matches[0] - log.Info("file uploaded", "hash", hash) - - // get the file from the HTTP API of each node - for _, node := range cluster.Nodes { - log.Info("getting file from node", "node", node.Name) - - res, err := http.Get(node.URL + "/bzz:/" + hash) - if err != nil { - t.Fatal(err) - } - defer res.Body.Close() - - reply, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Fatal(err) - } - if res.StatusCode != 200 { - t.Fatalf("expected HTTP status 200, got %s", res.Status) - } - if string(reply) != data { - t.Fatalf("expected HTTP body %q, got %q", data, reply) - } - log.Debug("verifying uploaded file using `swarm down`") - //try to get the content with `swarm down` - tmpDownload, err := ioutil.TempDir("", "swarm-test") - tmpDownload = path.Join(tmpDownload, "tmpfile.tmp") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpDownload) - - bzzLocator := "bzz:/" + hash - flags = []string{ - "--bzzapi", cluster.Nodes[0].URL, - "down", - bzzLocator, - tmpDownload, - } - - down := runSwarm(t, flags...) - down.ExpectExit() - - fi, err := os.Stat(tmpDownload) - if err != nil { - t.Fatalf("could not stat path: %v", err) - } - - switch mode := fi.Mode(); { - case mode.IsRegular(): - downloadedBytes, err := ioutil.ReadFile(tmpDownload) - if err != nil { - t.Fatalf("had an error reading the downloaded file: %v", err) - } - if !bytes.Equal(downloadedBytes, bytes.NewBufferString(data).Bytes()) { - t.Fatalf("retrieved data and posted data not equal!") - } - - default: - t.Fatalf("expected to download regular file, got %s", fi.Mode()) - } - } - - timeout := time.Duration(2 * time.Second) - httpClient := http.Client{ - Timeout: timeout, - } - - // try to squeeze a timeout by getting an non-existent hash from each node - for _, node := range cluster.Nodes { - _, err := httpClient.Get(node.URL + "/bzz:/1023e8bae0f70be7d7b5f74343088ba408a218254391490c85ae16278e230340") - // we're speeding up the timeout here since netstore has a 60 seconds timeout on a request - if err != nil && !strings.Contains(err.Error(), "Client.Timeout exceeded while awaiting headers") { - t.Fatal(err) - } - // this is disabled since it takes 60s due to netstore timeout - // if res.StatusCode != 404 { - // t.Fatalf("expected HTTP status 404, got %s", res.Status) - // } - } -} - -func testRecursive(toEncrypt bool, t *testing.T) { - tmpUploadDir, err := ioutil.TempDir("", "swarm-test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpUploadDir) - // create tmp files - for _, path := range []string{"tmp1", "tmp2"} { - if err := ioutil.WriteFile(filepath.Join(tmpUploadDir, path), bytes.NewBufferString(data).Bytes(), 0644); err != nil { - t.Fatal(err) - } - } - - hashRegexp := `[a-f\d]{64}` - flags := []string{ - "--bzzapi", cluster.Nodes[0].URL, - "--recursive", - "up", - tmpUploadDir} - if toEncrypt { - hashRegexp = `[a-f\d]{128}` - flags = []string{ - "--bzzapi", cluster.Nodes[0].URL, - "--recursive", - "up", - "--encrypt", - tmpUploadDir} - } - // upload the file with 'swarm up' and expect a hash - log.Info(fmt.Sprintf("uploading file with 'swarm up'")) - up := runSwarm(t, flags...) - _, matches := up.ExpectRegexp(hashRegexp) - up.ExpectExit() - hash := matches[0] - log.Info("dir uploaded", "hash", hash) - - // get the file from the HTTP API of each node - for _, node := range cluster.Nodes { - log.Info("getting file from node", "node", node.Name) - //try to get the content with `swarm down` - tmpDownload, err := ioutil.TempDir("", "swarm-test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmpDownload) - bzzLocator := "bzz:/" + hash - flagss := []string{ - "--bzzapi", cluster.Nodes[0].URL, - "down", - "--recursive", - bzzLocator, - tmpDownload, - } - - fmt.Println("downloading from swarm with recursive") - down := runSwarm(t, flagss...) - down.ExpectExit() - - files, err := ioutil.ReadDir(tmpDownload) - for _, v := range files { - fi, err := os.Stat(path.Join(tmpDownload, v.Name())) - if err != nil { - t.Fatalf("got an error: %v", err) - } - - switch mode := fi.Mode(); { - case mode.IsRegular(): - if file, err := swarmapi.Open(path.Join(tmpDownload, v.Name())); err != nil { - t.Fatalf("encountered an error opening the file returned from the CLI: %v", err) - } else { - ff := make([]byte, len(data)) - io.ReadFull(file, ff) - buf := bytes.NewBufferString(data) - - if !bytes.Equal(ff, buf.Bytes()) { - t.Fatalf("retrieved data and posted data not equal!") - } - } - default: - t.Fatalf("this shouldnt happen") - } - } - if err != nil { - t.Fatalf("could not list files at: %v", files) - } - } -} - -// testDefaultPathAll tests swarm recursive upload with relative and absolute -// default paths and with encryption. -func testDefaultPathAll(t *testing.T) { - testDefaultPath(false, false, t) - testDefaultPath(false, true, t) - testDefaultPath(true, false, t) - testDefaultPath(true, true, t) -} - -func testDefaultPath(toEncrypt bool, absDefaultPath bool, t *testing.T) { - tmp, err := ioutil.TempDir("", "swarm-defaultpath-test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmp) - - err = ioutil.WriteFile(filepath.Join(tmp, "index.html"), []byte("

Test

"), 0666) - if err != nil { - t.Fatal(err) - } - err = ioutil.WriteFile(filepath.Join(tmp, "robots.txt"), []byte("Disallow: /"), 0666) - if err != nil { - t.Fatal(err) - } - - defaultPath := "index.html" - if absDefaultPath { - defaultPath = filepath.Join(tmp, defaultPath) - } - - args := []string{ - "--bzzapi", - cluster.Nodes[0].URL, - "--recursive", - "--defaultpath", - defaultPath, - "up", - tmp, - } - if toEncrypt { - args = append(args, "--encrypt") - } - - up := runSwarm(t, args...) - hashRegexp := `[a-f\d]{64,128}` - _, matches := up.ExpectRegexp(hashRegexp) - up.ExpectExit() - hash := matches[0] - - client := swarmapi.NewClient(cluster.Nodes[0].URL) - - m, isEncrypted, err := client.DownloadManifest(hash) - if err != nil { - t.Fatal(err) - } - - if toEncrypt != isEncrypted { - t.Error("downloaded manifest is not encrypted") - } - - var found bool - var entriesCount int - for _, e := range m.Entries { - entriesCount++ - if e.Path == "" { - found = true - } - } - - if !found { - t.Error("manifest default entry was not found") - } - - if entriesCount != 3 { - t.Errorf("manifest contains %v entries, expected %v", entriesCount, 3) - } -} diff --git a/contracts/chequebook/api.go b/contracts/chequebook/api.go deleted file mode 100644 index 4b8b72c4e602..000000000000 --- a/contracts/chequebook/api.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package chequebook - -import ( - "errors" - "math/big" - - "github.com/ubiq/go-ubiq/common" -) - -const Version = "1.0" - -var errNoChequebook = errors.New("no chequebook") - -type API struct { - chequebookf func() *Chequebook -} - -func NewAPI(ch func() *Chequebook) *API { - return &API{ch} -} - -func (a *API) Balance() (string, error) { - ch := a.chequebookf() - if ch == nil { - return "", errNoChequebook - } - return ch.Balance().String(), nil -} - -func (a *API) Issue(beneficiary common.Address, amount *big.Int) (cheque *Cheque, err error) { - ch := a.chequebookf() - if ch == nil { - return nil, errNoChequebook - } - return ch.Issue(beneficiary, amount) -} - -func (a *API) Cash(cheque *Cheque) (txhash string, err error) { - ch := a.chequebookf() - if ch == nil { - return "", errNoChequebook - } - return ch.Cash(cheque) -} - -func (a *API) Deposit(amount *big.Int) (txhash string, err error) { - ch := a.chequebookf() - if ch == nil { - return "", errNoChequebook - } - return ch.Deposit(amount) -} diff --git a/contracts/chequebook/cheque.go b/contracts/chequebook/cheque.go deleted file mode 100644 index b6819b16e7e0..000000000000 --- a/contracts/chequebook/cheque.go +++ /dev/null @@ -1,641 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Package chequebook package wraps the 'chequebook' Ethereum smart contract. -// -// The functions in this package allow using chequebook for -// issuing, receiving, verifying cheques in ether; (auto)cashing cheques in ether -// as well as (auto)depositing ether to the chequebook contract. -package chequebook - -//go:generate abigen --sol contract/chequebook.sol --exc contract/mortal.sol:mortal,contract/owned.sol:owned --pkg contract --out contract/chequebook.go -//go:generate go run ./gencode.go - -import ( - "bytes" - "context" - "crypto/ecdsa" - "encoding/json" - "fmt" - "io/ioutil" - "math/big" - "os" - "sync" - "time" - - "github.com/ubiq/go-ubiq/accounts/abi/bind" - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/common/hexutil" - "github.com/ubiq/go-ubiq/contracts/chequebook/contract" - "github.com/ubiq/go-ubiq/core/types" - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/swarm/services/swap/swap" -) - -// TODO(zelig): watch peer solvency and notify of bouncing cheques -// TODO(zelig): enable paying with cheque by signing off - -// Some functionality requires interacting with the blockchain: -// * setting current balance on peer's chequebook -// * sending the transaction to cash the cheque -// * depositing ether to the chequebook -// * watching incoming ether - -var ( - gasToCash = uint64(2000000) // gas cost of a cash transaction using chequebook - // gasToDeploy = uint64(3000000) -) - -// Backend wraps all methods required for chequebook operation. -type Backend interface { - bind.ContractBackend - TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) - BalanceAt(ctx context.Context, address common.Address, blockNum *big.Int) (*big.Int, error) -} - -// Cheque represents a payment promise to a single beneficiary. -type Cheque struct { - Contract common.Address // address of chequebook, needed to avoid cross-contract submission - Beneficiary common.Address - Amount *big.Int // cumulative amount of all funds sent - Sig []byte // signature Sign(Keccak256(contract, beneficiary, amount), prvKey) -} - -func (ch *Cheque) String() string { - return fmt.Sprintf("contract: %s, beneficiary: %s, amount: %v, signature: %x", ch.Contract.Hex(), ch.Beneficiary.Hex(), ch.Amount, ch.Sig) -} - -type Params struct { - ContractCode, ContractAbi string -} - -var ContractParams = &Params{contract.ChequebookBin, contract.ChequebookABI} - -// Chequebook can create and sign cheques from a single contract to multiple beneficiaries. -// It is the outgoing payment handler for peer to peer micropayments. -type Chequebook struct { - path string // path to chequebook file - prvKey *ecdsa.PrivateKey // private key to sign cheque with - lock sync.Mutex // - backend Backend // blockchain API - quit chan bool // when closed causes autodeposit to stop - owner common.Address // owner address (derived from pubkey) - contract *contract.Chequebook // abigen binding - session *contract.ChequebookSession // abigen binding with Tx Opts - - // persisted fields - balance *big.Int // not synced with blockchain - contractAddr common.Address // contract address - sent map[common.Address]*big.Int //tallies for beneficiaries - - txhash string // tx hash of last deposit tx - threshold *big.Int // threshold that triggers autodeposit if not nil - buffer *big.Int // buffer to keep on top of balance for fork protection - - log log.Logger // contextual logger with the contract address embedded -} - -func (chbook *Chequebook) String() string { - return fmt.Sprintf("contract: %s, owner: %s, balance: %v, signer: %x", chbook.contractAddr.Hex(), chbook.owner.Hex(), chbook.balance, chbook.prvKey.PublicKey) -} - -// NewChequebook creates a new Chequebook. -func NewChequebook(path string, contractAddr common.Address, prvKey *ecdsa.PrivateKey, backend Backend) (self *Chequebook, err error) { - balance := new(big.Int) - sent := make(map[common.Address]*big.Int) - - chbook, err := contract.NewChequebook(contractAddr, backend) - if err != nil { - return nil, err - } - transactOpts := bind.NewKeyedTransactor(prvKey) - session := &contract.ChequebookSession{ - Contract: chbook, - TransactOpts: *transactOpts, - } - - self = &Chequebook{ - prvKey: prvKey, - balance: balance, - contractAddr: contractAddr, - sent: sent, - path: path, - backend: backend, - owner: transactOpts.From, - contract: chbook, - session: session, - log: log.New("contract", contractAddr), - } - - if (contractAddr != common.Address{}) { - self.setBalanceFromBlockChain() - self.log.Trace("New chequebook initialised", "owner", self.owner, "balance", self.balance) - } - return -} - -func (chbook *Chequebook) setBalanceFromBlockChain() { - balance, err := chbook.backend.BalanceAt(context.TODO(), chbook.contractAddr, nil) - if err != nil { - log.Error("Failed to retrieve chequebook balance", "err", err) - } else { - chbook.balance.Set(balance) - } -} - -// LoadChequebook loads a chequebook from disk (file path). -func LoadChequebook(path string, prvKey *ecdsa.PrivateKey, backend Backend, checkBalance bool) (self *Chequebook, err error) { - var data []byte - data, err = ioutil.ReadFile(path) - if err != nil { - return - } - self, _ = NewChequebook(path, common.Address{}, prvKey, backend) - - err = json.Unmarshal(data, self) - if err != nil { - return nil, err - } - if checkBalance { - self.setBalanceFromBlockChain() - } - log.Trace("Loaded chequebook from disk", "path", path) - - return -} - -// chequebookFile is the JSON representation of a chequebook. -type chequebookFile struct { - Balance string - Contract string - Owner string - Sent map[string]string -} - -// UnmarshalJSON deserialises a chequebook. -func (chbook *Chequebook) UnmarshalJSON(data []byte) error { - var file chequebookFile - err := json.Unmarshal(data, &file) - if err != nil { - return err - } - _, ok := chbook.balance.SetString(file.Balance, 10) - if !ok { - return fmt.Errorf("cumulative amount sent: unable to convert string to big integer: %v", file.Balance) - } - chbook.contractAddr = common.HexToAddress(file.Contract) - for addr, sent := range file.Sent { - chbook.sent[common.HexToAddress(addr)], ok = new(big.Int).SetString(sent, 10) - if !ok { - return fmt.Errorf("beneficiary %v cumulative amount sent: unable to convert string to big integer: %v", addr, sent) - } - } - return nil -} - -// MarshalJSON serialises a chequebook. -func (chbook *Chequebook) MarshalJSON() ([]byte, error) { - var file = &chequebookFile{ - Balance: chbook.balance.String(), - Contract: chbook.contractAddr.Hex(), - Owner: chbook.owner.Hex(), - Sent: make(map[string]string), - } - for addr, sent := range chbook.sent { - file.Sent[addr.Hex()] = sent.String() - } - return json.Marshal(file) -} - -// Save persists the chequebook on disk, remembering balance, contract address and -// cumulative amount of funds sent for each beneficiary. -func (chbook *Chequebook) Save() (err error) { - data, err := json.MarshalIndent(chbook, "", " ") - if err != nil { - return err - } - chbook.log.Trace("Saving chequebook to disk", chbook.path) - - return ioutil.WriteFile(chbook.path, data, os.ModePerm) -} - -// Stop quits the autodeposit go routine to terminate -func (chbook *Chequebook) Stop() { - defer chbook.lock.Unlock() - chbook.lock.Lock() - if chbook.quit != nil { - close(chbook.quit) - chbook.quit = nil - } -} - -// Issue creates a cheque signed by the chequebook owner's private key. The -// signer commits to a contract (one that they own), a beneficiary and amount. -func (chbook *Chequebook) Issue(beneficiary common.Address, amount *big.Int) (ch *Cheque, err error) { - defer chbook.lock.Unlock() - chbook.lock.Lock() - - if amount.Sign() <= 0 { - return nil, fmt.Errorf("amount must be greater than zero (%v)", amount) - } - if chbook.balance.Cmp(amount) < 0 { - err = fmt.Errorf("insufficient funds to issue cheque for amount: %v. balance: %v", amount, chbook.balance) - } else { - var sig []byte - sent, found := chbook.sent[beneficiary] - if !found { - sent = new(big.Int) - chbook.sent[beneficiary] = sent - } - sum := new(big.Int).Set(sent) - sum.Add(sum, amount) - - sig, err = crypto.Sign(sigHash(chbook.contractAddr, beneficiary, sum), chbook.prvKey) - if err == nil { - ch = &Cheque{ - Contract: chbook.contractAddr, - Beneficiary: beneficiary, - Amount: sum, - Sig: sig, - } - sent.Set(sum) - chbook.balance.Sub(chbook.balance, amount) // subtract amount from balance - } - } - - // auto deposit if threshold is set and balance is less then threshold - // note this is called even if issuing cheque fails - // so we reattempt depositing - if chbook.threshold != nil { - if chbook.balance.Cmp(chbook.threshold) < 0 { - send := new(big.Int).Sub(chbook.buffer, chbook.balance) - chbook.deposit(send) - } - } - - return -} - -// Cash is a convenience method to cash any cheque. -func (chbook *Chequebook) Cash(ch *Cheque) (txhash string, err error) { - return ch.Cash(chbook.session) -} - -// data to sign: contract address, beneficiary, cumulative amount of funds ever sent -func sigHash(contract, beneficiary common.Address, sum *big.Int) []byte { - bigamount := sum.Bytes() - if len(bigamount) > 32 { - return nil - } - var amount32 [32]byte - copy(amount32[32-len(bigamount):32], bigamount) - input := append(contract.Bytes(), beneficiary.Bytes()...) - input = append(input, amount32[:]...) - return crypto.Keccak256(input) -} - -// Balance returns the current balance of the chequebook. -func (chbook *Chequebook) Balance() *big.Int { - defer chbook.lock.Unlock() - chbook.lock.Lock() - return new(big.Int).Set(chbook.balance) -} - -// Owner returns the owner account of the chequebook. -func (chbook *Chequebook) Owner() common.Address { - return chbook.owner -} - -// Address returns the on-chain contract address of the chequebook. -func (chbook *Chequebook) Address() common.Address { - return chbook.contractAddr -} - -// Deposit deposits money to the chequebook account. -func (chbook *Chequebook) Deposit(amount *big.Int) (string, error) { - defer chbook.lock.Unlock() - chbook.lock.Lock() - return chbook.deposit(amount) -} - -// deposit deposits amount to the chequebook account. -// The caller must hold self.lock. -func (chbook *Chequebook) deposit(amount *big.Int) (string, error) { - // since the amount is variable here, we do not use sessions - depositTransactor := bind.NewKeyedTransactor(chbook.prvKey) - depositTransactor.Value = amount - chbookRaw := &contract.ChequebookRaw{Contract: chbook.contract} - tx, err := chbookRaw.Transfer(depositTransactor) - if err != nil { - chbook.log.Warn("Failed to fund chequebook", "amount", amount, "balance", chbook.balance, "target", chbook.buffer, "err", err) - return "", err - } - // assume that transaction is actually successful, we add the amount to balance right away - chbook.balance.Add(chbook.balance, amount) - chbook.log.Trace("Deposited funds to chequebook", "amount", amount, "balance", chbook.balance, "target", chbook.buffer) - return tx.Hash().Hex(), nil -} - -// AutoDeposit (re)sets interval time and amount which triggers sending funds to the -// chequebook. Contract backend needs to be set if threshold is not less than buffer, then -// deposit will be triggered on every new cheque issued. -func (chbook *Chequebook) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) { - defer chbook.lock.Unlock() - chbook.lock.Lock() - chbook.threshold = threshold - chbook.buffer = buffer - chbook.autoDeposit(interval) -} - -// autoDeposit starts a goroutine that periodically sends funds to the chequebook -// contract caller holds the lock the go routine terminates if Chequebook.quit is closed. -func (chbook *Chequebook) autoDeposit(interval time.Duration) { - if chbook.quit != nil { - close(chbook.quit) - chbook.quit = nil - } - // if threshold >= balance autodeposit after every cheque issued - if interval == time.Duration(0) || chbook.threshold != nil && chbook.buffer != nil && chbook.threshold.Cmp(chbook.buffer) >= 0 { - return - } - - ticker := time.NewTicker(interval) - chbook.quit = make(chan bool) - quit := chbook.quit - - go func() { - for { - select { - case <-quit: - return - case <-ticker.C: - chbook.lock.Lock() - if chbook.balance.Cmp(chbook.buffer) < 0 { - amount := new(big.Int).Sub(chbook.buffer, chbook.balance) - txhash, err := chbook.deposit(amount) - if err == nil { - chbook.txhash = txhash - } - } - chbook.lock.Unlock() - } - } - }() -} - -// Outbox can issue cheques from a single contract to a single beneficiary. -type Outbox struct { - chequeBook *Chequebook - beneficiary common.Address -} - -// NewOutbox creates an outbox. -func NewOutbox(chbook *Chequebook, beneficiary common.Address) *Outbox { - return &Outbox{chbook, beneficiary} -} - -// Issue creates cheque. -func (o *Outbox) Issue(amount *big.Int) (swap.Promise, error) { - return o.chequeBook.Issue(o.beneficiary, amount) -} - -// AutoDeposit enables auto-deposits on the underlying chequebook. -func (o *Outbox) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) { - o.chequeBook.AutoDeposit(interval, threshold, buffer) -} - -// Stop helps satisfy the swap.OutPayment interface. -func (o *Outbox) Stop() {} - -// String implements fmt.Stringer. -func (o *Outbox) String() string { - return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", o.chequeBook.Address().Hex(), o.beneficiary.Hex(), o.chequeBook.Balance()) -} - -// Inbox can deposit, verify and cash cheques from a single contract to a single -// beneficiary. It is the incoming payment handler for peer to peer micropayments. -type Inbox struct { - lock sync.Mutex - contract common.Address // peer's chequebook contract - beneficiary common.Address // local peer's receiving address - sender common.Address // local peer's address to send cashing tx from - signer *ecdsa.PublicKey // peer's public key - txhash string // tx hash of last cashing tx - session *contract.ChequebookSession // abi contract backend with tx opts - quit chan bool // when closed causes autocash to stop - maxUncashed *big.Int // threshold that triggers autocashing - cashed *big.Int // cumulative amount cashed - cheque *Cheque // last cheque, nil if none yet received - log log.Logger // contextual logger with the contract address embedded -} - -// NewInbox creates an Inbox. An Inboxes is not persisted, the cumulative sum is updated -// from blockchain when first cheque is received. -func NewInbox(prvKey *ecdsa.PrivateKey, contractAddr, beneficiary common.Address, signer *ecdsa.PublicKey, abigen bind.ContractBackend) (self *Inbox, err error) { - if signer == nil { - return nil, fmt.Errorf("signer is null") - } - chbook, err := contract.NewChequebook(contractAddr, abigen) - if err != nil { - return nil, err - } - transactOpts := bind.NewKeyedTransactor(prvKey) - transactOpts.GasLimit = gasToCash - session := &contract.ChequebookSession{ - Contract: chbook, - TransactOpts: *transactOpts, - } - sender := transactOpts.From - - self = &Inbox{ - contract: contractAddr, - beneficiary: beneficiary, - sender: sender, - signer: signer, - session: session, - cashed: new(big.Int).Set(common.Big0), - log: log.New("contract", contractAddr), - } - self.log.Trace("New chequebook inbox initialized", "beneficiary", self.beneficiary, "signer", hexutil.Bytes(crypto.FromECDSAPub(signer))) - return -} - -func (i *Inbox) String() string { - return fmt.Sprintf("chequebook: %v, beneficiary: %s, balance: %v", i.contract.Hex(), i.beneficiary.Hex(), i.cheque.Amount) -} - -// Stop quits the autocash goroutine. -func (i *Inbox) Stop() { - defer i.lock.Unlock() - i.lock.Lock() - if i.quit != nil { - close(i.quit) - i.quit = nil - } -} - -// Cash attempts to cash the current cheque. -func (i *Inbox) Cash() (txhash string, err error) { - if i.cheque != nil { - txhash, err = i.cheque.Cash(i.session) - i.log.Trace("Cashing in chequebook cheque", "amount", i.cheque.Amount, "beneficiary", i.beneficiary) - i.cashed = i.cheque.Amount - } - return -} - -// AutoCash (re)sets maximum time and amount which triggers cashing of the last uncashed -// cheque if maxUncashed is set to 0, then autocash on receipt. -func (i *Inbox) AutoCash(cashInterval time.Duration, maxUncashed *big.Int) { - defer i.lock.Unlock() - i.lock.Lock() - i.maxUncashed = maxUncashed - i.autoCash(cashInterval) -} - -// autoCash starts a loop that periodically clears the last cheque -// if the peer is trusted. Clearing period could be 24h or a week. -// The caller must hold self.lock. -func (i *Inbox) autoCash(cashInterval time.Duration) { - if i.quit != nil { - close(i.quit) - i.quit = nil - } - // if maxUncashed is set to 0, then autocash on receipt - if cashInterval == time.Duration(0) || i.maxUncashed != nil && i.maxUncashed.Sign() == 0 { - return - } - - ticker := time.NewTicker(cashInterval) - i.quit = make(chan bool) - quit := i.quit - - go func() { - for { - select { - case <-quit: - return - case <-ticker.C: - i.lock.Lock() - if i.cheque != nil && i.cheque.Amount.Cmp(i.cashed) != 0 { - txhash, err := i.Cash() - if err == nil { - i.txhash = txhash - } - } - i.lock.Unlock() - } - } - }() -} - -// Receive is called to deposit the latest cheque to the incoming Inbox. -// The given promise must be a *Cheque. -func (i *Inbox) Receive(promise swap.Promise) (*big.Int, error) { - ch := promise.(*Cheque) - - defer i.lock.Unlock() - i.lock.Lock() - - var sum *big.Int - if i.cheque == nil { - // the sum is checked against the blockchain once a cheque is received - tally, err := i.session.Sent(i.beneficiary) - if err != nil { - return nil, fmt.Errorf("inbox: error calling backend to set amount: %v", err) - } - sum = tally - } else { - sum = i.cheque.Amount - } - - amount, err := ch.Verify(i.signer, i.contract, i.beneficiary, sum) - var uncashed *big.Int - if err == nil { - i.cheque = ch - - if i.maxUncashed != nil { - uncashed = new(big.Int).Sub(ch.Amount, i.cashed) - if i.maxUncashed.Cmp(uncashed) < 0 { - i.Cash() - } - } - i.log.Trace("Received cheque in chequebook inbox", "amount", amount, "uncashed", uncashed) - } - - return amount, err -} - -// Verify verifies cheque for signer, contract, beneficiary, amount, valid signature. -func (ch *Cheque) Verify(signerKey *ecdsa.PublicKey, contract, beneficiary common.Address, sum *big.Int) (*big.Int, error) { - log.Trace("Verifying chequebook cheque", "cheque", ch, "sum", sum) - if sum == nil { - return nil, fmt.Errorf("invalid amount") - } - - if ch.Beneficiary != beneficiary { - return nil, fmt.Errorf("beneficiary mismatch: %v != %v", ch.Beneficiary.Hex(), beneficiary.Hex()) - } - if ch.Contract != contract { - return nil, fmt.Errorf("contract mismatch: %v != %v", ch.Contract.Hex(), contract.Hex()) - } - - amount := new(big.Int).Set(ch.Amount) - if sum != nil { - amount.Sub(amount, sum) - if amount.Sign() <= 0 { - return nil, fmt.Errorf("incorrect amount: %v <= 0", amount) - } - } - - pubKey, err := crypto.SigToPub(sigHash(ch.Contract, beneficiary, ch.Amount), ch.Sig) - if err != nil { - return nil, fmt.Errorf("invalid signature: %v", err) - } - if !bytes.Equal(crypto.FromECDSAPub(pubKey), crypto.FromECDSAPub(signerKey)) { - return nil, fmt.Errorf("signer mismatch: %x != %x", crypto.FromECDSAPub(pubKey), crypto.FromECDSAPub(signerKey)) - } - return amount, nil -} - -// v/r/s representation of signature -func sig2vrs(sig []byte) (v byte, r, s [32]byte) { - v = sig[64] + 27 - copy(r[:], sig[:32]) - copy(s[:], sig[32:64]) - return -} - -// Cash cashes the cheque by sending an Ethereum transaction. -func (ch *Cheque) Cash(session *contract.ChequebookSession) (string, error) { - v, r, s := sig2vrs(ch.Sig) - tx, err := session.Cash(ch.Beneficiary, ch.Amount, v, r, s) - if err != nil { - return "", err - } - return tx.Hash().Hex(), nil -} - -// ValidateCode checks that the on-chain code at address matches the expected chequebook -// contract code. This is used to detect suicided chequebooks. -func ValidateCode(ctx context.Context, b Backend, address common.Address) (ok bool, err error) { - code, err := b.CodeAt(ctx, address, nil) - if err != nil { - return false, err - } - return bytes.Equal(code, common.FromHex(contract.ContractDeployedCode)), nil -} diff --git a/contracts/chequebook/cheque_test.go b/contracts/chequebook/cheque_test.go deleted file mode 100644 index 712ec5920af6..000000000000 --- a/contracts/chequebook/cheque_test.go +++ /dev/null @@ -1,487 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package chequebook - -import ( - "crypto/ecdsa" - "math/big" - "os" - "path/filepath" - "testing" - "time" - - "github.com/ubiq/go-ubiq/accounts/abi/bind" - "github.com/ubiq/go-ubiq/accounts/abi/bind/backends" - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/contracts/chequebook/contract" - "github.com/ubiq/go-ubiq/core" - "github.com/ubiq/go-ubiq/crypto" -) - -var ( - key0, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - key1, _ = crypto.HexToECDSA("8a1f9a8f95be41cd7ccb6168179afb4504aefe388d1e14474d32c45c72ce7b7a") - key2, _ = crypto.HexToECDSA("49a7b37aa6f6645917e7b807e9d1c00d4fa71f18343b0d4122a4d2df64dd6fee") - addr0 = crypto.PubkeyToAddress(key0.PublicKey) - addr1 = crypto.PubkeyToAddress(key1.PublicKey) - addr2 = crypto.PubkeyToAddress(key2.PublicKey) -) - -func newTestBackend() *backends.SimulatedBackend { - return backends.NewSimulatedBackend(core.GenesisAlloc{ - addr0: {Balance: big.NewInt(1000000000)}, - addr1: {Balance: big.NewInt(1000000000)}, - addr2: {Balance: big.NewInt(1000000000)}, - }, 10000000) -} - -func deploy(prvKey *ecdsa.PrivateKey, amount *big.Int, backend *backends.SimulatedBackend) (common.Address, error) { - deployTransactor := bind.NewKeyedTransactor(prvKey) - deployTransactor.Value = amount - addr, _, _, err := contract.DeployChequebook(deployTransactor, backend) - if err != nil { - return common.Address{}, err - } - backend.Commit() - return addr, nil -} - -func TestIssueAndReceive(t *testing.T) { - path := filepath.Join(os.TempDir(), "chequebook-test.json") - backend := newTestBackend() - addr0, err := deploy(key0, big.NewInt(0), backend) - if err != nil { - t.Fatalf("deploy contract: expected no error, got %v", err) - } - chbook, err := NewChequebook(path, addr0, key0, backend) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - chbook.sent[addr1] = new(big.Int).SetUint64(42) - amount := common.Big1 - - if _, err = chbook.Issue(addr1, amount); err == nil { - t.Fatalf("expected insufficient funds error, got none") - } - - chbook.balance = new(big.Int).Set(common.Big1) - if chbook.Balance().Cmp(common.Big1) != 0 { - t.Fatalf("expected: %v, got %v", "0", chbook.Balance()) - } - - ch, err := chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - if chbook.Balance().Sign() != 0 { - t.Errorf("expected: %v, got %v", "0", chbook.Balance()) - } - - chbox, err := NewInbox(key1, addr0, addr1, &key0.PublicKey, backend) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - received, err := chbox.Receive(ch) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - if received.Cmp(big.NewInt(43)) != 0 { - t.Errorf("expected: %v, got %v", "43", received) - } - -} - -func TestCheckbookFile(t *testing.T) { - path := filepath.Join(os.TempDir(), "chequebook-test.json") - backend := newTestBackend() - chbook, err := NewChequebook(path, addr0, key0, backend) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - chbook.sent[addr1] = new(big.Int).SetUint64(42) - chbook.balance = new(big.Int).Set(common.Big1) - - chbook.Save() - - chbook, err = LoadChequebook(path, key0, backend, false) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - if chbook.Balance().Cmp(common.Big1) != 0 { - t.Errorf("expected: %v, got %v", "0", chbook.Balance()) - } - - var ch *Cheque - if ch, err = chbook.Issue(addr1, common.Big1); err != nil { - t.Fatalf("expected no error, got %v", err) - } - if ch.Amount.Cmp(new(big.Int).SetUint64(43)) != 0 { - t.Errorf("expected: %v, got %v", "0", ch.Amount) - } - - err = chbook.Save() - if err != nil { - t.Fatalf("expected no error, got %v", err) - } -} - -func TestVerifyErrors(t *testing.T) { - path0 := filepath.Join(os.TempDir(), "chequebook-test-0.json") - backend := newTestBackend() - contr0, err := deploy(key0, common.Big2, backend) - if err != nil { - t.Errorf("expected no error, got %v", err) - } - chbook0, err := NewChequebook(path0, contr0, key0, backend) - if err != nil { - t.Errorf("expected no error, got %v", err) - } - - path1 := filepath.Join(os.TempDir(), "chequebook-test-1.json") - contr1, _ := deploy(key1, common.Big2, backend) - chbook1, err := NewChequebook(path1, contr1, key1, backend) - if err != nil { - t.Errorf("expected no error, got %v", err) - } - - chbook0.sent[addr1] = new(big.Int).SetUint64(42) - chbook0.balance = new(big.Int).Set(common.Big2) - chbook1.balance = new(big.Int).Set(common.Big1) - amount := common.Big1 - ch0, err := chbook0.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - chbox, err := NewInbox(key1, contr0, addr1, &key0.PublicKey, backend) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - received, err := chbox.Receive(ch0) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - if received.Cmp(big.NewInt(43)) != 0 { - t.Errorf("expected: %v, got %v", "43", received) - } - - ch1, err := chbook0.Issue(addr2, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - received, err = chbox.Receive(ch1) - t.Logf("correct error: %v", err) - if err == nil { - t.Fatalf("expected receiver error, got none and value %v", received) - } - - ch2, err := chbook1.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - received, err = chbox.Receive(ch2) - t.Logf("correct error: %v", err) - if err == nil { - t.Fatalf("expected sender error, got none and value %v", received) - } - - _, err = chbook1.Issue(addr1, new(big.Int).SetInt64(-1)) - t.Logf("correct error: %v", err) - if err == nil { - t.Fatalf("expected incorrect amount error, got none") - } - - received, err = chbox.Receive(ch0) - t.Logf("correct error: %v", err) - if err == nil { - t.Fatalf("expected incorrect amount error, got none and value %v", received) - } - -} - -func TestDeposit(t *testing.T) { - path0 := filepath.Join(os.TempDir(), "chequebook-test-0.json") - backend := newTestBackend() - contr0, _ := deploy(key0, new(big.Int), backend) - - chbook, err := NewChequebook(path0, contr0, key0, backend) - if err != nil { - t.Errorf("expected no error, got %v", err) - } - - balance := new(big.Int).SetUint64(42) - chbook.Deposit(balance) - backend.Commit() - if chbook.Balance().Cmp(balance) != 0 { - t.Fatalf("expected balance %v, got %v", balance, chbook.Balance()) - } - - amount := common.Big1 - _, err = chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - exp := new(big.Int).SetUint64(41) - if chbook.Balance().Cmp(exp) != 0 { - t.Fatalf("expected balance %v, got %v", exp, chbook.Balance()) - } - - // autodeposit on each issue - chbook.AutoDeposit(0, balance, balance) - _, err = chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - _, err = chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - if chbook.Balance().Cmp(balance) != 0 { - t.Fatalf("expected balance %v, got %v", balance, chbook.Balance()) - } - - // autodeposit off - chbook.AutoDeposit(0, common.Big0, balance) - _, err = chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - _, err = chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - - exp = new(big.Int).SetUint64(40) - if chbook.Balance().Cmp(exp) != 0 { - t.Fatalf("expected balance %v, got %v", exp, chbook.Balance()) - } - - // autodeposit every 200ms if new cheque issued - interval := 200 * time.Millisecond - chbook.AutoDeposit(interval, common.Big1, balance) - _, err = chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - _, err = chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - - exp = new(big.Int).SetUint64(38) - if chbook.Balance().Cmp(exp) != 0 { - t.Fatalf("expected balance %v, got %v", exp, chbook.Balance()) - } - - time.Sleep(3 * interval) - backend.Commit() - if chbook.Balance().Cmp(balance) != 0 { - t.Fatalf("expected balance %v, got %v", balance, chbook.Balance()) - } - - exp = new(big.Int).SetUint64(40) - chbook.AutoDeposit(4*interval, exp, balance) - _, err = chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - _, err = chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - time.Sleep(3 * interval) - backend.Commit() - if chbook.Balance().Cmp(exp) != 0 { - t.Fatalf("expected balance %v, got %v", exp, chbook.Balance()) - } - - _, err = chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - time.Sleep(1 * interval) - backend.Commit() - - if chbook.Balance().Cmp(balance) != 0 { - t.Fatalf("expected balance %v, got %v", balance, chbook.Balance()) - } - - chbook.AutoDeposit(1*interval, common.Big0, balance) - chbook.Stop() - - _, err = chbook.Issue(addr1, common.Big1) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - - _, err = chbook.Issue(addr1, common.Big2) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - time.Sleep(1 * interval) - backend.Commit() - - exp = new(big.Int).SetUint64(39) - if chbook.Balance().Cmp(exp) != 0 { - t.Fatalf("expected balance %v, got %v", exp, chbook.Balance()) - } - -} - -func TestCash(t *testing.T) { - path := filepath.Join(os.TempDir(), "chequebook-test.json") - backend := newTestBackend() - contr0, _ := deploy(key0, common.Big2, backend) - - chbook, err := NewChequebook(path, contr0, key0, backend) - if err != nil { - t.Errorf("expected no error, got %v", err) - } - chbook.sent[addr1] = new(big.Int).SetUint64(42) - amount := common.Big1 - chbook.balance = new(big.Int).Set(common.Big1) - ch, err := chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - chbox, err := NewInbox(key1, contr0, addr1, &key0.PublicKey, backend) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - // cashing latest cheque - if _, err = chbox.Receive(ch); err != nil { - t.Fatalf("expected no error, got %v", err) - } - if _, err = ch.Cash(chbook.session); err != nil { - t.Fatal("Cash failed:", err) - } - backend.Commit() - - chbook.balance = new(big.Int).Set(common.Big3) - ch0, err := chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - ch1, err := chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - - interval := 10 * time.Millisecond - // setting autocash with interval of 10ms - chbox.AutoCash(interval, nil) - _, err = chbox.Receive(ch0) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - _, err = chbox.Receive(ch1) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - // after 3x interval time and 2 cheques received, exactly one cashing tx is sent - time.Sleep(4 * interval) - backend.Commit() - - // after stopping autocash no more tx are sent - ch2, err := chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - chbox.Stop() - _, err = chbox.Receive(ch2) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - time.Sleep(2 * interval) - backend.Commit() - - // autocash below 1 - chbook.balance = big.NewInt(2) - chbox.AutoCash(0, common.Big1) - - ch3, err := chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - - ch4, err := chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - - _, err = chbox.Receive(ch3) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - _, err = chbox.Receive(ch4) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - - // autochash on receipt when maxUncashed is 0 - chbook.balance = new(big.Int).Set(common.Big2) - chbox.AutoCash(0, common.Big0) - - ch5, err := chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - - ch6, err := chbook.Issue(addr1, amount) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - - _, err = chbox.Receive(ch5) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - - _, err = chbox.Receive(ch6) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - backend.Commit() - -} diff --git a/contracts/chequebook/contract/chequebook.go b/contracts/chequebook/contract/chequebook.go deleted file mode 100644 index 8654dbf8e9bb..000000000000 --- a/contracts/chequebook/contract/chequebook.go +++ /dev/null @@ -1,367 +0,0 @@ -// Code generated - DO NOT EDIT. -// This file is a generated binding and any manual changes will be lost. - -package contract - -import ( - "math/big" - "strings" - - ethereum "github.com/ubiq/go-ubiq" - "github.com/ubiq/go-ubiq/accounts/abi" - "github.com/ubiq/go-ubiq/accounts/abi/bind" - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/core/types" - "github.com/ubiq/go-ubiq/event" -) - -// ChequebookABI is the input ABI used to generate the binding from. -const ChequebookABI = "[{\"constant\":false,\"inputs\":[],\"name\":\"kill\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"name\":\"\",\"type\":\"address\"}],\"name\":\"sent\",\"outputs\":[{\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"name\":\"beneficiary\",\"type\":\"address\"},{\"name\":\"amount\",\"type\":\"uint256\"},{\"name\":\"sig_v\",\"type\":\"uint8\"},{\"name\":\"sig_r\",\"type\":\"bytes32\"},{\"name\":\"sig_s\",\"type\":\"bytes32\"}],\"name\":\"cash\",\"outputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"payable\":true,\"stateMutability\":\"payable\",\"type\":\"fallback\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"name\":\"deadbeat\",\"type\":\"address\"}],\"name\":\"Overdraft\",\"type\":\"event\"}]" - -// ChequebookBin is the compiled bytecode used for deploying new contracts. -const ChequebookBin = `0x606060405260008054600160a060020a033316600160a060020a03199091161790556102ec806100306000396000f3006060604052600436106100565763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166341c0e1b581146100585780637bf786f81461006b578063fbf788d61461009c575b005b341561006357600080fd5b6100566100ca565b341561007657600080fd5b61008a600160a060020a03600435166100f1565b60405190815260200160405180910390f35b34156100a757600080fd5b610056600160a060020a036004351660243560ff60443516606435608435610103565b60005433600160a060020a03908116911614156100ef57600054600160a060020a0316ff5b565b60016020526000908152604090205481565b600160a060020a0385166000908152600160205260408120548190861161012957600080fd5b3087876040516c01000000000000000000000000600160a060020a03948516810282529290931690910260148301526028820152604801604051809103902091506001828686866040516000815260200160405260006040516020015260405193845260ff90921660208085019190915260408085019290925260608401929092526080909201915160208103908084039060008661646e5a03f115156101cf57600080fd5b505060206040510351600054600160a060020a039081169116146101f257600080fd5b50600160a060020a03808716600090815260016020526040902054860390301631811161026257600160a060020a0387166000818152600160205260409081902088905582156108fc0290839051600060405180830381858888f19350505050151561025d57600080fd5b6102b7565b6000547f2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f97890600160a060020a0316604051600160a060020a03909116815260200160405180910390a186600160a060020a0316ff5b505050505050505600a165627a7a72305820533e856fc37e3d64d1706bcc7dfb6b1d490c8d566ea498d9d01ec08965a896ca0029` - -// DeployChequebook deploys a new Ubiq contract, binding an instance of Chequebook to it. -func DeployChequebook(auth *bind.TransactOpts, backend bind.ContractBackend) (common.Address, *types.Transaction, *Chequebook, error) { - parsed, err := abi.JSON(strings.NewReader(ChequebookABI)) - if err != nil { - return common.Address{}, nil, nil, err - } - address, tx, contract, err := bind.DeployContract(auth, parsed, common.FromHex(ChequebookBin), backend) - if err != nil { - return common.Address{}, nil, nil, err - } - return address, tx, &Chequebook{ChequebookCaller: ChequebookCaller{contract: contract}, ChequebookTransactor: ChequebookTransactor{contract: contract}, ChequebookFilterer: ChequebookFilterer{contract: contract}}, nil -} - -// Chequebook is an auto generated Go binding around an Ubiq contract. -type Chequebook struct { - ChequebookCaller // Read-only binding to the contract - ChequebookTransactor // Write-only binding to the contract - ChequebookFilterer // Log filterer for contract events -} - -// ChequebookCaller is an auto generated read-only Go binding around an Ubiq contract. -type ChequebookCaller struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// ChequebookTransactor is an auto generated write-only Go binding around an Ubiq contract. -type ChequebookTransactor struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// ChequebookFilterer is an auto generated log filtering Go binding around an Ubiq contract events. -type ChequebookFilterer struct { - contract *bind.BoundContract // Generic contract wrapper for the low level calls -} - -// ChequebookSession is an auto generated Go binding around an Ubiq contract, -// with pre-set call and transact options. -type ChequebookSession struct { - Contract *Chequebook // Generic contract binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// ChequebookCallerSession is an auto generated read-only Go binding around an Ubiq contract, -// with pre-set call options. -type ChequebookCallerSession struct { - Contract *ChequebookCaller // Generic contract caller binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session -} - -// ChequebookTransactorSession is an auto generated write-only Go binding around an Ubiq contract, -// with pre-set transact options. -type ChequebookTransactorSession struct { - Contract *ChequebookTransactor // Generic contract transactor binding to set the session for - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session -} - -// ChequebookRaw is an auto generated low-level Go binding around an Ubiq contract. -type ChequebookRaw struct { - Contract *Chequebook // Generic contract binding to access the raw methods on -} - -// ChequebookCallerRaw is an auto generated low-level read-only Go binding around an Ubiq contract. -type ChequebookCallerRaw struct { - Contract *ChequebookCaller // Generic read-only contract binding to access the raw methods on -} - -// ChequebookTransactorRaw is an auto generated low-level write-only Go binding around an Ubiq contract. -type ChequebookTransactorRaw struct { - Contract *ChequebookTransactor // Generic write-only contract binding to access the raw methods on -} - -// NewChequebook creates a new instance of Chequebook, bound to a specific deployed contract. -func NewChequebook(address common.Address, backend bind.ContractBackend) (*Chequebook, error) { - contract, err := bindChequebook(address, backend, backend, backend) - if err != nil { - return nil, err - } - return &Chequebook{ChequebookCaller: ChequebookCaller{contract: contract}, ChequebookTransactor: ChequebookTransactor{contract: contract}, ChequebookFilterer: ChequebookFilterer{contract: contract}}, nil -} - -// NewChequebookCaller creates a new read-only instance of Chequebook, bound to a specific deployed contract. -func NewChequebookCaller(address common.Address, caller bind.ContractCaller) (*ChequebookCaller, error) { - contract, err := bindChequebook(address, caller, nil, nil) - if err != nil { - return nil, err - } - return &ChequebookCaller{contract: contract}, nil -} - -// NewChequebookTransactor creates a new write-only instance of Chequebook, bound to a specific deployed contract. -func NewChequebookTransactor(address common.Address, transactor bind.ContractTransactor) (*ChequebookTransactor, error) { - contract, err := bindChequebook(address, nil, transactor, nil) - if err != nil { - return nil, err - } - return &ChequebookTransactor{contract: contract}, nil -} - -// NewChequebookFilterer creates a new log filterer instance of Chequebook, bound to a specific deployed contract. -func NewChequebookFilterer(address common.Address, filterer bind.ContractFilterer) (*ChequebookFilterer, error) { - contract, err := bindChequebook(address, nil, nil, filterer) - if err != nil { - return nil, err - } - return &ChequebookFilterer{contract: contract}, nil -} - -// bindChequebook binds a generic wrapper to an already deployed contract. -func bindChequebook(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { - parsed, err := abi.JSON(strings.NewReader(ChequebookABI)) - if err != nil { - return nil, err - } - return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_Chequebook *ChequebookRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { - return _Chequebook.Contract.ChequebookCaller.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_Chequebook *ChequebookRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _Chequebook.Contract.ChequebookTransactor.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_Chequebook *ChequebookRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _Chequebook.Contract.ChequebookTransactor.contract.Transact(opts, method, params...) -} - -// Call invokes the (constant) contract method with params as input values and -// sets the output to result. The result type might be a single field for simple -// returns, a slice of interfaces for anonymous returns and a struct for named -// returns. -func (_Chequebook *ChequebookCallerRaw) Call(opts *bind.CallOpts, result interface{}, method string, params ...interface{}) error { - return _Chequebook.Contract.contract.Call(opts, result, method, params...) -} - -// Transfer initiates a plain transaction to move funds to the contract, calling -// its default method if one is available. -func (_Chequebook *ChequebookTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { - return _Chequebook.Contract.contract.Transfer(opts) -} - -// Transact invokes the (paid) contract method with params as input values. -func (_Chequebook *ChequebookTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { - return _Chequebook.Contract.contract.Transact(opts, method, params...) -} - -// Sent is a free data retrieval call binding the contract method 0x7bf786f8. -// -// Solidity: function sent( address) constant returns(uint256) -func (_Chequebook *ChequebookCaller) Sent(opts *bind.CallOpts, arg0 common.Address) (*big.Int, error) { - var ( - ret0 = new(*big.Int) - ) - out := ret0 - err := _Chequebook.contract.Call(opts, out, "sent", arg0) - return *ret0, err -} - -// Sent is a free data retrieval call binding the contract method 0x7bf786f8. -// -// Solidity: function sent( address) constant returns(uint256) -func (_Chequebook *ChequebookSession) Sent(arg0 common.Address) (*big.Int, error) { - return _Chequebook.Contract.Sent(&_Chequebook.CallOpts, arg0) -} - -// Sent is a free data retrieval call binding the contract method 0x7bf786f8. -// -// Solidity: function sent( address) constant returns(uint256) -func (_Chequebook *ChequebookCallerSession) Sent(arg0 common.Address) (*big.Int, error) { - return _Chequebook.Contract.Sent(&_Chequebook.CallOpts, arg0) -} - -// Cash is a paid mutator transaction binding the contract method 0xfbf788d6. -// -// Solidity: function cash(beneficiary address, amount uint256, sig_v uint8, sig_r bytes32, sig_s bytes32) returns() -func (_Chequebook *ChequebookTransactor) Cash(opts *bind.TransactOpts, beneficiary common.Address, amount *big.Int, sigV uint8, sigR [32]byte, sigS [32]byte) (*types.Transaction, error) { - return _Chequebook.contract.Transact(opts, "cash", beneficiary, amount, sigV, sigR, sigS) -} - -// Cash is a paid mutator transaction binding the contract method 0xfbf788d6. -// -// Solidity: function cash(beneficiary address, amount uint256, sig_v uint8, sig_r bytes32, sig_s bytes32) returns() -func (_Chequebook *ChequebookSession) Cash(beneficiary common.Address, amount *big.Int, sigV uint8, sigR [32]byte, sigS [32]byte) (*types.Transaction, error) { - return _Chequebook.Contract.Cash(&_Chequebook.TransactOpts, beneficiary, amount, sigV, sigR, sigS) -} - -// Cash is a paid mutator transaction binding the contract method 0xfbf788d6. -// -// Solidity: function cash(beneficiary address, amount uint256, sig_v uint8, sig_r bytes32, sig_s bytes32) returns() -func (_Chequebook *ChequebookTransactorSession) Cash(beneficiary common.Address, amount *big.Int, sigV uint8, sigR [32]byte, sigS [32]byte) (*types.Transaction, error) { - return _Chequebook.Contract.Cash(&_Chequebook.TransactOpts, beneficiary, amount, sigV, sigR, sigS) -} - -// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. -// -// Solidity: function kill() returns() -func (_Chequebook *ChequebookTransactor) Kill(opts *bind.TransactOpts) (*types.Transaction, error) { - return _Chequebook.contract.Transact(opts, "kill") -} - -// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. -// -// Solidity: function kill() returns() -func (_Chequebook *ChequebookSession) Kill() (*types.Transaction, error) { - return _Chequebook.Contract.Kill(&_Chequebook.TransactOpts) -} - -// Kill is a paid mutator transaction binding the contract method 0x41c0e1b5. -// -// Solidity: function kill() returns() -func (_Chequebook *ChequebookTransactorSession) Kill() (*types.Transaction, error) { - return _Chequebook.Contract.Kill(&_Chequebook.TransactOpts) -} - -// ChequebookOverdraftIterator is returned from FilterOverdraft and is used to iterate over the raw logs and unpacked data for Overdraft events raised by the Chequebook contract. -type ChequebookOverdraftIterator struct { - Event *ChequebookOverdraft // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *ChequebookOverdraftIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(ChequebookOverdraft) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(ChequebookOverdraft) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error retruned any retrieval or parsing error occurred during filtering. -func (it *ChequebookOverdraftIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *ChequebookOverdraftIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// ChequebookOverdraft represents a Overdraft event raised by the Chequebook contract. -type ChequebookOverdraft struct { - Deadbeat common.Address - Raw types.Log // Blockchain specific contextual infos -} - -// FilterOverdraft is a free log retrieval operation binding the contract event 0x2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f978. -// -// Solidity: event Overdraft(deadbeat address) -func (_Chequebook *ChequebookFilterer) FilterOverdraft(opts *bind.FilterOpts) (*ChequebookOverdraftIterator, error) { - - logs, sub, err := _Chequebook.contract.FilterLogs(opts, "Overdraft") - if err != nil { - return nil, err - } - return &ChequebookOverdraftIterator{contract: _Chequebook.contract, event: "Overdraft", logs: logs, sub: sub}, nil -} - -// WatchOverdraft is a free log subscription operation binding the contract event 0x2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f978. -// -// Solidity: event Overdraft(deadbeat address) -func (_Chequebook *ChequebookFilterer) WatchOverdraft(opts *bind.WatchOpts, sink chan<- *ChequebookOverdraft) (event.Subscription, error) { - - logs, sub, err := _Chequebook.contract.WatchLogs(opts, "Overdraft") - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(ChequebookOverdraft) - if err := _Chequebook.contract.UnpackLog(event, "Overdraft", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} diff --git a/contracts/chequebook/contract/chequebook.sol b/contracts/chequebook/contract/chequebook.sol deleted file mode 100644 index c386cceed856..000000000000 --- a/contracts/chequebook/contract/chequebook.sol +++ /dev/null @@ -1,47 +0,0 @@ -pragma solidity ^0.4.18; - -import "./mortal.sol"; - -/// @title Chequebook for Ethereum micropayments -/// @author Daniel A. Nagy -contract chequebook is mortal { - // Cumulative paid amount in wei to each beneficiary - mapping (address => uint256) public sent; - - /// @notice Overdraft event - event Overdraft(address deadbeat); - - // Allow sending ether to the chequebook. - function() public payable { } - - /// @notice Cash cheque - /// - /// @param beneficiary beneficiary address - /// @param amount cumulative amount in wei - /// @param sig_v signature parameter v - /// @param sig_r signature parameter r - /// @param sig_s signature parameter s - /// The digital signature is calculated on the concatenated triplet of contract address, beneficiary address and cumulative amount - function cash(address beneficiary, uint256 amount, uint8 sig_v, bytes32 sig_r, bytes32 sig_s) public { - // Check if the cheque is old. - // Only cheques that are more recent than the last cashed one are considered. - require(amount > sent[beneficiary]); - // Check the digital signature of the cheque. - bytes32 hash = keccak256(address(this), beneficiary, amount); - require(owner == ecrecover(hash, sig_v, sig_r, sig_s)); - // Attempt sending the difference between the cumulative amount on the cheque - // and the cumulative amount on the last cashed cheque to beneficiary. - uint256 diff = amount - sent[beneficiary]; - if (diff <= this.balance) { - // update the cumulative amount before sending - sent[beneficiary] = amount; - beneficiary.transfer(diff); - } else { - // Upon failure, punish owner for writing a bounced cheque. - // owner.sendToDebtorsPrison(); - Overdraft(owner); - // Compensate beneficiary. - selfdestruct(beneficiary); - } - } -} diff --git a/contracts/chequebook/contract/code.go b/contracts/chequebook/contract/code.go deleted file mode 100644 index d837a9d60114..000000000000 --- a/contracts/chequebook/contract/code.go +++ /dev/null @@ -1,5 +0,0 @@ -package contract - -// ContractDeployedCode is used to detect suicides. This constant needs to be -// updated when the contract code is changed. -const ContractDeployedCode = "0x6060604052600436106100565763ffffffff7c010000000000000000000000000000000000000000000000000000000060003504166341c0e1b581146100585780637bf786f81461006b578063fbf788d61461009c575b005b341561006357600080fd5b6100566100ca565b341561007657600080fd5b61008a600160a060020a03600435166100f1565b60405190815260200160405180910390f35b34156100a757600080fd5b610056600160a060020a036004351660243560ff60443516606435608435610103565b60005433600160a060020a03908116911614156100ef57600054600160a060020a0316ff5b565b60016020526000908152604090205481565b600160a060020a0385166000908152600160205260408120548190861161012957600080fd5b3087876040516c01000000000000000000000000600160a060020a03948516810282529290931690910260148301526028820152604801604051809103902091506001828686866040516000815260200160405260006040516020015260405193845260ff90921660208085019190915260408085019290925260608401929092526080909201915160208103908084039060008661646e5a03f115156101cf57600080fd5b505060206040510351600054600160a060020a039081169116146101f257600080fd5b50600160a060020a03808716600090815260016020526040902054860390301631811161026257600160a060020a0387166000818152600160205260409081902088905582156108fc0290839051600060405180830381858888f19350505050151561025d57600080fd5b6102b7565b6000547f2250e2993c15843b32621c89447cc589ee7a9f049c026986e545d3c2c0c6f97890600160a060020a0316604051600160a060020a03909116815260200160405180910390a186600160a060020a0316ff5b505050505050505600a165627a7a72305820533e856fc37e3d64d1706bcc7dfb6b1d490c8d566ea498d9d01ec08965a896ca0029" diff --git a/contracts/chequebook/contract/mortal.sol b/contracts/chequebook/contract/mortal.sol deleted file mode 100644 index c43f1e4f7951..000000000000 --- a/contracts/chequebook/contract/mortal.sol +++ /dev/null @@ -1,10 +0,0 @@ -pragma solidity ^0.4.0; - -import "./owned.sol"; - -contract mortal is owned { - function kill() public { - if (msg.sender == owner) - selfdestruct(owner); - } -} diff --git a/contracts/chequebook/contract/owned.sol b/contracts/chequebook/contract/owned.sol deleted file mode 100644 index ee9860d343af..000000000000 --- a/contracts/chequebook/contract/owned.sol +++ /dev/null @@ -1,15 +0,0 @@ -pragma solidity ^0.4.0; - -contract owned { - address owner; - - modifier onlyowner() { - if (msg.sender == owner) { - _; - } - } - - function owned() public { - owner = msg.sender; - } -} diff --git a/contracts/chequebook/gencode.go b/contracts/chequebook/gencode.go deleted file mode 100644 index 60c90a62d47b..000000000000 --- a/contracts/chequebook/gencode.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build none - -// This program generates contract/code.go, which contains the chequebook code -// after deployment. -package main - -import ( - "fmt" - "io/ioutil" - "math/big" - - "github.com/ubiq/go-ubiq/accounts/abi/bind" - "github.com/ubiq/go-ubiq/accounts/abi/bind/backends" - "github.com/ubiq/go-ubiq/contracts/chequebook/contract" - "github.com/ubiq/go-ubiq/core" - "github.com/ubiq/go-ubiq/crypto" -) - -var ( - testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") - testAlloc = core.GenesisAlloc{ - crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(500000000000)}, - } -) - -func main() { - backend := backends.NewSimulatedBackend(testAlloc, uint64(100000000)) - auth := bind.NewKeyedTransactor(testKey) - - // Deploy the contract, get the code. - addr, _, _, err := contract.DeployChequebook(auth, backend) - if err != nil { - panic(err) - } - backend.Commit() - code, err := backend.CodeAt(nil, addr, nil) - if err != nil { - panic(err) - } - if len(code) == 0 { - panic("empty code") - } - - // Write the output file. - content := fmt.Sprintf(`package contract - -// ContractDeployedCode is used to detect suicides. This constant needs to be -// updated when the contract code is changed. -const ContractDeployedCode = "%#x" -`, code) - if err := ioutil.WriteFile("contract/code.go", []byte(content), 0644); err != nil { - panic(err) - } -} diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index aa76d00694ef..cce76262ec7d 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -20,7 +20,6 @@ package web3ext var Modules = map[string]string{ "accounting": Accounting_JS, "admin": Admin_JS, - "chequebook": Chequebook_JS, "clique": Clique_JS, "ubqhash": Ubqhash_JS, "debug": Debug_JS, @@ -30,41 +29,9 @@ var Modules = map[string]string{ "personal": Personal_JS, "rpc": RPC_JS, "shh": Shh_JS, - "swarmfs": SWARMFS_JS, "txpool": TxPool_JS, } -const Chequebook_JS = ` -web3._extend({ - property: 'chequebook', - methods: [ - new web3._extend.Method({ - name: 'deposit', - call: 'chequebook_deposit', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Property({ - name: 'balance', - getter: 'chequebook_balance', - outputFormatter: web3._extend.utils.toDecimal - }), - new web3._extend.Method({ - name: 'cash', - call: 'chequebook_cash', - params: 1, - inputFormatter: [null] - }), - new web3._extend.Method({ - name: 'issue', - call: 'chequebook_issue', - params: 2, - inputFormatter: [null, null] - }), - ] -}); -` - const Clique_JS = ` web3._extend({ property: 'clique', @@ -661,30 +628,6 @@ web3._extend({ }); ` -const SWARMFS_JS = ` -web3._extend({ - property: 'swarmfs', - methods: - [ - new web3._extend.Method({ - name: 'mount', - call: 'swarmfs_mount', - params: 2 - }), - new web3._extend.Method({ - name: 'unmount', - call: 'swarmfs_unmount', - params: 1 - }), - new web3._extend.Method({ - name: 'listmounts', - call: 'swarmfs_listmounts', - params: 0 - }), - ] -}); -` - const TxPool_JS = ` web3._extend({ property: 'txpool', diff --git a/p2p/protocols/protocol.go b/p2p/protocols/protocol.go index bb0f1cffe04b..10f8afc9c669 100644 --- a/p2p/protocols/protocol.go +++ b/p2p/protocols/protocol.go @@ -38,13 +38,13 @@ import ( "sync" "time" + opentracing "github.com/opentracing/opentracing-go" "github.com/ubiq/go-ubiq/log" "github.com/ubiq/go-ubiq/metrics" "github.com/ubiq/go-ubiq/p2p" + "github.com/ubiq/go-ubiq/p2p/spancontext" + "github.com/ubiq/go-ubiq/p2p/tracing" "github.com/ubiq/go-ubiq/rlp" - "github.com/ubiq/go-ubiq/swarm/spancontext" - "github.com/ubiq/go-ubiq/swarm/tracing" - opentracing "github.com/opentracing/opentracing-go" ) // error codes used by this protocol scheme diff --git a/swarm/spancontext/spancontext.go b/p2p/spancontext/spancontext.go similarity index 100% rename from swarm/spancontext/spancontext.go rename to p2p/spancontext/spancontext.go diff --git a/swarm/tracing/tracing.go b/p2p/tracing/tracing.go similarity index 100% rename from swarm/tracing/tracing.go rename to p2p/tracing/tracing.go index 6eb8e4a9db31..12d6033139ad 100644 --- a/swarm/tracing/tracing.go +++ b/p2p/tracing/tracing.go @@ -6,9 +6,9 @@ import ( "strings" "time" - "github.com/ubiq/go-ubiq/log" jaeger "github.com/uber/jaeger-client-go" jaegercfg "github.com/uber/jaeger-client-go/config" + "github.com/ubiq/go-ubiq/log" cli "gopkg.in/urfave/cli.v1" ) diff --git a/swarm/AUTHORS b/swarm/AUTHORS deleted file mode 100644 index f7232f07ce74..000000000000 --- a/swarm/AUTHORS +++ /dev/null @@ -1,35 +0,0 @@ -# Core team members - -Viktor Trón - @zelig -Louis Holbrook - @nolash -Lewis Marshall - @lmars -Anton Evangelatov - @nonsense -Janoš Guljaš - @janos -Balint Gabor - @gbalint -Elad Nachmias - @justelad -Daniel A. Nagy - @nagydani -Aron Fischer - @homotopycolimit -Fabio Barone - @holisticode -Zahoor Mohamed - @jmozah -Zsolt Felföldi - @zsfelfoldi - -# External contributors - -Kiel Barry -Gary Rong -Jared Wasinger -Leon Stanko -Javier Peletier [epiclabs.io] -Bartek Borkowski [tungsten-labs.com] -Shane Howley [mainframe.com] -Doug Leonard [mainframe.com] -Ivan Daniluk [status.im] -Felix Lange [EF] -Martin Holst Swende [EF] -Guillaume Ballet [EF] -ligi [EF] -Christopher Dro [blick-labs.com] -Sergii Bomko [ledgerleopard.com] -Domino Valdano -Rafael Matias -Coogan Brennan \ No newline at end of file diff --git a/swarm/OWNERS b/swarm/OWNERS deleted file mode 100644 index 4b9ca96ebe62..000000000000 --- a/swarm/OWNERS +++ /dev/null @@ -1,25 +0,0 @@ -# Ownership by go packages - -swarm -├── api ─────────────────── ethersphere -├── bmt ─────────────────── @zelig -├── dev ─────────────────── @lmars -├── fuse ────────────────── @jmozah, @holisticode -├── grafana_dashboards ──── @nonsense -├── metrics ─────────────── @nonsense, @holisticode -├── network ─────────────── ethersphere -│ ├── bitvector ───────── @zelig, @janos, @gbalint -│ ├── priorityqueue ───── @zelig, @janos, @gbalint -│ ├── simulations ─────── @zelig -│ └── stream ──────────── @janos, @zelig, @gbalint, @holisticode, @justelad -│ ├── intervals ───── @janos -│ └── testing ─────── @zelig -├── pot ─────────────────── @zelig -├── pss ─────────────────── @nolash, @zelig, @nonsense -├── services ────────────── @zelig -├── state ───────────────── @justelad -├── storage ─────────────── ethersphere -│ ├── encryption ──────── @gbalint, @zelig, @nagydani -│ ├── mock ────────────── @janos -│ └── feed ────────────── @nolash, @jpeletier -└── testutil ────────────── @lmars \ No newline at end of file diff --git a/swarm/README.md b/swarm/README.md deleted file mode 100644 index 37d810ef742d..000000000000 --- a/swarm/README.md +++ /dev/null @@ -1,244 +0,0 @@ -## Swarm - -[https://swarm.ubiq.org](https://swarm.ubiq.org) - -Swarm is a distributed storage platform and content distribution service, a native base layer service of the ethereum web3 stack. The primary objective of Swarm is to provide a decentralized and redundant store for dapp code and data as well as block chain and state data. Swarm is also set out to provide various base layer services for web3, including node-to-node messaging, media streaming, decentralised database services and scalable state-channel infrastructure for decentralised service economies. - -[![Travis](https://travis-ci.org/ubiq/go-ubiq.svg?branch=master)](https://travis-ci.org/ubiq/go-ubiq) -[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ethersphere/orange-lounge?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) - -## Table of Contents - -* [Building the source](#building-the-source) -* [Running Swarm](#running-swarm) -* [Documentation](#documentation) -* [Developers Guide](#developers-guide) - * [Go Environment](#development-environment) - * [Vendored Dependencies](#vendored-dependencies) - * [Testing](#testing) - * [Profiling Swarm](#profiling-swarm) - * [Metrics and Instrumentation in Swarm](#metrics-and-instrumentation-in-swarm) -* [Public Gateways](#public-gateways) -* [Swarm Dapps](#swarm-dapps) -* [Contributing](#contributing) -* [License](#license) - -## Building the source - -Building Swarm requires Go (version 1.10 or later). - - go get -d github.com/ubiq/go-ubiq - - go install github.com/ubiq/go-ubiq/cmd/swarm - -## Running Swarm - -Going through all the possible command line flags is out of scope here, but we've enumerated a few common parameter combos to get you up to speed quickly on how you can run your own Swarm node. - -To run Swarm you need an Ethereum account. You can create a new account by running the following command: - - gubiq account new - -You will be prompted for a password: - - Your new account is locked with a password. Please give a password. Do not forget this password. - Passphrase: - Repeat passphrase: - -Once you have specified the password, the output will be the Ethereum address representing that account. For example: - - Address: {2f1cd699b0bf461dcfbf0098ad8f5587b038f0f1} - -Using this account, connect to Swarm with - - swarm --bzzaccount - - # in our example - - swarm --bzzaccount 2f1cd699b0bf461dcfbf0098ad8f5587b038f0f1 - - -### Verifying that your local Swarm node is running - -When running, Swarm is accessible through an HTTP API on port 8500. - -Confirm that it is up and running by pointing your browser to http://localhost:8500 - -### Ethereum Name Service resolution - -The Ethereum Name Service is the Ethereum equivalent of DNS in the classic web. In order to use ENS to resolve names to Swarm content hashes (e.g. `bzz://theswarm.eth`), `swarm` has to connect to a `gubiq` instance, which is synced with the Ethereum mainnet. This is done using the `--ens-api` flag. - - swarm --bzzaccount \ - --ens-api '$HOME/.ubiq/gubiq.ipc' - - # in our example - - swarm --bzzaccount 2f1cd699b0bf461dcfbf0098ad8f5587b038f0f1 \ - --ens-api '$HOME/.ubiq/gubiq.ipc' - -For more information on usage, features or command line flags, please consult the Documentation. - - -## Documentation - -Swarm documentation can be found at [https://swarm-guide.readthedocs.io](https://swarm-guide.readthedocs.io). - - -## Developers Guide - -### Go Environment - -We assume that you have Go v1.10 installed, and `GOPATH` is set. - -You must have your working copy under `$GOPATH/src/github.com/ubiq/go-ubiq`. - -Most likely you will be working from your fork of `go-ethereum`, let's say from `github.com/nirname/go-ethereum`. Clone or move your fork into the right place: - -``` -git clone git@github.com:nirname/go-ethereum.git $GOPATH/src/github.com/ubiq/go-ubiq -``` - - -### Vendored Dependencies - -All dependencies are tracked in the `vendor` directory. We use `govendor` to manage them. - -If you want to add a new dependency, run `govendor fetch `, then commit the result. - -If you want to update all dependencies to their latest upstream version, run `govendor fetch +v`. - - -### Testing - -This section explains how to run unit, integration, and end-to-end tests in your development sandbox. - -Testing one library: - -``` -go test -v -cpu 4 ./swarm/api -``` - -Note: Using options -cpu (number of cores allowed) and -v (logging even if no error) is recommended. - -Testing only some methods: - -``` -go test -v -cpu 4 ./eth -run TestMethod -``` - -Note: here all tests with prefix TestMethod will be run, so if you got TestMethod, TestMethod1, then both! - -Running benchmarks: - -``` -go test -v -cpu 4 -bench . -run BenchmarkJoin -``` - - -### Profiling Swarm - -This section explains how to add Go `pprof` profiler to Swarm - -If `swarm` is started with the `--pprof` option, a debugging HTTP server is made available on port 6060. - -You can bring up http://localhost:6060/debug/pprof to see the heap, running routines etc. - -By clicking full goroutine stack dump (clicking http://localhost:6060/debug/pprof/goroutine?debug=2) you can generate trace that is useful for debugging. - - -### Metrics and Instrumentation in Swarm - -This section explains how to visualize and use existing Swarm metrics and how to instrument Swarm with a new metric. - -Swarm metrics system is based on the `go-metrics` library. - -The most common types of measurements we use in Swarm are `counters` and `resetting timers`. Consult the `go-metrics` documentation for full reference of available types. - -``` -# incrementing a counter -metrics.GetOrRegisterCounter("network.stream.received_chunks", nil).Inc(1) - -# measuring latency with a resetting timer -start := time.Now() -t := metrics.GetOrRegisterResettingTimer("http.request.GET.time"), nil) -... -t := UpdateSince(start) -``` - -#### Visualizing metrics - -Swarm supports an InfluxDB exporter. Consult the help section to learn about the command line arguments used to configure it: - -``` -swarm --help | grep metrics -``` - -We use Grafana and InfluxDB to visualise metrics reported by Swarm. We keep our Grafana dashboards under version control at `./swarm/grafana_dashboards`. You could use them or design your own. - -We have built a tool to help with automatic start of Grafana and InfluxDB and provisioning of dashboards at https://github.com/nonsense/stateth , which requires that you have Docker installed. - -Once you have `stateth` installed, and you have Docker running locally, you have to: - -1. Run `stateth` and keep it running in the background -``` -stateth --rm --grafana-dashboards-folder $GOPATH/src/github.com/ubiq/go-ubiq/swarm/grafana_dashboards --influxdb-database metrics -``` - -2. Run `swarm` with at least the following params: -``` ---metrics \ ---metrics.influxdb.export \ ---metrics.influxdb.endpoint "http://localhost:8086" \ ---metrics.influxdb.username "admin" \ ---metrics.influxdb.password "admin" \ ---metrics.influxdb.database "metrics" -``` - -3. Open Grafana at http://localhost:3000 and view the dashboards to gain insight into Swarm. - - -## Public Gateways - -Swarm offers a local HTTP proxy API that Dapps can use to interact with Swarm. The Ethereum Foundation is hosting a public gateway, which allows free access so that people can try Swarm without running their own node. - -The Swarm public gateways are temporary and users should not rely on their existence for production services. - -The Swarm public gateway can be found at https://swarm-gateways.net and is always running the latest `stable` Swarm release. - -## Swarm Dapps - -You can find a few reference Swarm decentralised applications at: https://swarm-gateways.net/bzz:/swarmapps.eth - -Their source code can be found at: https://github.com/ethersphere/swarm-dapps - -## Contributing - -Thank you for considering to help out with the source code! We welcome contributions from -anyone on the internet, and are grateful for even the smallest of fixes! - -If you'd like to contribute to Swarm, please fork, fix, commit and send a pull request -for the maintainers to review and merge into the main code base. If you wish to submit more -complex changes though, please check up with the core devs first on [our Swarm gitter channel](https://gitter.im/ethersphere/orange-lounge) -to ensure those changes are in line with the general philosophy of the project and/or get some -early feedback which can make both your efforts much lighter as well as our review and merge -procedures quick and simple. - -Please make sure your contributions adhere to our coding guidelines: - - * Code must adhere to the official Go [formatting](https://golang.org/doc/effective_go.html#formatting) guidelines (i.e. uses [gofmt](https://golang.org/cmd/gofmt/)). - * Code must be documented adhering to the official Go [commentary](https://golang.org/doc/effective_go.html#commentary) guidelines. - * Pull requests need to be based on and opened against the `master` branch. - * [Code review guidelines](https://github.com/ubiq/go-ubiq/wiki/Code-Review-Guidelines). - * Commit messages should be prefixed with the package(s) they modify. - * E.g. "swarm/fuse: ignore default manifest entry" - - -## License - -The go-ethereum library (i.e. all code outside of the `cmd` directory) is licensed under the -[GNU Lesser General Public License v3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html), also -included in our repository in the `COPYING.LESSER` file. - -The go-ethereum binaries (i.e. all code inside of the `cmd` directory) is licensed under the -[GNU General Public License v3.0](https://www.gnu.org/licenses/gpl-3.0.en.html), also included -in our repository in the `COPYING` file. diff --git a/swarm/api/act.go b/swarm/api/act.go deleted file mode 100644 index 0c76417cf8b4..000000000000 --- a/swarm/api/act.go +++ /dev/null @@ -1,538 +0,0 @@ -package api - -import ( - "context" - "crypto/ecdsa" - "crypto/rand" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "io" - "strings" - "time" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/crypto/ecies" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/sctx" - "github.com/ubiq/go-ubiq/swarm/storage" - "golang.org/x/crypto/scrypt" - "golang.org/x/crypto/sha3" - cli "gopkg.in/urfave/cli.v1" -) - -var ( - ErrDecrypt = errors.New("cant decrypt - forbidden") - ErrUnknownAccessType = errors.New("unknown access type (or not implemented)") - ErrDecryptDomainForbidden = errors.New("decryption request domain forbidden - can only decrypt on localhost") - AllowedDecryptDomains = []string{ - "localhost", - "127.0.0.1", - } -) - -const EMPTY_CREDENTIALS = "" - -type AccessEntry struct { - Type AccessType - Publisher string - Salt []byte - Act string - KdfParams *KdfParams -} - -type DecryptFunc func(*ManifestEntry) error - -func (a *AccessEntry) MarshalJSON() (out []byte, err error) { - - return json.Marshal(struct { - Type AccessType `json:"type,omitempty"` - Publisher string `json:"publisher,omitempty"` - Salt string `json:"salt,omitempty"` - Act string `json:"act,omitempty"` - KdfParams *KdfParams `json:"kdf_params,omitempty"` - }{ - Type: a.Type, - Publisher: a.Publisher, - Salt: hex.EncodeToString(a.Salt), - Act: a.Act, - KdfParams: a.KdfParams, - }) - -} - -func (a *AccessEntry) UnmarshalJSON(value []byte) error { - v := struct { - Type AccessType `json:"type,omitempty"` - Publisher string `json:"publisher,omitempty"` - Salt string `json:"salt,omitempty"` - Act string `json:"act,omitempty"` - KdfParams *KdfParams `json:"kdf_params,omitempty"` - }{} - - err := json.Unmarshal(value, &v) - if err != nil { - return err - } - a.Act = v.Act - a.KdfParams = v.KdfParams - a.Publisher = v.Publisher - a.Salt, err = hex.DecodeString(v.Salt) - if err != nil { - return err - } - if len(a.Salt) != 32 { - return errors.New("salt should be 32 bytes long") - } - a.Type = v.Type - return nil -} - -type KdfParams struct { - N int `json:"n"` - P int `json:"p"` - R int `json:"r"` -} - -type AccessType string - -const AccessTypePass = AccessType("pass") -const AccessTypePK = AccessType("pk") -const AccessTypeACT = AccessType("act") - -// NewAccessEntryPassword creates a manifest AccessEntry in order to create an ACT protected by a password -func NewAccessEntryPassword(salt []byte, kdfParams *KdfParams) (*AccessEntry, error) { - if len(salt) != 32 { - return nil, fmt.Errorf("salt should be 32 bytes long") - } - return &AccessEntry{ - Type: AccessTypePass, - Salt: salt, - KdfParams: kdfParams, - }, nil -} - -// NewAccessEntryPK creates a manifest AccessEntry in order to create an ACT protected by a pair of Elliptic Curve keys -func NewAccessEntryPK(publisher string, salt []byte) (*AccessEntry, error) { - if len(publisher) != 66 { - return nil, fmt.Errorf("publisher should be 66 characters long, got %d", len(publisher)) - } - if len(salt) != 32 { - return nil, fmt.Errorf("salt should be 32 bytes long") - } - return &AccessEntry{ - Type: AccessTypePK, - Publisher: publisher, - Salt: salt, - }, nil -} - -// NewAccessEntryACT creates a manifest AccessEntry in order to create an ACT protected by a combination of EC keys and passwords -func NewAccessEntryACT(publisher string, salt []byte, act string) (*AccessEntry, error) { - if len(salt) != 32 { - return nil, fmt.Errorf("salt should be 32 bytes long") - } - if len(publisher) != 66 { - return nil, fmt.Errorf("publisher should be 66 characters long") - } - - return &AccessEntry{ - Type: AccessTypeACT, - Publisher: publisher, - Salt: salt, - Act: act, - KdfParams: DefaultKdfParams, - }, nil -} - -// NOOPDecrypt is a generic decrypt function that is passed into the API in places where real ACT decryption capabilities are -// either unwanted, or alternatively, cannot be implemented in the immediate scope -func NOOPDecrypt(*ManifestEntry) error { - return nil -} - -var DefaultKdfParams = NewKdfParams(262144, 1, 8) - -// NewKdfParams returns a KdfParams struct with the given scrypt params -func NewKdfParams(n, p, r int) *KdfParams { - - return &KdfParams{ - N: n, - P: p, - R: r, - } -} - -// NewSessionKeyPassword creates a session key based on a shared secret (password) and the given salt -// and kdf parameters in the access entry -func NewSessionKeyPassword(password string, accessEntry *AccessEntry) ([]byte, error) { - if accessEntry.Type != AccessTypePass && accessEntry.Type != AccessTypeACT { - return nil, errors.New("incorrect access entry type") - - } - return sessionKeyPassword(password, accessEntry.Salt, accessEntry.KdfParams) -} - -func sessionKeyPassword(password string, salt []byte, kdfParams *KdfParams) ([]byte, error) { - return scrypt.Key( - []byte(password), - salt, - kdfParams.N, - kdfParams.R, - kdfParams.P, - 32, - ) -} - -// NewSessionKeyPK creates a new ACT Session Key using an ECDH shared secret for the given key pair and the given salt value -func NewSessionKeyPK(private *ecdsa.PrivateKey, public *ecdsa.PublicKey, salt []byte) ([]byte, error) { - granteePubEcies := ecies.ImportECDSAPublic(public) - privateKey := ecies.ImportECDSA(private) - - bytes, err := privateKey.GenerateShared(granteePubEcies, 16, 16) - if err != nil { - return nil, err - } - bytes = append(salt, bytes...) - sessionKey := crypto.Keccak256(bytes) - return sessionKey, nil -} - -func (a *API) doDecrypt(ctx context.Context, credentials string, pk *ecdsa.PrivateKey) DecryptFunc { - return func(m *ManifestEntry) error { - if m.Access == nil { - return nil - } - - allowed := false - requestDomain := sctx.GetHost(ctx) - for _, v := range AllowedDecryptDomains { - if strings.Contains(requestDomain, v) { - allowed = true - } - } - - if !allowed { - return ErrDecryptDomainForbidden - } - - switch m.Access.Type { - case "pass": - if credentials != "" { - key, err := NewSessionKeyPassword(credentials, m.Access) - if err != nil { - return err - } - - ref, err := hex.DecodeString(m.Hash) - if err != nil { - return err - } - - enc := NewRefEncryption(len(ref) - 8) - decodedRef, err := enc.Decrypt(ref, key) - if err != nil { - return ErrDecrypt - } - - m.Hash = hex.EncodeToString(decodedRef) - m.Access = nil - return nil - } - return ErrDecrypt - case "pk": - publisherBytes, err := hex.DecodeString(m.Access.Publisher) - if err != nil { - return ErrDecrypt - } - publisher, err := crypto.DecompressPubkey(publisherBytes) - if err != nil { - return ErrDecrypt - } - key, err := NewSessionKeyPK(pk, publisher, m.Access.Salt) - if err != nil { - return ErrDecrypt - } - ref, err := hex.DecodeString(m.Hash) - if err != nil { - return err - } - - enc := NewRefEncryption(len(ref) - 8) - decodedRef, err := enc.Decrypt(ref, key) - if err != nil { - return ErrDecrypt - } - - m.Hash = hex.EncodeToString(decodedRef) - m.Access = nil - return nil - case "act": - var ( - sessionKey []byte - err error - ) - - publisherBytes, err := hex.DecodeString(m.Access.Publisher) - if err != nil { - return ErrDecrypt - } - publisher, err := crypto.DecompressPubkey(publisherBytes) - if err != nil { - return ErrDecrypt - } - - sessionKey, err = NewSessionKeyPK(pk, publisher, m.Access.Salt) - if err != nil { - return ErrDecrypt - } - - found, ciphertext, decryptionKey, err := a.getACTDecryptionKey(ctx, storage.Address(common.Hex2Bytes(m.Access.Act)), sessionKey) - if err != nil { - return err - } - if !found { - // try to fall back to password - if credentials != "" { - sessionKey, err = NewSessionKeyPassword(credentials, m.Access) - if err != nil { - return err - } - found, ciphertext, decryptionKey, err = a.getACTDecryptionKey(ctx, storage.Address(common.Hex2Bytes(m.Access.Act)), sessionKey) - if err != nil { - return err - } - if !found { - return ErrDecrypt - } - } else { - return ErrDecrypt - } - } - enc := NewRefEncryption(len(ciphertext) - 8) - decodedRef, err := enc.Decrypt(ciphertext, decryptionKey) - if err != nil { - return ErrDecrypt - } - - ref, err := hex.DecodeString(m.Hash) - if err != nil { - return err - } - - enc = NewRefEncryption(len(ref) - 8) - decodedMainRef, err := enc.Decrypt(ref, decodedRef) - if err != nil { - return ErrDecrypt - } - m.Hash = hex.EncodeToString(decodedMainRef) - m.Access = nil - return nil - } - return ErrUnknownAccessType - } -} - -func (a *API) getACTDecryptionKey(ctx context.Context, actManifestAddress storage.Address, sessionKey []byte) (found bool, ciphertext, decryptionKey []byte, err error) { - hasher := sha3.NewLegacyKeccak256() - hasher.Write(append(sessionKey, 0)) - lookupKey := hasher.Sum(nil) - hasher.Reset() - - hasher.Write(append(sessionKey, 1)) - accessKeyDecryptionKey := hasher.Sum(nil) - hasher.Reset() - - lk := hex.EncodeToString(lookupKey) - list, err := a.GetManifestList(ctx, NOOPDecrypt, actManifestAddress, lk) - if err != nil { - return false, nil, nil, err - } - for _, v := range list.Entries { - if v.Path == lk { - cipherTextBytes, err := hex.DecodeString(v.Hash) - if err != nil { - return false, nil, nil, err - } - return true, cipherTextBytes, accessKeyDecryptionKey, nil - } - } - return false, nil, nil, nil -} - -func GenerateAccessControlManifest(ctx *cli.Context, ref string, accessKey []byte, ae *AccessEntry) (*Manifest, error) { - refBytes, err := hex.DecodeString(ref) - if err != nil { - return nil, err - } - // encrypt ref with accessKey - enc := NewRefEncryption(len(refBytes)) - encrypted, err := enc.Encrypt(refBytes, accessKey) - if err != nil { - return nil, err - } - - m := &Manifest{ - Entries: []ManifestEntry{ - { - Hash: hex.EncodeToString(encrypted), - ContentType: ManifestType, - ModTime: time.Now(), - Access: ae, - }, - }, - } - - return m, nil -} - -// DoPK is a helper function to the CLI API that handles the entire business logic for -// creating a session key and access entry given the cli context, ec keys and salt -func DoPK(ctx *cli.Context, privateKey *ecdsa.PrivateKey, granteePublicKey string, salt []byte) (sessionKey []byte, ae *AccessEntry, err error) { - if granteePublicKey == "" { - return nil, nil, errors.New("need a grantee Public Key") - } - b, err := hex.DecodeString(granteePublicKey) - if err != nil { - log.Error("error decoding grantee public key", "err", err) - return nil, nil, err - } - - granteePub, err := crypto.DecompressPubkey(b) - if err != nil { - log.Error("error decompressing grantee public key", "err", err) - return nil, nil, err - } - - sessionKey, err = NewSessionKeyPK(privateKey, granteePub, salt) - if err != nil { - log.Error("error getting session key", "err", err) - return nil, nil, err - } - - ae, err = NewAccessEntryPK(hex.EncodeToString(crypto.CompressPubkey(&privateKey.PublicKey)), salt) - if err != nil { - log.Error("error generating access entry", "err", err) - return nil, nil, err - } - - return sessionKey, ae, nil -} - -// DoACT is a helper function to the CLI API that handles the entire business logic for -// creating a access key, access entry and ACT manifest (including uploading it) given the cli context, ec keys, password grantees and salt -func DoACT(ctx *cli.Context, privateKey *ecdsa.PrivateKey, salt []byte, grantees []string, encryptPasswords []string) (accessKey []byte, ae *AccessEntry, actManifest *Manifest, err error) { - if len(grantees) == 0 && len(encryptPasswords) == 0 { - return nil, nil, nil, errors.New("did not get any grantee public keys or any encryption passwords") - } - - publisherPub := hex.EncodeToString(crypto.CompressPubkey(&privateKey.PublicKey)) - grantees = append(grantees, publisherPub) - - accessKey = make([]byte, 32) - if _, err := io.ReadFull(rand.Reader, salt); err != nil { - panic("reading from crypto/rand failed: " + err.Error()) - } - if _, err := io.ReadFull(rand.Reader, accessKey); err != nil { - panic("reading from crypto/rand failed: " + err.Error()) - } - - lookupPathEncryptedAccessKeyMap := make(map[string]string) - i := 0 - for _, v := range grantees { - i++ - if v == "" { - return nil, nil, nil, errors.New("need a grantee Public Key") - } - b, err := hex.DecodeString(v) - if err != nil { - log.Error("error decoding grantee public key", "err", err) - return nil, nil, nil, err - } - - granteePub, err := crypto.DecompressPubkey(b) - if err != nil { - log.Error("error decompressing grantee public key", "err", err) - return nil, nil, nil, err - } - sessionKey, err := NewSessionKeyPK(privateKey, granteePub, salt) - if err != nil { - return nil, nil, nil, err - } - - hasher := sha3.NewLegacyKeccak256() - hasher.Write(append(sessionKey, 0)) - lookupKey := hasher.Sum(nil) - - hasher.Reset() - hasher.Write(append(sessionKey, 1)) - - accessKeyEncryptionKey := hasher.Sum(nil) - - enc := NewRefEncryption(len(accessKey)) - encryptedAccessKey, err := enc.Encrypt(accessKey, accessKeyEncryptionKey) - if err != nil { - return nil, nil, nil, err - } - lookupPathEncryptedAccessKeyMap[hex.EncodeToString(lookupKey)] = hex.EncodeToString(encryptedAccessKey) - } - - for _, pass := range encryptPasswords { - sessionKey, err := sessionKeyPassword(pass, salt, DefaultKdfParams) - if err != nil { - return nil, nil, nil, err - } - hasher := sha3.NewLegacyKeccak256() - hasher.Write(append(sessionKey, 0)) - lookupKey := hasher.Sum(nil) - - hasher.Reset() - hasher.Write(append(sessionKey, 1)) - - accessKeyEncryptionKey := hasher.Sum(nil) - - enc := NewRefEncryption(len(accessKey)) - encryptedAccessKey, err := enc.Encrypt(accessKey, accessKeyEncryptionKey) - if err != nil { - return nil, nil, nil, err - } - lookupPathEncryptedAccessKeyMap[hex.EncodeToString(lookupKey)] = hex.EncodeToString(encryptedAccessKey) - } - - m := &Manifest{ - Entries: []ManifestEntry{}, - } - - for k, v := range lookupPathEncryptedAccessKeyMap { - m.Entries = append(m.Entries, ManifestEntry{ - Path: k, - Hash: v, - ContentType: "text/plain", - }) - } - - ae, err = NewAccessEntryACT(hex.EncodeToString(crypto.CompressPubkey(&privateKey.PublicKey)), salt, "") - if err != nil { - return nil, nil, nil, err - } - - return accessKey, ae, m, nil -} - -// DoPassword is a helper function to the CLI API that handles the entire business logic for -// creating a session key and an access entry given the cli context, password and salt. -// By default - DefaultKdfParams are used as the scrypt params -func DoPassword(ctx *cli.Context, password string, salt []byte) (sessionKey []byte, ae *AccessEntry, err error) { - ae, err = NewAccessEntryPassword(salt, DefaultKdfParams) - if err != nil { - return nil, nil, err - } - - sessionKey, err = NewSessionKeyPassword(password, ae) - if err != nil { - return nil, nil, err - } - return sessionKey, ae, nil -} diff --git a/swarm/api/api.go b/swarm/api/api.go deleted file mode 100644 index de6aec7b228e..000000000000 --- a/swarm/api/api.go +++ /dev/null @@ -1,1017 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package api - -//go:generate mimegen --types=./../../cmd/swarm/mimegen/mime.types --package=api --out=gen_mime.go -//go:generate gofmt -s -w gen_mime.go - -import ( - "archive/tar" - "context" - "crypto/ecdsa" - "encoding/hex" - "errors" - "fmt" - "io" - "math/big" - "net/http" - "path" - "strings" - - "bytes" - "mime" - "path/filepath" - "time" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/contracts/ens" - "github.com/ubiq/go-ubiq/core/types" - "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/spancontext" - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/ubiq/go-ubiq/swarm/storage/feed" - "github.com/ubiq/go-ubiq/swarm/storage/feed/lookup" - - opentracing "github.com/opentracing/opentracing-go" -) - -var ( - apiResolveCount = metrics.NewRegisteredCounter("api.resolve.count", nil) - apiResolveFail = metrics.NewRegisteredCounter("api.resolve.fail", nil) - apiPutCount = metrics.NewRegisteredCounter("api.put.count", nil) - apiPutFail = metrics.NewRegisteredCounter("api.put.fail", nil) - apiGetCount = metrics.NewRegisteredCounter("api.get.count", nil) - apiGetNotFound = metrics.NewRegisteredCounter("api.get.notfound", nil) - apiGetHTTP300 = metrics.NewRegisteredCounter("api.get.http.300", nil) - apiManifestUpdateCount = metrics.NewRegisteredCounter("api.manifestupdate.count", nil) - apiManifestUpdateFail = metrics.NewRegisteredCounter("api.manifestupdate.fail", nil) - apiManifestListCount = metrics.NewRegisteredCounter("api.manifestlist.count", nil) - apiManifestListFail = metrics.NewRegisteredCounter("api.manifestlist.fail", nil) - apiDeleteCount = metrics.NewRegisteredCounter("api.delete.count", nil) - apiDeleteFail = metrics.NewRegisteredCounter("api.delete.fail", nil) - apiGetTarCount = metrics.NewRegisteredCounter("api.gettar.count", nil) - apiGetTarFail = metrics.NewRegisteredCounter("api.gettar.fail", nil) - apiUploadTarCount = metrics.NewRegisteredCounter("api.uploadtar.count", nil) - apiUploadTarFail = metrics.NewRegisteredCounter("api.uploadtar.fail", nil) - apiModifyCount = metrics.NewRegisteredCounter("api.modify.count", nil) - apiModifyFail = metrics.NewRegisteredCounter("api.modify.fail", nil) - apiAddFileCount = metrics.NewRegisteredCounter("api.addfile.count", nil) - apiAddFileFail = metrics.NewRegisteredCounter("api.addfile.fail", nil) - apiRmFileCount = metrics.NewRegisteredCounter("api.removefile.count", nil) - apiRmFileFail = metrics.NewRegisteredCounter("api.removefile.fail", nil) - apiAppendFileCount = metrics.NewRegisteredCounter("api.appendfile.count", nil) - apiAppendFileFail = metrics.NewRegisteredCounter("api.appendfile.fail", nil) - apiGetInvalid = metrics.NewRegisteredCounter("api.get.invalid", nil) -) - -// Resolver interface resolve a domain name to a hash using ENS -type Resolver interface { - Resolve(string) (common.Hash, error) -} - -// ResolveValidator is used to validate the contained Resolver -type ResolveValidator interface { - Resolver - Owner(node [32]byte) (common.Address, error) - HeaderByNumber(context.Context, *big.Int) (*types.Header, error) -} - -// NoResolverError is returned by MultiResolver.Resolve if no resolver -// can be found for the address. -type NoResolverError struct { - TLD string -} - -// NewNoResolverError creates a NoResolverError for the given top level domain -func NewNoResolverError(tld string) *NoResolverError { - return &NoResolverError{TLD: tld} -} - -// Error NoResolverError implements error -func (e *NoResolverError) Error() string { - if e.TLD == "" { - return "no ENS resolver" - } - return fmt.Sprintf("no ENS endpoint configured to resolve .%s TLD names", e.TLD) -} - -// MultiResolver is used to resolve URL addresses based on their TLDs. -// Each TLD can have multiple resolvers, and the resolution from the -// first one in the sequence will be returned. -type MultiResolver struct { - resolvers map[string][]ResolveValidator - nameHash func(string) common.Hash -} - -// MultiResolverOption sets options for MultiResolver and is used as -// arguments for its constructor. -type MultiResolverOption func(*MultiResolver) - -// MultiResolverOptionWithResolver adds a Resolver to a list of resolvers -// for a specific TLD. If TLD is an empty string, the resolver will be added -// to the list of default resolver, the ones that will be used for resolution -// of addresses which do not have their TLD resolver specified. -func MultiResolverOptionWithResolver(r ResolveValidator, tld string) MultiResolverOption { - return func(m *MultiResolver) { - m.resolvers[tld] = append(m.resolvers[tld], r) - } -} - -// NewMultiResolver creates a new instance of MultiResolver. -func NewMultiResolver(opts ...MultiResolverOption) (m *MultiResolver) { - m = &MultiResolver{ - resolvers: make(map[string][]ResolveValidator), - nameHash: ens.EnsNode, - } - for _, o := range opts { - o(m) - } - return m -} - -// Resolve resolves address by choosing a Resolver by TLD. -// If there are more default Resolvers, or for a specific TLD, -// the Hash from the first one which does not return error -// will be returned. -func (m *MultiResolver) Resolve(addr string) (h common.Hash, err error) { - rs, err := m.getResolveValidator(addr) - if err != nil { - return h, err - } - for _, r := range rs { - h, err = r.Resolve(addr) - if err == nil { - return - } - } - return -} - -// getResolveValidator uses the hostname to retrieve the resolver associated with the top level domain -func (m *MultiResolver) getResolveValidator(name string) ([]ResolveValidator, error) { - rs := m.resolvers[""] - tld := path.Ext(name) - if tld != "" { - tld = tld[1:] - rstld, ok := m.resolvers[tld] - if ok { - return rstld, nil - } - } - if len(rs) == 0 { - return rs, NewNoResolverError(tld) - } - return rs, nil -} - -/* -API implements webserver/file system related content storage and retrieval -on top of the FileStore -it is the public interface of the FileStore which is included in the ethereum stack -*/ -type API struct { - feed *feed.Handler - fileStore *storage.FileStore - dns Resolver - Decryptor func(context.Context, string) DecryptFunc -} - -// NewAPI the api constructor initialises a new API instance. -func NewAPI(fileStore *storage.FileStore, dns Resolver, feedHandler *feed.Handler, pk *ecdsa.PrivateKey) (self *API) { - self = &API{ - fileStore: fileStore, - dns: dns, - feed: feedHandler, - Decryptor: func(ctx context.Context, credentials string) DecryptFunc { - return self.doDecrypt(ctx, credentials, pk) - }, - } - return -} - -// Retrieve FileStore reader API -func (a *API) Retrieve(ctx context.Context, addr storage.Address) (reader storage.LazySectionReader, isEncrypted bool) { - return a.fileStore.Retrieve(ctx, addr) -} - -// Store wraps the Store API call of the embedded FileStore -func (a *API) Store(ctx context.Context, data io.Reader, size int64, toEncrypt bool) (addr storage.Address, wait func(ctx context.Context) error, err error) { - log.Debug("api.store", "size", size) - return a.fileStore.Store(ctx, data, size, toEncrypt) -} - -// Resolve a name into a content-addressed hash -// where address could be an ENS name, or a content addressed hash -func (a *API) Resolve(ctx context.Context, address string) (storage.Address, error) { - // if DNS is not configured, return an error - if a.dns == nil { - if hashMatcher.MatchString(address) { - return common.Hex2Bytes(address), nil - } - apiResolveFail.Inc(1) - return nil, fmt.Errorf("no DNS to resolve name: %q", address) - } - // try and resolve the address - resolved, err := a.dns.Resolve(address) - if err != nil { - if hashMatcher.MatchString(address) { - return common.Hex2Bytes(address), nil - } - return nil, err - } - return resolved[:], nil -} - -// Resolve resolves a URI to an Address using the MultiResolver. -func (a *API) ResolveURI(ctx context.Context, uri *URI, credentials string) (storage.Address, error) { - apiResolveCount.Inc(1) - log.Trace("resolving", "uri", uri.Addr) - - var sp opentracing.Span - ctx, sp = spancontext.StartSpan( - ctx, - "api.resolve") - defer sp.Finish() - - // if the URI is immutable, check if the address looks like a hash - if uri.Immutable() { - key := uri.Address() - if key == nil { - return nil, fmt.Errorf("immutable address not a content hash: %q", uri.Addr) - } - return key, nil - } - - addr, err := a.Resolve(ctx, uri.Addr) - if err != nil { - return nil, err - } - - if uri.Path == "" { - return addr, nil - } - walker, err := a.NewManifestWalker(ctx, addr, a.Decryptor(ctx, credentials), nil) - if err != nil { - return nil, err - } - var entry *ManifestEntry - walker.Walk(func(e *ManifestEntry) error { - // if the entry matches the path, set entry and stop - // the walk - if e.Path == uri.Path { - entry = e - // return an error to cancel the walk - return errors.New("found") - } - // ignore non-manifest files - if e.ContentType != ManifestType { - return nil - } - // if the manifest's path is a prefix of the - // requested path, recurse into it by returning - // nil and continuing the walk - if strings.HasPrefix(uri.Path, e.Path) { - return nil - } - return ErrSkipManifest - }) - if entry == nil { - return nil, errors.New("not found") - } - addr = storage.Address(common.Hex2Bytes(entry.Hash)) - return addr, nil -} - -// Put provides singleton manifest creation on top of FileStore store -func (a *API) Put(ctx context.Context, content string, contentType string, toEncrypt bool) (k storage.Address, wait func(context.Context) error, err error) { - apiPutCount.Inc(1) - r := strings.NewReader(content) - key, waitContent, err := a.fileStore.Store(ctx, r, int64(len(content)), toEncrypt) - if err != nil { - apiPutFail.Inc(1) - return nil, nil, err - } - manifest := fmt.Sprintf(`{"entries":[{"hash":"%v","contentType":"%s"}]}`, key, contentType) - r = strings.NewReader(manifest) - key, waitManifest, err := a.fileStore.Store(ctx, r, int64(len(manifest)), toEncrypt) - if err != nil { - apiPutFail.Inc(1) - return nil, nil, err - } - return key, func(ctx context.Context) error { - err := waitContent(ctx) - if err != nil { - return err - } - return waitManifest(ctx) - }, nil -} - -// Get uses iterative manifest retrieval and prefix matching -// to resolve basePath to content using FileStore retrieve -// it returns a section reader, mimeType, status, the key of the actual content and an error -func (a *API) Get(ctx context.Context, decrypt DecryptFunc, manifestAddr storage.Address, path string) (reader storage.LazySectionReader, mimeType string, status int, contentAddr storage.Address, err error) { - log.Debug("api.get", "key", manifestAddr, "path", path) - apiGetCount.Inc(1) - trie, err := loadManifest(ctx, a.fileStore, manifestAddr, nil, decrypt) - if err != nil { - apiGetNotFound.Inc(1) - status = http.StatusNotFound - return nil, "", http.StatusNotFound, nil, err - } - - log.Debug("trie getting entry", "key", manifestAddr, "path", path) - entry, _ := trie.getEntry(path) - - if entry != nil { - log.Debug("trie got entry", "key", manifestAddr, "path", path, "entry.Hash", entry.Hash) - - if entry.ContentType == ManifestType { - log.Debug("entry is manifest", "key", manifestAddr, "new key", entry.Hash) - adr, err := hex.DecodeString(entry.Hash) - if err != nil { - return nil, "", 0, nil, err - } - return a.Get(ctx, decrypt, adr, entry.Path) - } - - // we need to do some extra work if this is a Swarm feed manifest - if entry.ContentType == FeedContentType { - if entry.Feed == nil { - return reader, mimeType, status, nil, fmt.Errorf("Cannot decode Feed in manifest") - } - _, err := a.feed.Lookup(ctx, feed.NewQueryLatest(entry.Feed, lookup.NoClue)) - if err != nil { - apiGetNotFound.Inc(1) - status = http.StatusNotFound - log.Debug(fmt.Sprintf("get feed update content error: %v", err)) - return reader, mimeType, status, nil, err - } - // get the data of the update - _, contentAddr, err := a.feed.GetContent(entry.Feed) - if err != nil { - apiGetNotFound.Inc(1) - status = http.StatusNotFound - log.Warn(fmt.Sprintf("get feed update content error: %v", err)) - return reader, mimeType, status, nil, err - } - - // extract content hash - if len(contentAddr) != storage.AddressLength { - apiGetInvalid.Inc(1) - status = http.StatusUnprocessableEntity - errorMessage := fmt.Sprintf("invalid swarm hash in feed update. Expected %d bytes. Got %d", storage.AddressLength, len(contentAddr)) - log.Warn(errorMessage) - return reader, mimeType, status, nil, errors.New(errorMessage) - } - manifestAddr = storage.Address(contentAddr) - log.Trace("feed update contains swarm hash", "key", manifestAddr) - - // get the manifest the swarm hash points to - trie, err := loadManifest(ctx, a.fileStore, manifestAddr, nil, NOOPDecrypt) - if err != nil { - apiGetNotFound.Inc(1) - status = http.StatusNotFound - log.Warn(fmt.Sprintf("loadManifestTrie (feed update) error: %v", err)) - return reader, mimeType, status, nil, err - } - - // finally, get the manifest entry - // it will always be the entry on path "" - entry, _ = trie.getEntry(path) - if entry == nil { - status = http.StatusNotFound - apiGetNotFound.Inc(1) - err = fmt.Errorf("manifest (feed update) entry for '%s' not found", path) - log.Trace("manifest (feed update) entry not found", "key", manifestAddr, "path", path) - return reader, mimeType, status, nil, err - } - } - - // regardless of feed update manifests or normal manifests we will converge at this point - // get the key the manifest entry points to and serve it if it's unambiguous - contentAddr = common.Hex2Bytes(entry.Hash) - status = entry.Status - if status == http.StatusMultipleChoices { - apiGetHTTP300.Inc(1) - return nil, entry.ContentType, status, contentAddr, err - } - mimeType = entry.ContentType - log.Debug("content lookup key", "key", contentAddr, "mimetype", mimeType) - reader, _ = a.fileStore.Retrieve(ctx, contentAddr) - } else { - // no entry found - status = http.StatusNotFound - apiGetNotFound.Inc(1) - err = fmt.Errorf("Not found: could not find resource '%s'", path) - log.Trace("manifest entry not found", "key", contentAddr, "path", path) - } - return -} - -func (a *API) Delete(ctx context.Context, addr string, path string) (storage.Address, error) { - apiDeleteCount.Inc(1) - uri, err := Parse("bzz:/" + addr) - if err != nil { - apiDeleteFail.Inc(1) - return nil, err - } - key, err := a.ResolveURI(ctx, uri, EMPTY_CREDENTIALS) - - if err != nil { - return nil, err - } - newKey, err := a.UpdateManifest(ctx, key, func(mw *ManifestWriter) error { - log.Debug(fmt.Sprintf("removing %s from manifest %s", path, key.Log())) - return mw.RemoveEntry(path) - }) - if err != nil { - apiDeleteFail.Inc(1) - return nil, err - } - - return newKey, nil -} - -// GetDirectoryTar fetches a requested directory as a tarstream -// it returns an io.Reader and an error. Do not forget to Close() the returned ReadCloser -func (a *API) GetDirectoryTar(ctx context.Context, decrypt DecryptFunc, uri *URI) (io.ReadCloser, error) { - apiGetTarCount.Inc(1) - addr, err := a.Resolve(ctx, uri.Addr) - if err != nil { - return nil, err - } - walker, err := a.NewManifestWalker(ctx, addr, decrypt, nil) - if err != nil { - apiGetTarFail.Inc(1) - return nil, err - } - - piper, pipew := io.Pipe() - - tw := tar.NewWriter(pipew) - - go func() { - err := walker.Walk(func(entry *ManifestEntry) error { - // ignore manifests (walk will recurse into them) - if entry.ContentType == ManifestType { - return nil - } - - // retrieve the entry's key and size - reader, _ := a.Retrieve(ctx, storage.Address(common.Hex2Bytes(entry.Hash))) - size, err := reader.Size(ctx, nil) - if err != nil { - return err - } - - // write a tar header for the entry - hdr := &tar.Header{ - Name: entry.Path, - Mode: entry.Mode, - Size: size, - ModTime: entry.ModTime, - Xattrs: map[string]string{ - "user.swarm.content-type": entry.ContentType, - }, - } - - if err := tw.WriteHeader(hdr); err != nil { - return err - } - - // copy the file into the tar stream - n, err := io.Copy(tw, io.LimitReader(reader, hdr.Size)) - if err != nil { - return err - } else if n != size { - return fmt.Errorf("error writing %s: expected %d bytes but sent %d", entry.Path, size, n) - } - - return nil - }) - // close tar writer before closing pipew - // to flush remaining data to pipew - // regardless of error value - tw.Close() - if err != nil { - apiGetTarFail.Inc(1) - pipew.CloseWithError(err) - } else { - pipew.Close() - } - }() - - return piper, nil -} - -// GetManifestList lists the manifest entries for the specified address and prefix -// and returns it as a ManifestList -func (a *API) GetManifestList(ctx context.Context, decryptor DecryptFunc, addr storage.Address, prefix string) (list ManifestList, err error) { - apiManifestListCount.Inc(1) - walker, err := a.NewManifestWalker(ctx, addr, decryptor, nil) - if err != nil { - apiManifestListFail.Inc(1) - return ManifestList{}, err - } - - err = walker.Walk(func(entry *ManifestEntry) error { - // handle non-manifest files - if entry.ContentType != ManifestType { - // ignore the file if it doesn't have the specified prefix - if !strings.HasPrefix(entry.Path, prefix) { - return nil - } - - // if the path after the prefix contains a slash, add a - // common prefix to the list, otherwise add the entry - suffix := strings.TrimPrefix(entry.Path, prefix) - if index := strings.Index(suffix, "/"); index > -1 { - list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1]) - return nil - } - if entry.Path == "" { - entry.Path = "/" - } - list.Entries = append(list.Entries, entry) - return nil - } - - // if the manifest's path is a prefix of the specified prefix - // then just recurse into the manifest by returning nil and - // continuing the walk - if strings.HasPrefix(prefix, entry.Path) { - return nil - } - - // if the manifest's path has the specified prefix, then if the - // path after the prefix contains a slash, add a common prefix - // to the list and skip the manifest, otherwise recurse into - // the manifest by returning nil and continuing the walk - if strings.HasPrefix(entry.Path, prefix) { - suffix := strings.TrimPrefix(entry.Path, prefix) - if index := strings.Index(suffix, "/"); index > -1 { - list.CommonPrefixes = append(list.CommonPrefixes, prefix+suffix[:index+1]) - return ErrSkipManifest - } - return nil - } - - // the manifest neither has the prefix or needs recursing in to - // so just skip it - return ErrSkipManifest - }) - - if err != nil { - apiManifestListFail.Inc(1) - return ManifestList{}, err - } - - return list, nil -} - -func (a *API) UpdateManifest(ctx context.Context, addr storage.Address, update func(mw *ManifestWriter) error) (storage.Address, error) { - apiManifestUpdateCount.Inc(1) - mw, err := a.NewManifestWriter(ctx, addr, nil) - if err != nil { - apiManifestUpdateFail.Inc(1) - return nil, err - } - - if err := update(mw); err != nil { - apiManifestUpdateFail.Inc(1) - return nil, err - } - - addr, err = mw.Store() - if err != nil { - apiManifestUpdateFail.Inc(1) - return nil, err - } - log.Debug(fmt.Sprintf("generated manifest %s", addr)) - return addr, nil -} - -// Modify loads manifest and checks the content hash before recalculating and storing the manifest. -func (a *API) Modify(ctx context.Context, addr storage.Address, path, contentHash, contentType string) (storage.Address, error) { - apiModifyCount.Inc(1) - quitC := make(chan bool) - trie, err := loadManifest(ctx, a.fileStore, addr, quitC, NOOPDecrypt) - if err != nil { - apiModifyFail.Inc(1) - return nil, err - } - if contentHash != "" { - entry := newManifestTrieEntry(&ManifestEntry{ - Path: path, - ContentType: contentType, - }, nil) - entry.Hash = contentHash - trie.addEntry(entry, quitC) - } else { - trie.deleteEntry(path, quitC) - } - - if err := trie.recalcAndStore(); err != nil { - apiModifyFail.Inc(1) - return nil, err - } - return trie.ref, nil -} - -// AddFile creates a new manifest entry, adds it to swarm, then adds a file to swarm. -func (a *API) AddFile(ctx context.Context, mhash, path, fname string, content []byte, nameresolver bool) (storage.Address, string, error) { - apiAddFileCount.Inc(1) - - uri, err := Parse("bzz:/" + mhash) - if err != nil { - apiAddFileFail.Inc(1) - return nil, "", err - } - mkey, err := a.ResolveURI(ctx, uri, EMPTY_CREDENTIALS) - if err != nil { - apiAddFileFail.Inc(1) - return nil, "", err - } - - // trim the root dir we added - if path[:1] == "/" { - path = path[1:] - } - - entry := &ManifestEntry{ - Path: filepath.Join(path, fname), - ContentType: mime.TypeByExtension(filepath.Ext(fname)), - Mode: 0700, - Size: int64(len(content)), - ModTime: time.Now(), - } - - mw, err := a.NewManifestWriter(ctx, mkey, nil) - if err != nil { - apiAddFileFail.Inc(1) - return nil, "", err - } - - fkey, err := mw.AddEntry(ctx, bytes.NewReader(content), entry) - if err != nil { - apiAddFileFail.Inc(1) - return nil, "", err - } - - newMkey, err := mw.Store() - if err != nil { - apiAddFileFail.Inc(1) - return nil, "", err - - } - - return fkey, newMkey.String(), nil -} - -func (a *API) UploadTar(ctx context.Context, bodyReader io.ReadCloser, manifestPath, defaultPath string, mw *ManifestWriter) (storage.Address, error) { - apiUploadTarCount.Inc(1) - var contentKey storage.Address - tr := tar.NewReader(bodyReader) - defer bodyReader.Close() - var defaultPathFound bool - for { - hdr, err := tr.Next() - if err == io.EOF { - break - } else if err != nil { - apiUploadTarFail.Inc(1) - return nil, fmt.Errorf("error reading tar stream: %s", err) - } - - // only store regular files - if !hdr.FileInfo().Mode().IsRegular() { - continue - } - - // add the entry under the path from the request - manifestPath := path.Join(manifestPath, hdr.Name) - contentType := hdr.Xattrs["user.swarm.content-type"] - if contentType == "" { - contentType = mime.TypeByExtension(filepath.Ext(hdr.Name)) - } - //DetectContentType("") - entry := &ManifestEntry{ - Path: manifestPath, - ContentType: contentType, - Mode: hdr.Mode, - Size: hdr.Size, - ModTime: hdr.ModTime, - } - contentKey, err = mw.AddEntry(ctx, tr, entry) - if err != nil { - apiUploadTarFail.Inc(1) - return nil, fmt.Errorf("error adding manifest entry from tar stream: %s", err) - } - if hdr.Name == defaultPath { - contentType := hdr.Xattrs["user.swarm.content-type"] - if contentType == "" { - contentType = mime.TypeByExtension(filepath.Ext(hdr.Name)) - } - - entry := &ManifestEntry{ - Hash: contentKey.Hex(), - Path: "", // default entry - ContentType: contentType, - Mode: hdr.Mode, - Size: hdr.Size, - ModTime: hdr.ModTime, - } - contentKey, err = mw.AddEntry(ctx, nil, entry) - if err != nil { - apiUploadTarFail.Inc(1) - return nil, fmt.Errorf("error adding default manifest entry from tar stream: %s", err) - } - defaultPathFound = true - } - } - if defaultPath != "" && !defaultPathFound { - return contentKey, fmt.Errorf("default path %q not found", defaultPath) - } - return contentKey, nil -} - -// RemoveFile removes a file entry in a manifest. -func (a *API) RemoveFile(ctx context.Context, mhash string, path string, fname string, nameresolver bool) (string, error) { - apiRmFileCount.Inc(1) - - uri, err := Parse("bzz:/" + mhash) - if err != nil { - apiRmFileFail.Inc(1) - return "", err - } - mkey, err := a.ResolveURI(ctx, uri, EMPTY_CREDENTIALS) - if err != nil { - apiRmFileFail.Inc(1) - return "", err - } - - // trim the root dir we added - if path[:1] == "/" { - path = path[1:] - } - - mw, err := a.NewManifestWriter(ctx, mkey, nil) - if err != nil { - apiRmFileFail.Inc(1) - return "", err - } - - err = mw.RemoveEntry(filepath.Join(path, fname)) - if err != nil { - apiRmFileFail.Inc(1) - return "", err - } - - newMkey, err := mw.Store() - if err != nil { - apiRmFileFail.Inc(1) - return "", err - - } - - return newMkey.String(), nil -} - -// AppendFile removes old manifest, appends file entry to new manifest and adds it to Swarm. -func (a *API) AppendFile(ctx context.Context, mhash, path, fname string, existingSize int64, content []byte, oldAddr storage.Address, offset int64, addSize int64, nameresolver bool) (storage.Address, string, error) { - apiAppendFileCount.Inc(1) - - buffSize := offset + addSize - if buffSize < existingSize { - buffSize = existingSize - } - - buf := make([]byte, buffSize) - - oldReader, _ := a.Retrieve(ctx, oldAddr) - io.ReadAtLeast(oldReader, buf, int(offset)) - - newReader := bytes.NewReader(content) - io.ReadAtLeast(newReader, buf[offset:], int(addSize)) - - if buffSize < existingSize { - io.ReadAtLeast(oldReader, buf[addSize:], int(buffSize)) - } - - combinedReader := bytes.NewReader(buf) - totalSize := int64(len(buf)) - - // TODO(jmozah): to append using pyramid chunker when it is ready - //oldReader := a.Retrieve(oldKey) - //newReader := bytes.NewReader(content) - //combinedReader := io.MultiReader(oldReader, newReader) - - uri, err := Parse("bzz:/" + mhash) - if err != nil { - apiAppendFileFail.Inc(1) - return nil, "", err - } - mkey, err := a.ResolveURI(ctx, uri, EMPTY_CREDENTIALS) - if err != nil { - apiAppendFileFail.Inc(1) - return nil, "", err - } - - // trim the root dir we added - if path[:1] == "/" { - path = path[1:] - } - - mw, err := a.NewManifestWriter(ctx, mkey, nil) - if err != nil { - apiAppendFileFail.Inc(1) - return nil, "", err - } - - err = mw.RemoveEntry(filepath.Join(path, fname)) - if err != nil { - apiAppendFileFail.Inc(1) - return nil, "", err - } - - entry := &ManifestEntry{ - Path: filepath.Join(path, fname), - ContentType: mime.TypeByExtension(filepath.Ext(fname)), - Mode: 0700, - Size: totalSize, - ModTime: time.Now(), - } - - fkey, err := mw.AddEntry(ctx, io.Reader(combinedReader), entry) - if err != nil { - apiAppendFileFail.Inc(1) - return nil, "", err - } - - newMkey, err := mw.Store() - if err != nil { - apiAppendFileFail.Inc(1) - return nil, "", err - - } - - return fkey, newMkey.String(), nil -} - -// BuildDirectoryTree used by swarmfs_unix -func (a *API) BuildDirectoryTree(ctx context.Context, mhash string, nameresolver bool) (addr storage.Address, manifestEntryMap map[string]*manifestTrieEntry, err error) { - - uri, err := Parse("bzz:/" + mhash) - if err != nil { - return nil, nil, err - } - addr, err = a.Resolve(ctx, uri.Addr) - if err != nil { - return nil, nil, err - } - - quitC := make(chan bool) - rootTrie, err := loadManifest(ctx, a.fileStore, addr, quitC, NOOPDecrypt) - if err != nil { - return nil, nil, fmt.Errorf("can't load manifest %v: %v", addr.String(), err) - } - - manifestEntryMap = map[string]*manifestTrieEntry{} - err = rootTrie.listWithPrefix(uri.Path, quitC, func(entry *manifestTrieEntry, suffix string) { - manifestEntryMap[suffix] = entry - }) - - if err != nil { - return nil, nil, fmt.Errorf("list with prefix failed %v: %v", addr.String(), err) - } - return addr, manifestEntryMap, nil -} - -// FeedsLookup finds Swarm feeds updates at specific points in time, or the latest update -func (a *API) FeedsLookup(ctx context.Context, query *feed.Query) ([]byte, error) { - _, err := a.feed.Lookup(ctx, query) - if err != nil { - return nil, err - } - var data []byte - _, data, err = a.feed.GetContent(&query.Feed) - if err != nil { - return nil, err - } - return data, nil -} - -// FeedsNewRequest creates a Request object to update a specific feed -func (a *API) FeedsNewRequest(ctx context.Context, feed *feed.Feed) (*feed.Request, error) { - return a.feed.NewRequest(ctx, feed) -} - -// FeedsUpdate publishes a new update on the given feed -func (a *API) FeedsUpdate(ctx context.Context, request *feed.Request) (storage.Address, error) { - return a.feed.Update(ctx, request) -} - -// ErrCannotLoadFeedManifest is returned when looking up a feeds manifest fails -var ErrCannotLoadFeedManifest = errors.New("Cannot load feed manifest") - -// ErrNotAFeedManifest is returned when the address provided returned something other than a valid manifest -var ErrNotAFeedManifest = errors.New("Not a feed manifest") - -// ResolveFeedManifest retrieves the Swarm feed manifest for the given address, and returns the referenced Feed. -func (a *API) ResolveFeedManifest(ctx context.Context, addr storage.Address) (*feed.Feed, error) { - trie, err := loadManifest(ctx, a.fileStore, addr, nil, NOOPDecrypt) - if err != nil { - return nil, ErrCannotLoadFeedManifest - } - - entry, _ := trie.getEntry("") - if entry.ContentType != FeedContentType { - return nil, ErrNotAFeedManifest - } - - return entry.Feed, nil -} - -// ErrCannotResolveFeedURI is returned when the ENS resolver is not able to translate a name to a Swarm feed -var ErrCannotResolveFeedURI = errors.New("Cannot resolve Feed URI") - -// ErrCannotResolveFeed is returned when values provided are not enough or invalid to recreate a -// feed out of them. -var ErrCannotResolveFeed = errors.New("Cannot resolve Feed") - -// ResolveFeed attempts to extract feed information out of the manifest, if provided -// If not, it attempts to extract the feed out of a set of key-value pairs -func (a *API) ResolveFeed(ctx context.Context, uri *URI, values feed.Values) (*feed.Feed, error) { - var fd *feed.Feed - var err error - if uri.Addr != "" { - // resolve the content key. - manifestAddr := uri.Address() - if manifestAddr == nil { - manifestAddr, err = a.Resolve(ctx, uri.Addr) - if err != nil { - return nil, ErrCannotResolveFeedURI - } - } - - // get the Swarm feed from the manifest - fd, err = a.ResolveFeedManifest(ctx, manifestAddr) - if err != nil { - return nil, err - } - log.Debug("handle.get.feed: resolved", "manifestkey", manifestAddr, "feed", fd.Hex()) - } else { - var f feed.Feed - if err := f.FromValues(values); err != nil { - return nil, ErrCannotResolveFeed - - } - fd = &f - } - return fd, nil -} - -// MimeOctetStream default value of http Content-Type header -const MimeOctetStream = "application/octet-stream" - -// DetectContentType by file file extension, or fallback to content sniff -func DetectContentType(fileName string, f io.ReadSeeker) (string, error) { - ctype := mime.TypeByExtension(filepath.Ext(fileName)) - if ctype != "" { - return ctype, nil - } - - // save/rollback to get content probe from begin of file - currentPosition, err := f.Seek(0, io.SeekCurrent) - if err != nil { - return MimeOctetStream, fmt.Errorf("seeker can't seek, %s", err) - } - - // read a chunk to decide between utf-8 text and binary - var buf [512]byte - n, _ := f.Read(buf[:]) - ctype = http.DetectContentType(buf[:n]) - - _, err = f.Seek(currentPosition, io.SeekStart) // rewind to output whole file - if err != nil { - return MimeOctetStream, fmt.Errorf("seeker can't seek, %s", err) - } - - return ctype, nil -} diff --git a/swarm/api/api_test.go b/swarm/api/api_test.go deleted file mode 100644 index d7a0bc2bd1bc..000000000000 --- a/swarm/api/api_test.go +++ /dev/null @@ -1,502 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package api - -import ( - "bytes" - "context" - "errors" - "flag" - "fmt" - "io" - "io/ioutil" - "math/big" - "os" - "testing" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/core/types" - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/swarm/sctx" - "github.com/ubiq/go-ubiq/swarm/storage" -) - -func init() { - loglevel := flag.Int("loglevel", 2, "loglevel") - flag.Parse() - log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(true))))) -} - -func testAPI(t *testing.T, f func(*API, bool)) { - datadir, err := ioutil.TempDir("", "bzz-test") - if err != nil { - t.Fatalf("unable to create temp dir: %v", err) - } - defer os.RemoveAll(datadir) - fileStore, err := storage.NewLocalFileStore(datadir, make([]byte, 32)) - if err != nil { - return - } - api := NewAPI(fileStore, nil, nil, nil) - f(api, false) - f(api, true) -} - -type testResponse struct { - reader storage.LazySectionReader - *Response -} - -func checkResponse(t *testing.T, resp *testResponse, exp *Response) { - - if resp.MimeType != exp.MimeType { - t.Errorf("incorrect mimeType. expected '%s', got '%s'", exp.MimeType, resp.MimeType) - } - if resp.Status != exp.Status { - t.Errorf("incorrect status. expected '%d', got '%d'", exp.Status, resp.Status) - } - if resp.Size != exp.Size { - t.Errorf("incorrect size. expected '%d', got '%d'", exp.Size, resp.Size) - } - if resp.reader != nil { - content := make([]byte, resp.Size) - read, _ := resp.reader.Read(content) - if int64(read) != exp.Size { - t.Errorf("incorrect content length. expected '%d...', got '%d...'", read, exp.Size) - } - resp.Content = string(content) - } - if resp.Content != exp.Content { - // if !bytes.Equal(resp.Content, exp.Content) - t.Errorf("incorrect content. expected '%s...', got '%s...'", string(exp.Content), string(resp.Content)) - } -} - -// func expResponse(content []byte, mimeType string, status int) *Response { -func expResponse(content string, mimeType string, status int) *Response { - log.Trace(fmt.Sprintf("expected content (%v): %v ", len(content), content)) - return &Response{mimeType, status, int64(len(content)), content} -} - -func testGet(t *testing.T, api *API, bzzhash, path string) *testResponse { - addr := storage.Address(common.Hex2Bytes(bzzhash)) - reader, mimeType, status, _, err := api.Get(context.TODO(), NOOPDecrypt, addr, path) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - quitC := make(chan bool) - size, err := reader.Size(context.TODO(), quitC) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - log.Trace(fmt.Sprintf("reader size: %v ", size)) - s := make([]byte, size) - _, err = reader.Read(s) - if err != io.EOF { - t.Fatalf("unexpected error: %v", err) - } - reader.Seek(0, 0) - return &testResponse{reader, &Response{mimeType, status, size, string(s)}} - // return &testResponse{reader, &Response{mimeType, status, reader.Size(), nil}} -} - -func TestApiPut(t *testing.T) { - testAPI(t, func(api *API, toEncrypt bool) { - content := "hello" - exp := expResponse(content, "text/plain", 0) - ctx := context.TODO() - addr, wait, err := api.Put(ctx, content, exp.MimeType, toEncrypt) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - err = wait(ctx) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - resp := testGet(t, api, addr.Hex(), "") - checkResponse(t, resp, exp) - }) -} - -// testResolver implements the Resolver interface and either returns the given -// hash if it is set, or returns a "name not found" error -type testResolveValidator struct { - hash *common.Hash -} - -func newTestResolveValidator(addr string) *testResolveValidator { - r := &testResolveValidator{} - if addr != "" { - hash := common.HexToHash(addr) - r.hash = &hash - } - return r -} - -func (t *testResolveValidator) Resolve(addr string) (common.Hash, error) { - if t.hash == nil { - return common.Hash{}, fmt.Errorf("DNS name not found: %q", addr) - } - return *t.hash, nil -} - -func (t *testResolveValidator) Owner(node [32]byte) (addr common.Address, err error) { - return -} -func (t *testResolveValidator) HeaderByNumber(context.Context, *big.Int) (header *types.Header, err error) { - return -} - -// TestAPIResolve tests resolving URIs which can either contain content hashes -// or ENS names -func TestAPIResolve(t *testing.T) { - ensAddr := "swarm.eth" - hashAddr := "1111111111111111111111111111111111111111111111111111111111111111" - resolvedAddr := "2222222222222222222222222222222222222222222222222222222222222222" - doesResolve := newTestResolveValidator(resolvedAddr) - doesntResolve := newTestResolveValidator("") - - type test struct { - desc string - dns Resolver - addr string - immutable bool - result string - expectErr error - } - - tests := []*test{ - { - desc: "DNS not configured, hash address, returns hash address", - dns: nil, - addr: hashAddr, - result: hashAddr, - }, - { - desc: "DNS not configured, ENS address, returns error", - dns: nil, - addr: ensAddr, - expectErr: errors.New(`no DNS to resolve name: "swarm.eth"`), - }, - { - desc: "DNS configured, hash address, hash resolves, returns resolved address", - dns: doesResolve, - addr: hashAddr, - result: resolvedAddr, - }, - { - desc: "DNS configured, immutable hash address, hash resolves, returns hash address", - dns: doesResolve, - addr: hashAddr, - immutable: true, - result: hashAddr, - }, - { - desc: "DNS configured, hash address, hash doesn't resolve, returns hash address", - dns: doesntResolve, - addr: hashAddr, - result: hashAddr, - }, - { - desc: "DNS configured, ENS address, name resolves, returns resolved address", - dns: doesResolve, - addr: ensAddr, - result: resolvedAddr, - }, - { - desc: "DNS configured, immutable ENS address, name resolves, returns error", - dns: doesResolve, - addr: ensAddr, - immutable: true, - expectErr: errors.New(`immutable address not a content hash: "swarm.eth"`), - }, - { - desc: "DNS configured, ENS address, name doesn't resolve, returns error", - dns: doesntResolve, - addr: ensAddr, - expectErr: errors.New(`DNS name not found: "swarm.eth"`), - }, - } - for _, x := range tests { - t.Run(x.desc, func(t *testing.T) { - api := &API{dns: x.dns} - uri := &URI{Addr: x.addr, Scheme: "bzz"} - if x.immutable { - uri.Scheme = "bzz-immutable" - } - res, err := api.ResolveURI(context.TODO(), uri, "") - if err == nil { - if x.expectErr != nil { - t.Fatalf("expected error %q, got result %q", x.expectErr, res) - } - if res.String() != x.result { - t.Fatalf("expected result %q, got %q", x.result, res) - } - } else { - if x.expectErr == nil { - t.Fatalf("expected no error, got %q", err) - } - if err.Error() != x.expectErr.Error() { - t.Fatalf("expected error %q, got %q", x.expectErr, err) - } - } - }) - } -} - -func TestMultiResolver(t *testing.T) { - doesntResolve := newTestResolveValidator("") - - ethAddr := "swarm.eth" - ethHash := "0x2222222222222222222222222222222222222222222222222222222222222222" - ethResolve := newTestResolveValidator(ethHash) - - testAddr := "swarm.test" - testHash := "0x1111111111111111111111111111111111111111111111111111111111111111" - testResolve := newTestResolveValidator(testHash) - - tests := []struct { - desc string - r Resolver - addr string - result string - err error - }{ - { - desc: "No resolvers, returns error", - r: NewMultiResolver(), - err: NewNoResolverError(""), - }, - { - desc: "One default resolver, returns resolved address", - r: NewMultiResolver(MultiResolverOptionWithResolver(ethResolve, "")), - addr: ethAddr, - result: ethHash, - }, - { - desc: "Two default resolvers, returns resolved address", - r: NewMultiResolver( - MultiResolverOptionWithResolver(ethResolve, ""), - MultiResolverOptionWithResolver(ethResolve, ""), - ), - addr: ethAddr, - result: ethHash, - }, - { - desc: "Two default resolvers, first doesn't resolve, returns resolved address", - r: NewMultiResolver( - MultiResolverOptionWithResolver(doesntResolve, ""), - MultiResolverOptionWithResolver(ethResolve, ""), - ), - addr: ethAddr, - result: ethHash, - }, - { - desc: "Default resolver doesn't resolve, tld resolver resolve, returns resolved address", - r: NewMultiResolver( - MultiResolverOptionWithResolver(doesntResolve, ""), - MultiResolverOptionWithResolver(ethResolve, "eth"), - ), - addr: ethAddr, - result: ethHash, - }, - { - desc: "Three TLD resolvers, third resolves, returns resolved address", - r: NewMultiResolver( - MultiResolverOptionWithResolver(doesntResolve, "eth"), - MultiResolverOptionWithResolver(doesntResolve, "eth"), - MultiResolverOptionWithResolver(ethResolve, "eth"), - ), - addr: ethAddr, - result: ethHash, - }, - { - desc: "One TLD resolver doesn't resolve, returns error", - r: NewMultiResolver( - MultiResolverOptionWithResolver(doesntResolve, ""), - MultiResolverOptionWithResolver(ethResolve, "eth"), - ), - addr: ethAddr, - result: ethHash, - }, - { - desc: "One defautl and one TLD resolver, all doesn't resolve, returns error", - r: NewMultiResolver( - MultiResolverOptionWithResolver(doesntResolve, ""), - MultiResolverOptionWithResolver(doesntResolve, "eth"), - ), - addr: ethAddr, - result: ethHash, - err: errors.New(`DNS name not found: "swarm.eth"`), - }, - { - desc: "Two TLD resolvers, both resolve, returns resolved address", - r: NewMultiResolver( - MultiResolverOptionWithResolver(ethResolve, "eth"), - MultiResolverOptionWithResolver(testResolve, "test"), - ), - addr: testAddr, - result: testHash, - }, - { - desc: "One TLD resolver, no default resolver, returns error for different TLD", - r: NewMultiResolver( - MultiResolverOptionWithResolver(ethResolve, "eth"), - ), - addr: testAddr, - err: NewNoResolverError("test"), - }, - } - for _, x := range tests { - t.Run(x.desc, func(t *testing.T) { - res, err := x.r.Resolve(x.addr) - if err == nil { - if x.err != nil { - t.Fatalf("expected error %q, got result %q", x.err, res.Hex()) - } - if res.Hex() != x.result { - t.Fatalf("expected result %q, got %q", x.result, res.Hex()) - } - } else { - if x.err == nil { - t.Fatalf("expected no error, got %q", err) - } - if err.Error() != x.err.Error() { - t.Fatalf("expected error %q, got %q", x.err, err) - } - } - }) - } -} - -func TestDecryptOriginForbidden(t *testing.T) { - ctx := context.TODO() - ctx = sctx.SetHost(ctx, "swarm-gateways.net") - - me := &ManifestEntry{ - Access: &AccessEntry{Type: AccessTypePass}, - } - - api := NewAPI(nil, nil, nil, nil) - - f := api.Decryptor(ctx, "") - err := f(me) - if err != ErrDecryptDomainForbidden { - t.Fatalf("should fail with ErrDecryptDomainForbidden, got %v", err) - } -} - -func TestDecryptOrigin(t *testing.T) { - for _, v := range []struct { - host string - expectError error - }{ - { - host: "localhost", - expectError: ErrDecrypt, - }, - { - host: "127.0.0.1", - expectError: ErrDecrypt, - }, - { - host: "swarm-gateways.net", - expectError: ErrDecryptDomainForbidden, - }, - } { - ctx := context.TODO() - ctx = sctx.SetHost(ctx, v.host) - - me := &ManifestEntry{ - Access: &AccessEntry{Type: AccessTypePass}, - } - - api := NewAPI(nil, nil, nil, nil) - - f := api.Decryptor(ctx, "") - err := f(me) - if err != v.expectError { - t.Fatalf("should fail with %v, got %v", v.expectError, err) - } - } -} - -func TestDetectContentType(t *testing.T) { - for _, tc := range []struct { - file string - content string - expectedContentType string - }{ - { - file: "file-with-correct-css.css", - content: "body {background-color: orange}", - expectedContentType: "text/css; charset=utf-8", - }, - { - file: "empty-file.css", - content: "", - expectedContentType: "text/css; charset=utf-8", - }, - { - file: "empty-file.pdf", - content: "", - expectedContentType: "application/pdf", - }, - { - file: "empty-file.md", - content: "", - expectedContentType: "text/markdown; charset=utf-8", - }, - { - file: "empty-file-with-unknown-content.strangeext", - content: "", - expectedContentType: "text/plain; charset=utf-8", - }, - { - file: "file-with-unknown-extension-and-content.strangeext", - content: "Lorem Ipsum", - expectedContentType: "text/plain; charset=utf-8", - }, - { - file: "file-no-extension", - content: "Lorem Ipsum", - expectedContentType: "text/plain; charset=utf-8", - }, - { - file: "file-no-extension-no-content", - content: "", - expectedContentType: "text/plain; charset=utf-8", - }, - { - file: "css-file-with-html-inside.css", - content: "", - expectedContentType: "text/css; charset=utf-8", - }, - } { - t.Run(tc.file, func(t *testing.T) { - detected, err := DetectContentType(tc.file, bytes.NewReader([]byte(tc.content))) - if err != nil { - t.Fatal(err) - } - - if detected != tc.expectedContentType { - t.Fatalf("File: %s, Expected mime type %s, got %s", tc.file, tc.expectedContentType, detected) - } - - }) - } -} diff --git a/swarm/api/client/client.go b/swarm/api/client/client.go deleted file mode 100644 index 02fda87a1c36..000000000000 --- a/swarm/api/client/client.go +++ /dev/null @@ -1,797 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package client - -import ( - "archive/tar" - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "io/ioutil" - "mime/multipart" - "net/http" - "net/http/httptrace" - "net/textproto" - "net/url" - "os" - "path/filepath" - "regexp" - "strconv" - "strings" - "time" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/swarm/api" - "github.com/ubiq/go-ubiq/swarm/spancontext" - "github.com/ubiq/go-ubiq/swarm/storage/feed" - "github.com/pborman/uuid" -) - -var ( - ErrUnauthorized = errors.New("unauthorized") -) - -func NewClient(gateway string) *Client { - return &Client{ - Gateway: gateway, - } -} - -// Client wraps interaction with a swarm HTTP gateway. -type Client struct { - Gateway string -} - -// UploadRaw uploads raw data to swarm and returns the resulting hash. If toEncrypt is true it -// uploads encrypted data -func (c *Client) UploadRaw(r io.Reader, size int64, toEncrypt bool) (string, error) { - if size <= 0 { - return "", errors.New("data size must be greater than zero") - } - addr := "" - if toEncrypt { - addr = "encrypt" - } - req, err := http.NewRequest("POST", c.Gateway+"/bzz-raw:/"+addr, r) - if err != nil { - return "", err - } - req.ContentLength = size - res, err := http.DefaultClient.Do(req) - if err != nil { - return "", err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return "", fmt.Errorf("unexpected HTTP status: %s", res.Status) - } - data, err := ioutil.ReadAll(res.Body) - if err != nil { - return "", err - } - return string(data), nil -} - -// DownloadRaw downloads raw data from swarm and it returns a ReadCloser and a bool whether the -// content was encrypted -func (c *Client) DownloadRaw(hash string) (io.ReadCloser, bool, error) { - uri := c.Gateway + "/bzz-raw:/" + hash - res, err := http.DefaultClient.Get(uri) - if err != nil { - return nil, false, err - } - if res.StatusCode != http.StatusOK { - res.Body.Close() - return nil, false, fmt.Errorf("unexpected HTTP status: %s", res.Status) - } - isEncrypted := (res.Header.Get("X-Decrypted") == "true") - return res.Body, isEncrypted, nil -} - -// File represents a file in a swarm manifest and is used for uploading and -// downloading content to and from swarm -type File struct { - io.ReadCloser - api.ManifestEntry -} - -// Open opens a local file which can then be passed to client.Upload to upload -// it to swarm -func Open(path string) (*File, error) { - f, err := os.Open(path) - if err != nil { - return nil, err - } - stat, err := f.Stat() - if err != nil { - f.Close() - return nil, err - } - - contentType, err := api.DetectContentType(f.Name(), f) - if err != nil { - return nil, err - } - - return &File{ - ReadCloser: f, - ManifestEntry: api.ManifestEntry{ - ContentType: contentType, - Mode: int64(stat.Mode()), - Size: stat.Size(), - ModTime: stat.ModTime(), - }, - }, nil -} - -// Upload uploads a file to swarm and either adds it to an existing manifest -// (if the manifest argument is non-empty) or creates a new manifest containing -// the file, returning the resulting manifest hash (the file will then be -// available at bzz://) -func (c *Client) Upload(file *File, manifest string, toEncrypt bool) (string, error) { - if file.Size <= 0 { - return "", errors.New("file size must be greater than zero") - } - return c.TarUpload(manifest, &FileUploader{file}, "", toEncrypt) -} - -// Download downloads a file with the given path from the swarm manifest with -// the given hash (i.e. it gets bzz://) -func (c *Client) Download(hash, path string) (*File, error) { - uri := c.Gateway + "/bzz:/" + hash + "/" + path - res, err := http.DefaultClient.Get(uri) - if err != nil { - return nil, err - } - if res.StatusCode != http.StatusOK { - res.Body.Close() - return nil, fmt.Errorf("unexpected HTTP status: %s", res.Status) - } - return &File{ - ReadCloser: res.Body, - ManifestEntry: api.ManifestEntry{ - ContentType: res.Header.Get("Content-Type"), - Size: res.ContentLength, - }, - }, nil -} - -// UploadDirectory uploads a directory tree to swarm and either adds the files -// to an existing manifest (if the manifest argument is non-empty) or creates a -// new manifest, returning the resulting manifest hash (files from the -// directory will then be available at bzz://path/to/file), with -// the file specified in defaultPath being uploaded to the root of the manifest -// (i.e. bzz://) -func (c *Client) UploadDirectory(dir, defaultPath, manifest string, toEncrypt bool) (string, error) { - stat, err := os.Stat(dir) - if err != nil { - return "", err - } else if !stat.IsDir() { - return "", fmt.Errorf("not a directory: %s", dir) - } - if defaultPath != "" { - if _, err := os.Stat(filepath.Join(dir, defaultPath)); err != nil { - if os.IsNotExist(err) { - return "", fmt.Errorf("the default path %q was not found in the upload directory %q", defaultPath, dir) - } - return "", fmt.Errorf("default path: %v", err) - } - } - return c.TarUpload(manifest, &DirectoryUploader{dir}, defaultPath, toEncrypt) -} - -// DownloadDirectory downloads the files contained in a swarm manifest under -// the given path into a local directory (existing files will be overwritten) -func (c *Client) DownloadDirectory(hash, path, destDir, credentials string) error { - stat, err := os.Stat(destDir) - if err != nil { - return err - } else if !stat.IsDir() { - return fmt.Errorf("not a directory: %s", destDir) - } - - uri := c.Gateway + "/bzz:/" + hash + "/" + path - req, err := http.NewRequest("GET", uri, nil) - if err != nil { - return err - } - if credentials != "" { - req.SetBasicAuth("", credentials) - } - req.Header.Set("Accept", "application/x-tar") - res, err := http.DefaultClient.Do(req) - if err != nil { - return err - } - defer res.Body.Close() - switch res.StatusCode { - case http.StatusOK: - case http.StatusUnauthorized: - return ErrUnauthorized - default: - return fmt.Errorf("unexpected HTTP status: %s", res.Status) - } - tr := tar.NewReader(res.Body) - for { - hdr, err := tr.Next() - if err == io.EOF { - return nil - } else if err != nil { - return err - } - // ignore the default path file - if hdr.Name == "" { - continue - } - - dstPath := filepath.Join(destDir, filepath.Clean(strings.TrimPrefix(hdr.Name, path))) - if err := os.MkdirAll(filepath.Dir(dstPath), 0755); err != nil { - return err - } - var mode os.FileMode = 0644 - if hdr.Mode > 0 { - mode = os.FileMode(hdr.Mode) - } - dst, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode) - if err != nil { - return err - } - n, err := io.Copy(dst, tr) - dst.Close() - if err != nil { - return err - } else if n != hdr.Size { - return fmt.Errorf("expected %s to be %d bytes but got %d", hdr.Name, hdr.Size, n) - } - } -} - -// DownloadFile downloads a single file into the destination directory -// if the manifest entry does not specify a file name - it will fallback -// to the hash of the file as a filename -func (c *Client) DownloadFile(hash, path, dest, credentials string) error { - hasDestinationFilename := false - if stat, err := os.Stat(dest); err == nil { - hasDestinationFilename = !stat.IsDir() - } else { - if os.IsNotExist(err) { - // does not exist - should be created - hasDestinationFilename = true - } else { - return fmt.Errorf("could not stat path: %v", err) - } - } - - manifestList, err := c.List(hash, path, credentials) - if err != nil { - return err - } - - switch len(manifestList.Entries) { - case 0: - return fmt.Errorf("could not find path requested at manifest address. make sure the path you've specified is correct") - case 1: - //continue - default: - return fmt.Errorf("got too many matches for this path") - } - - uri := c.Gateway + "/bzz:/" + hash + "/" + path - req, err := http.NewRequest("GET", uri, nil) - if err != nil { - return err - } - if credentials != "" { - req.SetBasicAuth("", credentials) - } - res, err := http.DefaultClient.Do(req) - if err != nil { - return err - } - defer res.Body.Close() - switch res.StatusCode { - case http.StatusOK: - case http.StatusUnauthorized: - return ErrUnauthorized - default: - return fmt.Errorf("unexpected HTTP status: expected 200 OK, got %d", res.StatusCode) - } - filename := "" - if hasDestinationFilename { - filename = dest - } else { - // try to assert - re := regexp.MustCompile("[^/]+$") //everything after last slash - - if results := re.FindAllString(path, -1); len(results) > 0 { - filename = results[len(results)-1] - } else { - if entry := manifestList.Entries[0]; entry.Path != "" && entry.Path != "/" { - filename = entry.Path - } else { - // assume hash as name if there's nothing from the command line - filename = hash - } - } - filename = filepath.Join(dest, filename) - } - filePath, err := filepath.Abs(filename) - if err != nil { - return err - } - - if err := os.MkdirAll(filepath.Dir(filePath), 0777); err != nil { - return err - } - - dst, err := os.Create(filename) - if err != nil { - return err - } - defer dst.Close() - - _, err = io.Copy(dst, res.Body) - return err -} - -// UploadManifest uploads the given manifest to swarm -func (c *Client) UploadManifest(m *api.Manifest, toEncrypt bool) (string, error) { - data, err := json.Marshal(m) - if err != nil { - return "", err - } - return c.UploadRaw(bytes.NewReader(data), int64(len(data)), toEncrypt) -} - -// DownloadManifest downloads a swarm manifest -func (c *Client) DownloadManifest(hash string) (*api.Manifest, bool, error) { - res, isEncrypted, err := c.DownloadRaw(hash) - if err != nil { - return nil, isEncrypted, err - } - defer res.Close() - var manifest api.Manifest - if err := json.NewDecoder(res).Decode(&manifest); err != nil { - return nil, isEncrypted, err - } - return &manifest, isEncrypted, nil -} - -// List list files in a swarm manifest which have the given prefix, grouping -// common prefixes using "/" as a delimiter. -// -// For example, if the manifest represents the following directory structure: -// -// file1.txt -// file2.txt -// dir1/file3.txt -// dir1/dir2/file4.txt -// -// Then: -// -// - a prefix of "" would return [dir1/, file1.txt, file2.txt] -// - a prefix of "file" would return [file1.txt, file2.txt] -// - a prefix of "dir1/" would return [dir1/dir2/, dir1/file3.txt] -// -// where entries ending with "/" are common prefixes. -func (c *Client) List(hash, prefix, credentials string) (*api.ManifestList, error) { - req, err := http.NewRequest(http.MethodGet, c.Gateway+"/bzz-list:/"+hash+"/"+prefix, nil) - if err != nil { - return nil, err - } - if credentials != "" { - req.SetBasicAuth("", credentials) - } - res, err := http.DefaultClient.Do(req) - if err != nil { - return nil, err - } - defer res.Body.Close() - switch res.StatusCode { - case http.StatusOK: - case http.StatusUnauthorized: - return nil, ErrUnauthorized - default: - return nil, fmt.Errorf("unexpected HTTP status: %s", res.Status) - } - var list api.ManifestList - if err := json.NewDecoder(res.Body).Decode(&list); err != nil { - return nil, err - } - return &list, nil -} - -// Uploader uploads files to swarm using a provided UploadFn -type Uploader interface { - Upload(UploadFn) error -} - -type UploaderFunc func(UploadFn) error - -func (u UploaderFunc) Upload(upload UploadFn) error { - return u(upload) -} - -// DirectoryUploader uploads all files in a directory, optionally uploading -// a file to the default path -type DirectoryUploader struct { - Dir string -} - -// Upload performs the upload of the directory and default path -func (d *DirectoryUploader) Upload(upload UploadFn) error { - return filepath.Walk(d.Dir, func(path string, f os.FileInfo, err error) error { - if err != nil { - return err - } - if f.IsDir() { - return nil - } - file, err := Open(path) - if err != nil { - return err - } - relPath, err := filepath.Rel(d.Dir, path) - if err != nil { - return err - } - file.Path = filepath.ToSlash(relPath) - return upload(file) - }) -} - -// FileUploader uploads a single file -type FileUploader struct { - File *File -} - -// Upload performs the upload of the file -func (f *FileUploader) Upload(upload UploadFn) error { - return upload(f.File) -} - -// UploadFn is the type of function passed to an Uploader to perform the upload -// of a single file (for example, a directory uploader would call a provided -// UploadFn for each file in the directory tree) -type UploadFn func(file *File) error - -// TarUpload uses the given Uploader to upload files to swarm as a tar stream, -// returning the resulting manifest hash -func (c *Client) TarUpload(hash string, uploader Uploader, defaultPath string, toEncrypt bool) (string, error) { - ctx, sp := spancontext.StartSpan(context.Background(), "api.client.tarupload") - defer sp.Finish() - - var tn time.Time - - reqR, reqW := io.Pipe() - defer reqR.Close() - addr := hash - - // If there is a hash already (a manifest), then that manifest will determine if the upload has - // to be encrypted or not. If there is no manifest then the toEncrypt parameter decides if - // there is encryption or not. - if hash == "" && toEncrypt { - // This is the built-in address for the encrypted upload endpoint - addr = "encrypt" - } - req, err := http.NewRequest("POST", c.Gateway+"/bzz:/"+addr, reqR) - if err != nil { - return "", err - } - - trace := GetClientTrace("swarm api client - upload tar", "api.client.uploadtar", uuid.New()[:8], &tn) - - req = req.WithContext(httptrace.WithClientTrace(ctx, trace)) - transport := http.DefaultTransport - - req.Header.Set("Content-Type", "application/x-tar") - if defaultPath != "" { - q := req.URL.Query() - q.Set("defaultpath", defaultPath) - req.URL.RawQuery = q.Encode() - } - - // use 'Expect: 100-continue' so we don't send the request body if - // the server refuses the request - req.Header.Set("Expect", "100-continue") - - tw := tar.NewWriter(reqW) - - // define an UploadFn which adds files to the tar stream - uploadFn := func(file *File) error { - hdr := &tar.Header{ - Name: file.Path, - Mode: file.Mode, - Size: file.Size, - ModTime: file.ModTime, - Xattrs: map[string]string{ - "user.swarm.content-type": file.ContentType, - }, - } - if err := tw.WriteHeader(hdr); err != nil { - return err - } - _, err = io.Copy(tw, file) - return err - } - - // run the upload in a goroutine so we can send the request headers and - // wait for a '100 Continue' response before sending the tar stream - go func() { - err := uploader.Upload(uploadFn) - if err == nil { - err = tw.Close() - } - reqW.CloseWithError(err) - }() - tn = time.Now() - res, err := transport.RoundTrip(req) - if err != nil { - return "", err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return "", fmt.Errorf("unexpected HTTP status: %s", res.Status) - } - data, err := ioutil.ReadAll(res.Body) - if err != nil { - return "", err - } - return string(data), nil -} - -// MultipartUpload uses the given Uploader to upload files to swarm as a -// multipart form, returning the resulting manifest hash -func (c *Client) MultipartUpload(hash string, uploader Uploader) (string, error) { - reqR, reqW := io.Pipe() - defer reqR.Close() - req, err := http.NewRequest("POST", c.Gateway+"/bzz:/"+hash, reqR) - if err != nil { - return "", err - } - - // use 'Expect: 100-continue' so we don't send the request body if - // the server refuses the request - req.Header.Set("Expect", "100-continue") - - mw := multipart.NewWriter(reqW) - req.Header.Set("Content-Type", fmt.Sprintf("multipart/form-data; boundary=%q", mw.Boundary())) - - // define an UploadFn which adds files to the multipart form - uploadFn := func(file *File) error { - hdr := make(textproto.MIMEHeader) - hdr.Set("Content-Disposition", fmt.Sprintf("form-data; name=%q", file.Path)) - hdr.Set("Content-Type", file.ContentType) - hdr.Set("Content-Length", strconv.FormatInt(file.Size, 10)) - w, err := mw.CreatePart(hdr) - if err != nil { - return err - } - _, err = io.Copy(w, file) - return err - } - - // run the upload in a goroutine so we can send the request headers and - // wait for a '100 Continue' response before sending the multipart form - go func() { - err := uploader.Upload(uploadFn) - if err == nil { - err = mw.Close() - } - reqW.CloseWithError(err) - }() - - res, err := http.DefaultClient.Do(req) - if err != nil { - return "", err - } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return "", fmt.Errorf("unexpected HTTP status: %s", res.Status) - } - data, err := ioutil.ReadAll(res.Body) - if err != nil { - return "", err - } - return string(data), nil -} - -// ErrNoFeedUpdatesFound is returned when Swarm cannot find updates of the given feed -var ErrNoFeedUpdatesFound = errors.New("No updates found for this feed") - -// CreateFeedWithManifest creates a feed manifest, initializing it with the provided -// data -// Returns the resulting feed manifest address that you can use to include in an ENS Resolver (setContent) -// or reference future updates (Client.UpdateFeed) -func (c *Client) CreateFeedWithManifest(request *feed.Request) (string, error) { - responseStream, err := c.updateFeed(request, true) - if err != nil { - return "", err - } - defer responseStream.Close() - - body, err := ioutil.ReadAll(responseStream) - if err != nil { - return "", err - } - - var manifestAddress string - if err = json.Unmarshal(body, &manifestAddress); err != nil { - return "", err - } - return manifestAddress, nil -} - -// UpdateFeed allows you to set a new version of your content -func (c *Client) UpdateFeed(request *feed.Request) error { - _, err := c.updateFeed(request, false) - return err -} - -func (c *Client) updateFeed(request *feed.Request, createManifest bool) (io.ReadCloser, error) { - URL, err := url.Parse(c.Gateway) - if err != nil { - return nil, err - } - URL.Path = "/bzz-feed:/" - values := URL.Query() - body := request.AppendValues(values) - if createManifest { - values.Set("manifest", "1") - } - URL.RawQuery = values.Encode() - - req, err := http.NewRequest("POST", URL.String(), bytes.NewBuffer(body)) - if err != nil { - return nil, err - } - - res, err := http.DefaultClient.Do(req) - if err != nil { - return nil, err - } - - return res.Body, nil -} - -// QueryFeed returns a byte stream with the raw content of the feed update -// manifestAddressOrDomain is the address you obtained in CreateFeedWithManifest or an ENS domain whose Resolver -// points to that address -func (c *Client) QueryFeed(query *feed.Query, manifestAddressOrDomain string) (io.ReadCloser, error) { - return c.queryFeed(query, manifestAddressOrDomain, false) -} - -// queryFeed returns a byte stream with the raw content of the feed update -// manifestAddressOrDomain is the address you obtained in CreateFeedWithManifest or an ENS domain whose Resolver -// points to that address -// meta set to true will instruct the node return feed metainformation instead -func (c *Client) queryFeed(query *feed.Query, manifestAddressOrDomain string, meta bool) (io.ReadCloser, error) { - URL, err := url.Parse(c.Gateway) - if err != nil { - return nil, err - } - URL.Path = "/bzz-feed:/" + manifestAddressOrDomain - values := URL.Query() - if query != nil { - query.AppendValues(values) //adds query parameters - } - if meta { - values.Set("meta", "1") - } - URL.RawQuery = values.Encode() - res, err := http.Get(URL.String()) - if err != nil { - return nil, err - } - - if res.StatusCode != http.StatusOK { - if res.StatusCode == http.StatusNotFound { - return nil, ErrNoFeedUpdatesFound - } - errorMessageBytes, err := ioutil.ReadAll(res.Body) - var errorMessage string - if err != nil { - errorMessage = "cannot retrieve error message: " + err.Error() - } else { - errorMessage = string(errorMessageBytes) - } - return nil, fmt.Errorf("Error retrieving feed updates: %s", errorMessage) - } - - return res.Body, nil -} - -// GetFeedRequest returns a structure that describes the referenced feed status -// manifestAddressOrDomain is the address you obtained in CreateFeedWithManifest or an ENS domain whose Resolver -// points to that address -func (c *Client) GetFeedRequest(query *feed.Query, manifestAddressOrDomain string) (*feed.Request, error) { - - responseStream, err := c.queryFeed(query, manifestAddressOrDomain, true) - if err != nil { - return nil, err - } - defer responseStream.Close() - - body, err := ioutil.ReadAll(responseStream) - if err != nil { - return nil, err - } - - var metadata feed.Request - if err := metadata.UnmarshalJSON(body); err != nil { - return nil, err - } - return &metadata, nil -} - -func GetClientTrace(traceMsg, metricPrefix, ruid string, tn *time.Time) *httptrace.ClientTrace { - trace := &httptrace.ClientTrace{ - GetConn: func(_ string) { - log.Trace(traceMsg+" - http get", "event", "GetConn", "ruid", ruid) - metrics.GetOrRegisterResettingTimer(metricPrefix+".getconn", nil).Update(time.Since(*tn)) - }, - GotConn: func(_ httptrace.GotConnInfo) { - log.Trace(traceMsg+" - http get", "event", "GotConn", "ruid", ruid) - metrics.GetOrRegisterResettingTimer(metricPrefix+".gotconn", nil).Update(time.Since(*tn)) - }, - PutIdleConn: func(err error) { - log.Trace(traceMsg+" - http get", "event", "PutIdleConn", "ruid", ruid, "err", err) - metrics.GetOrRegisterResettingTimer(metricPrefix+".putidle", nil).Update(time.Since(*tn)) - }, - GotFirstResponseByte: func() { - log.Trace(traceMsg+" - http get", "event", "GotFirstResponseByte", "ruid", ruid) - metrics.GetOrRegisterResettingTimer(metricPrefix+".firstbyte", nil).Update(time.Since(*tn)) - }, - Got100Continue: func() { - log.Trace(traceMsg, "event", "Got100Continue", "ruid", ruid) - metrics.GetOrRegisterResettingTimer(metricPrefix+".got100continue", nil).Update(time.Since(*tn)) - }, - DNSStart: func(_ httptrace.DNSStartInfo) { - log.Trace(traceMsg, "event", "DNSStart", "ruid", ruid) - metrics.GetOrRegisterResettingTimer(metricPrefix+".dnsstart", nil).Update(time.Since(*tn)) - }, - DNSDone: func(_ httptrace.DNSDoneInfo) { - log.Trace(traceMsg, "event", "DNSDone", "ruid", ruid) - metrics.GetOrRegisterResettingTimer(metricPrefix+".dnsdone", nil).Update(time.Since(*tn)) - }, - ConnectStart: func(network, addr string) { - log.Trace(traceMsg, "event", "ConnectStart", "ruid", ruid, "network", network, "addr", addr) - metrics.GetOrRegisterResettingTimer(metricPrefix+".connectstart", nil).Update(time.Since(*tn)) - }, - ConnectDone: func(network, addr string, err error) { - log.Trace(traceMsg, "event", "ConnectDone", "ruid", ruid, "network", network, "addr", addr, "err", err) - metrics.GetOrRegisterResettingTimer(metricPrefix+".connectdone", nil).Update(time.Since(*tn)) - }, - WroteHeaders: func() { - log.Trace(traceMsg, "event", "WroteHeaders(request)", "ruid", ruid) - metrics.GetOrRegisterResettingTimer(metricPrefix+".wroteheaders", nil).Update(time.Since(*tn)) - }, - Wait100Continue: func() { - log.Trace(traceMsg, "event", "Wait100Continue", "ruid", ruid) - metrics.GetOrRegisterResettingTimer(metricPrefix+".wait100continue", nil).Update(time.Since(*tn)) - }, - WroteRequest: func(_ httptrace.WroteRequestInfo) { - log.Trace(traceMsg, "event", "WroteRequest", "ruid", ruid) - metrics.GetOrRegisterResettingTimer(metricPrefix+".wroterequest", nil).Update(time.Since(*tn)) - }, - } - return trace -} diff --git a/swarm/api/client/client_test.go b/swarm/api/client/client_test.go deleted file mode 100644 index cbaf60e6c14c..000000000000 --- a/swarm/api/client/client_test.go +++ /dev/null @@ -1,589 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package client - -import ( - "bytes" - "io/ioutil" - "os" - "path/filepath" - "reflect" - "sort" - "testing" - - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/ubiq/go-ubiq/swarm/storage/feed/lookup" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/swarm/api" - swarmhttp "github.com/ubiq/go-ubiq/swarm/api/http" - "github.com/ubiq/go-ubiq/swarm/storage/feed" -) - -func serverFunc(api *api.API) swarmhttp.TestServer { - return swarmhttp.NewServer(api, "") -} - -// TestClientUploadDownloadRaw test uploading and downloading raw data to swarm -func TestClientUploadDownloadRaw(t *testing.T) { - testClientUploadDownloadRaw(false, t) -} -func TestClientUploadDownloadRawEncrypted(t *testing.T) { - testClientUploadDownloadRaw(true, t) -} - -func testClientUploadDownloadRaw(toEncrypt bool, t *testing.T) { - srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - client := NewClient(srv.URL) - - // upload some raw data - data := []byte("foo123") - hash, err := client.UploadRaw(bytes.NewReader(data), int64(len(data)), toEncrypt) - if err != nil { - t.Fatal(err) - } - - // check we can download the same data - res, isEncrypted, err := client.DownloadRaw(hash) - if err != nil { - t.Fatal(err) - } - if isEncrypted != toEncrypt { - t.Fatalf("Expected encyption status %v got %v", toEncrypt, isEncrypted) - } - defer res.Close() - gotData, err := ioutil.ReadAll(res) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(gotData, data) { - t.Fatalf("expected downloaded data to be %q, got %q", data, gotData) - } -} - -// TestClientUploadDownloadFiles test uploading and downloading files to swarm -// manifests -func TestClientUploadDownloadFiles(t *testing.T) { - testClientUploadDownloadFiles(false, t) -} - -func TestClientUploadDownloadFilesEncrypted(t *testing.T) { - testClientUploadDownloadFiles(true, t) -} - -func testClientUploadDownloadFiles(toEncrypt bool, t *testing.T) { - srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - client := NewClient(srv.URL) - upload := func(manifest, path string, data []byte) string { - file := &File{ - ReadCloser: ioutil.NopCloser(bytes.NewReader(data)), - ManifestEntry: api.ManifestEntry{ - Path: path, - ContentType: "text/plain", - Size: int64(len(data)), - }, - } - hash, err := client.Upload(file, manifest, toEncrypt) - if err != nil { - t.Fatal(err) - } - return hash - } - checkDownload := func(manifest, path string, expected []byte) { - file, err := client.Download(manifest, path) - if err != nil { - t.Fatal(err) - } - defer file.Close() - if file.Size != int64(len(expected)) { - t.Fatalf("expected downloaded file to be %d bytes, got %d", len(expected), file.Size) - } - if file.ContentType != "text/plain" { - t.Fatalf("expected downloaded file to have type %q, got %q", "text/plain", file.ContentType) - } - data, err := ioutil.ReadAll(file) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(data, expected) { - t.Fatalf("expected downloaded data to be %q, got %q", expected, data) - } - } - - // upload a file to the root of a manifest - rootData := []byte("some-data") - rootHash := upload("", "", rootData) - - // check we can download the root file - checkDownload(rootHash, "", rootData) - - // upload another file to the same manifest - otherData := []byte("some-other-data") - newHash := upload(rootHash, "some/other/path", otherData) - - // check we can download both files from the new manifest - checkDownload(newHash, "", rootData) - checkDownload(newHash, "some/other/path", otherData) - - // replace the root file with different data - newHash = upload(newHash, "", otherData) - - // check both files have the other data - checkDownload(newHash, "", otherData) - checkDownload(newHash, "some/other/path", otherData) -} - -var testDirFiles = []string{ - "file1.txt", - "file2.txt", - "dir1/file3.txt", - "dir1/file4.txt", - "dir2/file5.txt", - "dir2/dir3/file6.txt", - "dir2/dir4/file7.txt", - "dir2/dir4/file8.txt", -} - -func newTestDirectory(t *testing.T) string { - dir, err := ioutil.TempDir("", "swarm-client-test") - if err != nil { - t.Fatal(err) - } - - for _, file := range testDirFiles { - path := filepath.Join(dir, file) - if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { - os.RemoveAll(dir) - t.Fatalf("error creating dir for %s: %s", path, err) - } - if err := ioutil.WriteFile(path, []byte(file), 0644); err != nil { - os.RemoveAll(dir) - t.Fatalf("error writing file %s: %s", path, err) - } - } - - return dir -} - -// TestClientUploadDownloadDirectory tests uploading and downloading a -// directory of files to a swarm manifest -func TestClientUploadDownloadDirectory(t *testing.T) { - srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - dir := newTestDirectory(t) - defer os.RemoveAll(dir) - - // upload the directory - client := NewClient(srv.URL) - defaultPath := testDirFiles[0] - hash, err := client.UploadDirectory(dir, defaultPath, "", false) - if err != nil { - t.Fatalf("error uploading directory: %s", err) - } - - // check we can download the individual files - checkDownloadFile := func(path string, expected []byte) { - file, err := client.Download(hash, path) - if err != nil { - t.Fatal(err) - } - defer file.Close() - data, err := ioutil.ReadAll(file) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(data, expected) { - t.Fatalf("expected data to be %q, got %q", expected, data) - } - } - for _, file := range testDirFiles { - checkDownloadFile(file, []byte(file)) - } - - // check we can download the default path - checkDownloadFile("", []byte(testDirFiles[0])) - - // check we can download the directory - tmp, err := ioutil.TempDir("", "swarm-client-test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(tmp) - if err := client.DownloadDirectory(hash, "", tmp, ""); err != nil { - t.Fatal(err) - } - for _, file := range testDirFiles { - data, err := ioutil.ReadFile(filepath.Join(tmp, file)) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(data, []byte(file)) { - t.Fatalf("expected data to be %q, got %q", file, data) - } - } -} - -// TestClientFileList tests listing files in a swarm manifest -func TestClientFileList(t *testing.T) { - testClientFileList(false, t) -} - -func TestClientFileListEncrypted(t *testing.T) { - testClientFileList(true, t) -} - -func testClientFileList(toEncrypt bool, t *testing.T) { - srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - dir := newTestDirectory(t) - defer os.RemoveAll(dir) - - client := NewClient(srv.URL) - hash, err := client.UploadDirectory(dir, "", "", toEncrypt) - if err != nil { - t.Fatalf("error uploading directory: %s", err) - } - - ls := func(prefix string) []string { - list, err := client.List(hash, prefix, "") - if err != nil { - t.Fatal(err) - } - paths := make([]string, 0, len(list.CommonPrefixes)+len(list.Entries)) - paths = append(paths, list.CommonPrefixes...) - for _, entry := range list.Entries { - paths = append(paths, entry.Path) - } - sort.Strings(paths) - return paths - } - - tests := map[string][]string{ - "": {"dir1/", "dir2/", "file1.txt", "file2.txt"}, - "file": {"file1.txt", "file2.txt"}, - "file1": {"file1.txt"}, - "file2.txt": {"file2.txt"}, - "file12": {}, - "dir": {"dir1/", "dir2/"}, - "dir1": {"dir1/"}, - "dir1/": {"dir1/file3.txt", "dir1/file4.txt"}, - "dir1/file": {"dir1/file3.txt", "dir1/file4.txt"}, - "dir1/file3.txt": {"dir1/file3.txt"}, - "dir1/file34": {}, - "dir2/": {"dir2/dir3/", "dir2/dir4/", "dir2/file5.txt"}, - "dir2/file": {"dir2/file5.txt"}, - "dir2/dir": {"dir2/dir3/", "dir2/dir4/"}, - "dir2/dir3/": {"dir2/dir3/file6.txt"}, - "dir2/dir4/": {"dir2/dir4/file7.txt", "dir2/dir4/file8.txt"}, - "dir2/dir4/file": {"dir2/dir4/file7.txt", "dir2/dir4/file8.txt"}, - "dir2/dir4/file7.txt": {"dir2/dir4/file7.txt"}, - "dir2/dir4/file78": {}, - } - for prefix, expected := range tests { - actual := ls(prefix) - if !reflect.DeepEqual(actual, expected) { - t.Fatalf("expected prefix %q to return %v, got %v", prefix, expected, actual) - } - } -} - -// TestClientMultipartUpload tests uploading files to swarm using a multipart -// upload -func TestClientMultipartUpload(t *testing.T) { - srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - // define an uploader which uploads testDirFiles with some data - data := []byte("some-data") - uploader := UploaderFunc(func(upload UploadFn) error { - for _, name := range testDirFiles { - file := &File{ - ReadCloser: ioutil.NopCloser(bytes.NewReader(data)), - ManifestEntry: api.ManifestEntry{ - Path: name, - ContentType: "text/plain", - Size: int64(len(data)), - }, - } - if err := upload(file); err != nil { - return err - } - } - return nil - }) - - // upload the files as a multipart upload - client := NewClient(srv.URL) - hash, err := client.MultipartUpload("", uploader) - if err != nil { - t.Fatal(err) - } - - // check we can download the individual files - checkDownloadFile := func(path string) { - file, err := client.Download(hash, path) - if err != nil { - t.Fatal(err) - } - defer file.Close() - gotData, err := ioutil.ReadAll(file) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(gotData, data) { - t.Fatalf("expected data to be %q, got %q", data, gotData) - } - } - for _, file := range testDirFiles { - checkDownloadFile(file) - } -} - -func newTestSigner() (*feed.GenericSigner, error) { - privKey, err := crypto.HexToECDSA("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - if err != nil { - return nil, err - } - return feed.NewGenericSigner(privKey), nil -} - -// Test the transparent resolving of feed updates with bzz:// scheme -// -// First upload data to bzz:, and store the Swarm hash to the resulting manifest in a feed update. -// This effectively uses a feed to store a pointer to content rather than the content itself -// Retrieving the update with the Swarm hash should return the manifest pointing directly to the data -// and raw retrieve of that hash should return the data -func TestClientBzzWithFeed(t *testing.T) { - - signer, _ := newTestSigner() - - // Initialize a Swarm test server - srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil) - swarmClient := NewClient(srv.URL) - defer srv.Close() - - // put together some data for our test: - dataBytes := []byte(` - // - // Create some data our manifest will point to. Data that could be very big and wouldn't fit in a feed update. - // So what we are going to do is upload it to Swarm bzz:// and obtain a **manifest hash** pointing to it: - // - // MANIFEST HASH --> DATA - // - // Then, we store that **manifest hash** into a Swarm Feed update. Once we have done this, - // we can use the **feed manifest hash** in bzz:// instead, this way: bzz://feed-manifest-hash. - // - // FEED MANIFEST HASH --> MANIFEST HASH --> DATA - // - // Given that we can update the feed at any time with a new **manifest hash** but the **feed manifest hash** - // stays constant, we have effectively created a fixed address to changing content. (Applause) - // - // FEED MANIFEST HASH (the same) --> MANIFEST HASH(2) --> DATA(2) - // - `) - - // Create a virtual File out of memory containing the above data - f := &File{ - ReadCloser: ioutil.NopCloser(bytes.NewReader(dataBytes)), - ManifestEntry: api.ManifestEntry{ - ContentType: "text/plain", - Mode: 0660, - Size: int64(len(dataBytes)), - }, - } - - // upload data to bzz:// and retrieve the content-addressed manifest hash, hex-encoded. - manifestAddressHex, err := swarmClient.Upload(f, "", false) - if err != nil { - t.Fatalf("Error creating manifest: %s", err) - } - - // convert the hex-encoded manifest hash to a 32-byte slice - manifestAddress := common.FromHex(manifestAddressHex) - - if len(manifestAddress) != storage.AddressLength { - t.Fatalf("Something went wrong. Got a hash of an unexpected length. Expected %d bytes. Got %d", storage.AddressLength, len(manifestAddress)) - } - - // Now create a **feed manifest**. For that, we need a topic: - topic, _ := feed.NewTopic("interesting topic indeed", nil) - - // Build a feed request to update data - request := feed.NewFirstRequest(topic) - - // Put the 32-byte address of the manifest into the feed update - request.SetData(manifestAddress) - - // Sign the update - if err := request.Sign(signer); err != nil { - t.Fatalf("Error signing update: %s", err) - } - - // Publish the update and at the same time request a **feed manifest** to be created - feedManifestAddressHex, err := swarmClient.CreateFeedWithManifest(request) - if err != nil { - t.Fatalf("Error creating feed manifest: %s", err) - } - - // Check we have received the exact **feed manifest** to be expected - // given the topic and user signing the updates: - correctFeedManifestAddrHex := "747c402e5b9dc715a25a4393147512167bab018a007fad7cdcd9adc7fce1ced2" - if feedManifestAddressHex != correctFeedManifestAddrHex { - t.Fatalf("Response feed manifest mismatch, expected '%s', got '%s'", correctFeedManifestAddrHex, feedManifestAddressHex) - } - - // Check we get a not found error when trying to get feed updates with a made-up manifest - _, err = swarmClient.QueryFeed(nil, "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb") - if err != ErrNoFeedUpdatesFound { - t.Fatalf("Expected to receive ErrNoFeedUpdatesFound error. Got: %s", err) - } - - // If we query the feed directly we should get **manifest hash** back: - reader, err := swarmClient.QueryFeed(nil, correctFeedManifestAddrHex) - if err != nil { - t.Fatalf("Error retrieving feed updates: %s", err) - } - defer reader.Close() - gotData, err := ioutil.ReadAll(reader) - if err != nil { - t.Fatal(err) - } - - //Check that indeed the **manifest hash** is retrieved - if !bytes.Equal(manifestAddress, gotData) { - t.Fatalf("Expected: %v, got %v", manifestAddress, gotData) - } - - // Now the final test we were looking for: Use bzz:// and that should resolve all manifests - // and return the original data directly: - f, err = swarmClient.Download(feedManifestAddressHex, "") - if err != nil { - t.Fatal(err) - } - gotData, err = ioutil.ReadAll(f) - if err != nil { - t.Fatal(err) - } - - // Check that we get back the original data: - if !bytes.Equal(dataBytes, gotData) { - t.Fatalf("Expected: %v, got %v", manifestAddress, gotData) - } -} - -// TestClientCreateUpdateFeed will check that feeds can be created and updated via the HTTP client. -func TestClientCreateUpdateFeed(t *testing.T) { - - signer, _ := newTestSigner() - - srv := swarmhttp.NewTestSwarmServer(t, serverFunc, nil) - client := NewClient(srv.URL) - defer srv.Close() - - // set raw data for the feed update - databytes := []byte("En un lugar de La Mancha, de cuyo nombre no quiero acordarme...") - - // our feed topic name - topic, _ := feed.NewTopic("El Quijote", nil) - createRequest := feed.NewFirstRequest(topic) - - createRequest.SetData(databytes) - if err := createRequest.Sign(signer); err != nil { - t.Fatalf("Error signing update: %s", err) - } - - feedManifestHash, err := client.CreateFeedWithManifest(createRequest) - if err != nil { - t.Fatal(err) - } - - correctManifestAddrHex := "0e9b645ebc3da167b1d56399adc3276f7a08229301b72a03336be0e7d4b71882" - if feedManifestHash != correctManifestAddrHex { - t.Fatalf("Response feed manifest mismatch, expected '%s', got '%s'", correctManifestAddrHex, feedManifestHash) - } - - reader, err := client.QueryFeed(nil, correctManifestAddrHex) - if err != nil { - t.Fatalf("Error retrieving feed updates: %s", err) - } - defer reader.Close() - gotData, err := ioutil.ReadAll(reader) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(databytes, gotData) { - t.Fatalf("Expected: %v, got %v", databytes, gotData) - } - - // define different data - databytes = []byte("... no ha mucho tiempo que vivía un hidalgo de los de lanza en astillero ...") - - updateRequest, err := client.GetFeedRequest(nil, correctManifestAddrHex) - if err != nil { - t.Fatalf("Error retrieving update request template: %s", err) - } - - updateRequest.SetData(databytes) - if err := updateRequest.Sign(signer); err != nil { - t.Fatalf("Error signing update: %s", err) - } - - if err = client.UpdateFeed(updateRequest); err != nil { - t.Fatalf("Error updating feed: %s", err) - } - - reader, err = client.QueryFeed(nil, correctManifestAddrHex) - if err != nil { - t.Fatalf("Error retrieving feed updates: %s", err) - } - defer reader.Close() - gotData, err = ioutil.ReadAll(reader) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(databytes, gotData) { - t.Fatalf("Expected: %v, got %v", databytes, gotData) - } - - // now try retrieving feed updates without a manifest - - fd := &feed.Feed{ - Topic: topic, - User: signer.Address(), - } - - lookupParams := feed.NewQueryLatest(fd, lookup.NoClue) - reader, err = client.QueryFeed(lookupParams, "") - if err != nil { - t.Fatalf("Error retrieving feed updates: %s", err) - } - defer reader.Close() - gotData, err = ioutil.ReadAll(reader) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(databytes, gotData) { - t.Fatalf("Expected: %v, got %v", databytes, gotData) - } -} diff --git a/swarm/api/config.go b/swarm/api/config.go deleted file mode 100644 index bd02d9d17e35..000000000000 --- a/swarm/api/config.go +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package api - -import ( - "crypto/ecdsa" - "fmt" - "os" - "path/filepath" - "time" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/contracts/ens" - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/network" - "github.com/ubiq/go-ubiq/swarm/pss" - "github.com/ubiq/go-ubiq/swarm/services/swap" - "github.com/ubiq/go-ubiq/swarm/storage" -) - -const ( - DefaultHTTPListenAddr = "127.0.0.1" - DefaultHTTPPort = "8500" -) - -// separate bzz directories -// allow several bzz nodes running in parallel -type Config struct { - // serialised/persisted fields - *storage.FileStoreParams - *storage.LocalStoreParams - *network.HiveParams - Swap *swap.LocalProfile - Pss *pss.PssParams - //*network.SyncParams - Contract common.Address - EnsRoot common.Address - EnsAPIs []string - Path string - ListenAddr string - Port string - PublicKey string - BzzKey string - NodeID string - NetworkID uint64 - SwapEnabled bool - SyncEnabled bool - SyncingSkipCheck bool - DeliverySkipCheck bool - MaxStreamPeerServers int - LightNodeEnabled bool - BootnodeMode bool - SyncUpdateDelay time.Duration - SwapAPI string - Cors string - BzzAccount string - GlobalStoreAPI string - privateKey *ecdsa.PrivateKey -} - -//create a default config with all parameters to set to defaults -func NewConfig() (c *Config) { - - c = &Config{ - LocalStoreParams: storage.NewDefaultLocalStoreParams(), - FileStoreParams: storage.NewFileStoreParams(), - HiveParams: network.NewHiveParams(), - //SyncParams: network.NewDefaultSyncParams(), - Swap: swap.NewDefaultSwapParams(), - Pss: pss.NewPssParams(), - ListenAddr: DefaultHTTPListenAddr, - Port: DefaultHTTPPort, - Path: node.DefaultDataDir(), - EnsAPIs: nil, - EnsRoot: ens.TestNetAddress, - NetworkID: network.DefaultNetworkID, - SwapEnabled: false, - SyncEnabled: true, - SyncingSkipCheck: false, - MaxStreamPeerServers: 10000, - DeliverySkipCheck: true, - SyncUpdateDelay: 15 * time.Second, - SwapAPI: "", - } - - return -} - -//some config params need to be initialized after the complete -//config building phase is completed (e.g. due to overriding flags) -func (c *Config) Init(prvKey *ecdsa.PrivateKey) { - - address := crypto.PubkeyToAddress(prvKey.PublicKey) - c.Path = filepath.Join(c.Path, "bzz-"+common.Bytes2Hex(address.Bytes())) - err := os.MkdirAll(c.Path, os.ModePerm) - if err != nil { - log.Error(fmt.Sprintf("Error creating root swarm data directory: %v", err)) - return - } - - pubkey := crypto.FromECDSAPub(&prvKey.PublicKey) - pubkeyhex := common.ToHex(pubkey) - keyhex := crypto.Keccak256Hash(pubkey).Hex() - - c.PublicKey = pubkeyhex - c.BzzKey = keyhex - c.NodeID = enode.PubkeyToIDV4(&prvKey.PublicKey).String() - - if c.SwapEnabled { - c.Swap.Init(c.Contract, prvKey) - } - - c.privateKey = prvKey - c.LocalStoreParams.Init(c.Path) - c.LocalStoreParams.BaseKey = common.FromHex(keyhex) - - c.Pss = c.Pss.WithPrivateKey(c.privateKey) -} - -func (c *Config) ShiftPrivateKey() (privKey *ecdsa.PrivateKey) { - if c.privateKey != nil { - privKey = c.privateKey - c.privateKey = nil - } - return privKey -} diff --git a/swarm/api/config_test.go b/swarm/api/config_test.go deleted file mode 100644 index 653b2f610729..000000000000 --- a/swarm/api/config_test.go +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package api - -import ( - "reflect" - "testing" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/crypto" -) - -func TestConfig(t *testing.T) { - - var hexprvkey = "65138b2aa745041b372153550584587da326ab440576b2a1191dd95cee30039c" - - prvkey, err := crypto.HexToECDSA(hexprvkey) - if err != nil { - t.Fatalf("failed to load private key: %v", err) - } - - one := NewConfig() - two := NewConfig() - - one.LocalStoreParams = two.LocalStoreParams - if equal := reflect.DeepEqual(one, two); !equal { - t.Fatal("Two default configs are not equal") - } - - one.Init(prvkey) - - //the init function should set the following fields - if one.BzzKey == "" { - t.Fatal("Expected BzzKey to be set") - } - if one.PublicKey == "" { - t.Fatal("Expected PublicKey to be set") - } - if one.Swap.PayProfile.Beneficiary == (common.Address{}) && one.SwapEnabled { - t.Fatal("Failed to correctly initialize SwapParams") - } - if one.ChunkDbPath == one.Path { - t.Fatal("Failed to correctly initialize StoreParams") - } -} diff --git a/swarm/api/encrypt.go b/swarm/api/encrypt.go deleted file mode 100644 index c5bbeeb24f6e..000000000000 --- a/swarm/api/encrypt.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package api - -import ( - "encoding/binary" - "errors" - - "github.com/ubiq/go-ubiq/swarm/storage/encryption" - "golang.org/x/crypto/sha3" -) - -type RefEncryption struct { - refSize int - span []byte -} - -func NewRefEncryption(refSize int) *RefEncryption { - span := make([]byte, 8) - binary.LittleEndian.PutUint64(span, uint64(refSize)) - return &RefEncryption{ - refSize: refSize, - span: span, - } -} - -func (re *RefEncryption) Encrypt(ref []byte, key []byte) ([]byte, error) { - spanEncryption := encryption.New(key, 0, uint32(re.refSize/32), sha3.NewLegacyKeccak256) - encryptedSpan, err := spanEncryption.Encrypt(re.span) - if err != nil { - return nil, err - } - dataEncryption := encryption.New(key, re.refSize, 0, sha3.NewLegacyKeccak256) - encryptedData, err := dataEncryption.Encrypt(ref) - if err != nil { - return nil, err - } - encryptedRef := make([]byte, len(ref)+8) - copy(encryptedRef[:8], encryptedSpan) - copy(encryptedRef[8:], encryptedData) - - return encryptedRef, nil -} - -func (re *RefEncryption) Decrypt(ref []byte, key []byte) ([]byte, error) { - spanEncryption := encryption.New(key, 0, uint32(re.refSize/32), sha3.NewLegacyKeccak256) - decryptedSpan, err := spanEncryption.Decrypt(ref[:8]) - if err != nil { - return nil, err - } - - size := binary.LittleEndian.Uint64(decryptedSpan) - if size != uint64(len(ref)-8) { - return nil, errors.New("invalid span in encrypted reference") - } - - dataEncryption := encryption.New(key, re.refSize, 0, sha3.NewLegacyKeccak256) - decryptedRef, err := dataEncryption.Decrypt(ref[8:]) - if err != nil { - return nil, err - } - - return decryptedRef, nil -} diff --git a/swarm/api/filesystem.go b/swarm/api/filesystem.go deleted file mode 100644 index 5526854311da..000000000000 --- a/swarm/api/filesystem.go +++ /dev/null @@ -1,292 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package api - -import ( - "bufio" - "context" - "fmt" - "io" - "os" - "path" - "path/filepath" - "sync" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/storage" -) - -const maxParallelFiles = 5 - -type FileSystem struct { - api *API -} - -func NewFileSystem(api *API) *FileSystem { - return &FileSystem{api} -} - -// Upload replicates a local directory as a manifest file and uploads it -// using FileStore store -// This function waits the chunks to be stored. -// TODO: localpath should point to a manifest -// -// DEPRECATED: Use the HTTP API instead -func (fs *FileSystem) Upload(lpath, index string, toEncrypt bool) (string, error) { - var list []*manifestTrieEntry - localpath, err := filepath.Abs(filepath.Clean(lpath)) - if err != nil { - return "", err - } - - f, err := os.Open(localpath) - if err != nil { - return "", err - } - stat, err := f.Stat() - if err != nil { - return "", err - } - - var start int - if stat.IsDir() { - start = len(localpath) - log.Debug(fmt.Sprintf("uploading '%s'", localpath)) - err = filepath.Walk(localpath, func(path string, info os.FileInfo, err error) error { - if (err == nil) && !info.IsDir() { - if len(path) <= start { - return fmt.Errorf("Path is too short") - } - if path[:start] != localpath { - return fmt.Errorf("Path prefix of '%s' does not match localpath '%s'", path, localpath) - } - entry := newManifestTrieEntry(&ManifestEntry{Path: filepath.ToSlash(path)}, nil) - list = append(list, entry) - } - return err - }) - if err != nil { - return "", err - } - } else { - dir := filepath.Dir(localpath) - start = len(dir) - if len(localpath) <= start { - return "", fmt.Errorf("Path is too short") - } - if localpath[:start] != dir { - return "", fmt.Errorf("Path prefix of '%s' does not match dir '%s'", localpath, dir) - } - entry := newManifestTrieEntry(&ManifestEntry{Path: filepath.ToSlash(localpath)}, nil) - list = append(list, entry) - } - - errors := make([]error, len(list)) - sem := make(chan bool, maxParallelFiles) - defer close(sem) - - for i, entry := range list { - sem <- true - go func(i int, entry *manifestTrieEntry) { - defer func() { <-sem }() - - f, err := os.Open(entry.Path) - if err != nil { - errors[i] = err - return - } - defer f.Close() - - stat, err := f.Stat() - if err != nil { - errors[i] = err - return - } - - var hash storage.Address - var wait func(context.Context) error - ctx := context.TODO() - hash, wait, err = fs.api.fileStore.Store(ctx, f, stat.Size(), toEncrypt) - if err != nil { - errors[i] = err - return - } - if hash != nil { - list[i].Hash = hash.Hex() - } - if err := wait(ctx); err != nil { - errors[i] = err - return - } - - list[i].ContentType, err = DetectContentType(f.Name(), f) - if err != nil { - errors[i] = err - return - } - - }(i, entry) - } - for i := 0; i < cap(sem); i++ { - sem <- true - } - - trie := &manifestTrie{ - fileStore: fs.api.fileStore, - } - quitC := make(chan bool) - for i, entry := range list { - if errors[i] != nil { - return "", errors[i] - } - entry.Path = RegularSlashes(entry.Path[start:]) - if entry.Path == index { - ientry := newManifestTrieEntry(&ManifestEntry{ - ContentType: entry.ContentType, - }, nil) - ientry.Hash = entry.Hash - trie.addEntry(ientry, quitC) - } - trie.addEntry(entry, quitC) - } - - err2 := trie.recalcAndStore() - var hs string - if err2 == nil { - hs = trie.ref.Hex() - } - return hs, err2 -} - -// Download replicates the manifest basePath structure on the local filesystem -// under localpath -// -// DEPRECATED: Use the HTTP API instead -func (fs *FileSystem) Download(bzzpath, localpath string) error { - lpath, err := filepath.Abs(filepath.Clean(localpath)) - if err != nil { - return err - } - err = os.MkdirAll(lpath, os.ModePerm) - if err != nil { - return err - } - - //resolving host and port - uri, err := Parse(path.Join("bzz:/", bzzpath)) - if err != nil { - return err - } - addr, err := fs.api.Resolve(context.TODO(), uri.Addr) - if err != nil { - return err - } - path := uri.Path - - if len(path) > 0 { - path += "/" - } - - quitC := make(chan bool) - trie, err := loadManifest(context.TODO(), fs.api.fileStore, addr, quitC, NOOPDecrypt) - if err != nil { - log.Warn(fmt.Sprintf("fs.Download: loadManifestTrie error: %v", err)) - return err - } - - type downloadListEntry struct { - addr storage.Address - path string - } - - var list []*downloadListEntry - var mde error - - prevPath := lpath - err = trie.listWithPrefix(path, quitC, func(entry *manifestTrieEntry, suffix string) { - log.Trace(fmt.Sprintf("fs.Download: %#v", entry)) - - addr = common.Hex2Bytes(entry.Hash) - path := lpath + "/" + suffix - dir := filepath.Dir(path) - if dir != prevPath { - mde = os.MkdirAll(dir, os.ModePerm) - prevPath = dir - } - if (mde == nil) && (path != dir+"/") { - list = append(list, &downloadListEntry{addr: addr, path: path}) - } - }) - if err != nil { - return err - } - - wg := sync.WaitGroup{} - errC := make(chan error) - done := make(chan bool, maxParallelFiles) - for i, entry := range list { - select { - case done <- true: - wg.Add(1) - case <-quitC: - return fmt.Errorf("aborted") - } - go func(i int, entry *downloadListEntry) { - defer wg.Done() - err := retrieveToFile(quitC, fs.api.fileStore, entry.addr, entry.path) - if err != nil { - select { - case errC <- err: - case <-quitC: - } - return - } - <-done - }(i, entry) - } - go func() { - wg.Wait() - close(errC) - }() - select { - case err = <-errC: - return err - case <-quitC: - return fmt.Errorf("aborted") - } -} - -func retrieveToFile(quitC chan bool, fileStore *storage.FileStore, addr storage.Address, path string) error { - f, err := os.Create(path) // TODO: basePath separators - if err != nil { - return err - } - reader, _ := fileStore.Retrieve(context.TODO(), addr) - writer := bufio.NewWriter(f) - size, err := reader.Size(context.TODO(), quitC) - if err != nil { - return err - } - if _, err = io.CopyN(writer, reader, size); err != nil { - return err - } - if err := writer.Flush(); err != nil { - return err - } - return f.Close() -} diff --git a/swarm/api/filesystem_test.go b/swarm/api/filesystem_test.go deleted file mode 100644 index ec7caf45bb03..000000000000 --- a/swarm/api/filesystem_test.go +++ /dev/null @@ -1,199 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package api - -import ( - "bytes" - "context" - "io/ioutil" - "os" - "path/filepath" - "testing" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/swarm/storage" -) - -var testDownloadDir, _ = ioutil.TempDir(os.TempDir(), "bzz-test") - -func testFileSystem(t *testing.T, f func(*FileSystem, bool)) { - testAPI(t, func(api *API, toEncrypt bool) { - f(NewFileSystem(api), toEncrypt) - }) -} - -func readPath(t *testing.T, parts ...string) string { - file := filepath.Join(parts...) - content, err := ioutil.ReadFile(file) - - if err != nil { - t.Fatalf("unexpected error reading '%v': %v", file, err) - } - return string(content) -} - -func TestApiDirUpload0(t *testing.T) { - testFileSystem(t, func(fs *FileSystem, toEncrypt bool) { - api := fs.api - bzzhash, err := fs.Upload(filepath.Join("testdata", "test0"), "", toEncrypt) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - content := readPath(t, "testdata", "test0", "index.html") - resp := testGet(t, api, bzzhash, "index.html") - exp := expResponse(content, "text/html; charset=utf-8", 0) - checkResponse(t, resp, exp) - - content = readPath(t, "testdata", "test0", "index.css") - resp = testGet(t, api, bzzhash, "index.css") - exp = expResponse(content, "text/css; charset=utf-8", 0) - checkResponse(t, resp, exp) - - addr := storage.Address(common.Hex2Bytes(bzzhash)) - _, _, _, _, err = api.Get(context.TODO(), NOOPDecrypt, addr, "") - if err == nil { - t.Fatalf("expected error: %v", err) - } - - downloadDir := filepath.Join(testDownloadDir, "test0") - defer os.RemoveAll(downloadDir) - err = fs.Download(bzzhash, downloadDir) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - newbzzhash, err := fs.Upload(downloadDir, "", toEncrypt) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - // TODO: currently the hash is not deterministic in the encrypted case - if !toEncrypt && bzzhash != newbzzhash { - t.Fatalf("download %v reuploaded has incorrect hash, expected %v, got %v", downloadDir, bzzhash, newbzzhash) - } - }) -} - -func TestApiDirUploadModify(t *testing.T) { - testFileSystem(t, func(fs *FileSystem, toEncrypt bool) { - api := fs.api - bzzhash, err := fs.Upload(filepath.Join("testdata", "test0"), "", toEncrypt) - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - - addr := storage.Address(common.Hex2Bytes(bzzhash)) - addr, err = api.Modify(context.TODO(), addr, "index.html", "", "") - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - index, err := ioutil.ReadFile(filepath.Join("testdata", "test0", "index.html")) - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - ctx := context.TODO() - hash, wait, err := api.Store(ctx, bytes.NewReader(index), int64(len(index)), toEncrypt) - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - err = wait(ctx) - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - addr, err = api.Modify(context.TODO(), addr, "index2.html", hash.Hex(), "text/html; charset=utf-8") - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - addr, err = api.Modify(context.TODO(), addr, "img/logo.png", hash.Hex(), "text/html; charset=utf-8") - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - bzzhash = addr.Hex() - - content := readPath(t, "testdata", "test0", "index.html") - resp := testGet(t, api, bzzhash, "index2.html") - exp := expResponse(content, "text/html; charset=utf-8", 0) - checkResponse(t, resp, exp) - - resp = testGet(t, api, bzzhash, "img/logo.png") - exp = expResponse(content, "text/html; charset=utf-8", 0) - checkResponse(t, resp, exp) - - content = readPath(t, "testdata", "test0", "index.css") - resp = testGet(t, api, bzzhash, "index.css") - exp = expResponse(content, "text/css; charset=utf-8", 0) - checkResponse(t, resp, exp) - - _, _, _, _, err = api.Get(context.TODO(), nil, addr, "") - if err == nil { - t.Errorf("expected error: %v", err) - } - }) -} - -func TestApiDirUploadWithRootFile(t *testing.T) { - testFileSystem(t, func(fs *FileSystem, toEncrypt bool) { - api := fs.api - bzzhash, err := fs.Upload(filepath.Join("testdata", "test0"), "index.html", toEncrypt) - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - - content := readPath(t, "testdata", "test0", "index.html") - resp := testGet(t, api, bzzhash, "") - exp := expResponse(content, "text/html; charset=utf-8", 0) - checkResponse(t, resp, exp) - }) -} - -func TestApiFileUpload(t *testing.T) { - testFileSystem(t, func(fs *FileSystem, toEncrypt bool) { - api := fs.api - bzzhash, err := fs.Upload(filepath.Join("testdata", "test0", "index.html"), "", toEncrypt) - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - - content := readPath(t, "testdata", "test0", "index.html") - resp := testGet(t, api, bzzhash, "index.html") - exp := expResponse(content, "text/html; charset=utf-8", 0) - checkResponse(t, resp, exp) - }) -} - -func TestApiFileUploadWithRootFile(t *testing.T) { - testFileSystem(t, func(fs *FileSystem, toEncrypt bool) { - api := fs.api - bzzhash, err := fs.Upload(filepath.Join("testdata", "test0", "index.html"), "index.html", toEncrypt) - if err != nil { - t.Errorf("unexpected error: %v", err) - return - } - - content := readPath(t, "testdata", "test0", "index.html") - resp := testGet(t, api, bzzhash, "") - exp := expResponse(content, "text/html; charset=utf-8", 0) - checkResponse(t, resp, exp) - }) -} diff --git a/swarm/api/gen_mime.go b/swarm/api/gen_mime.go deleted file mode 100644 index d6758b0bfa95..000000000000 --- a/swarm/api/gen_mime.go +++ /dev/null @@ -1,1201 +0,0 @@ -// Code generated by github.com/ubiq/go-ubiq/cmd/swarm/mimegen. DO NOT EDIT. - -package api - -import "mime" - -func init() { - var mimeTypes = map[string]string{ - ".a2l": "application/A2L", - ".aml": "application/AML", - ".ez": "application/andrew-inset", - ".atf": "application/ATF", - ".atfx": "application/ATFX", - ".atxml": "application/ATXML", - ".atom": "application/atom+xml", - ".atomcat": "application/atomcat+xml", - ".atomdeleted": "application/atomdeleted+xml", - ".atomsvc": "application/atomsvc+xml", - ".apxml": "application/auth-policy+xml", - ".xdd": "application/bacnet-xdd+zip", - ".xcs": "application/calendar+xml", - ".cbor": "application/cbor", - ".ccmp": "application/ccmp+xml", - ".ccxml": "application/ccxml+xml", - ".cdfx": "application/CDFX+XML", - ".cdmia": "application/cdmi-capability", - ".cdmic": "application/cdmi-container", - ".cdmid": "application/cdmi-domain", - ".cdmio": "application/cdmi-object", - ".cdmiq": "application/cdmi-queue", - ".cea": "application/CEA", - ".cellml": "application/cellml+xml", - ".cml": "application/cellml+xml", - ".clue": "application/clue_info+xml", - ".cmsc": "application/cms", - ".cpl": "application/cpl+xml", - ".csrattrs": "application/csrattrs", - ".mpd": "application/dash+xml", - ".mpdd": "application/dashdelta", - ".davmount": "application/davmount+xml", - ".dcd": "application/DCD", - ".dcm": "application/dicom", - ".dii": "application/DII", - ".dit": "application/DIT", - ".xmls": "application/dskpp+xml", - ".dssc": "application/dssc+der", - ".xdssc": "application/dssc+xml", - ".dvc": "application/dvcs", - ".es": "application/ecmascript", - ".efi": "application/efi", - ".emma": "application/emma+xml", - ".emotionml": "application/emotionml+xml", - ".epub": "application/epub+zip", - ".exi": "application/exi", - ".finf": "application/fastinfoset", - ".fdt": "application/fdt+xml", - ".pfr": "application/font-tdpfr", - ".geojson": "application/geo+json", - ".gml": "application/gml+xml", - ".gz": "application/gzip", - ".tgz": "application/gzip", - ".stk": "application/hyperstudio", - ".ink": "application/inkml+xml", - ".inkml": "application/inkml+xml", - ".ipfix": "application/ipfix", - ".its": "application/its+xml", - ".js": "application/javascript", - ".jrd": "application/jrd+json", - ".json": "application/json", - ".json-patch": "application/json-patch+json", - ".jsonld": "application/ld+json", - ".lgr": "application/lgr+xml", - ".wlnk": "application/link-format", - ".lostxml": "application/lost+xml", - ".lostsyncxml": "application/lostsync+xml", - ".lxf": "application/LXF", - ".hqx": "application/mac-binhex40", - ".mads": "application/mads+xml", - ".mrc": "application/marc", - ".mrcx": "application/marcxml+xml", - ".nb": "application/mathematica", - ".ma": "application/mathematica", - ".mb": "application/mathematica", - ".mml": "application/mathml+xml", - ".mbox": "application/mbox", - ".meta4": "application/metalink4+xml", - ".mets": "application/mets+xml", - ".mf4": "application/MF4", - ".mods": "application/mods+xml", - ".m21": "application/mp21", - ".mp21": "application/mp21", - ".doc": "application/msword", - ".mxf": "application/mxf", - ".nq": "application/n-quads", - ".nt": "application/n-triples", - ".orq": "application/ocsp-request", - ".ors": "application/ocsp-response", - ".bin": "application/octet-stream", - ".lha": "application/octet-stream", - ".lzh": "application/octet-stream", - ".exe": "application/octet-stream", - ".class": "application/octet-stream", - ".so": "application/octet-stream", - ".dll": "application/octet-stream", - ".img": "application/octet-stream", - ".iso": "application/octet-stream", - ".oda": "application/oda", - ".odx": "application/ODX", - ".opf": "application/oebps-package+xml", - ".ogx": "application/ogg", - ".oxps": "application/oxps", - ".relo": "application/p2p-overlay+xml", - ".pdf": "application/pdf", - ".pdx": "application/PDX", - ".pgp": "application/pgp-encrypted", - ".sig": "application/pgp-signature", - ".p10": "application/pkcs10", - ".p12": "application/pkcs12", - ".pfx": "application/pkcs12", - ".p7m": "application/pkcs7-mime", - ".p7c": "application/pkcs7-mime", - ".p7s": "application/pkcs7-signature", - ".p8": "application/pkcs8", - ".cer": "application/pkix-cert", - ".crl": "application/pkix-crl", - ".pkipath": "application/pkix-pkipath", - ".pki": "application/pkixcmp", - ".pls": "application/pls+xml", - ".ps": "application/postscript", - ".eps": "application/postscript", - ".ai": "application/postscript", - ".provx": "application/provenance+xml", - ".cw": "application/prs.cww", - ".cww": "application/prs.cww", - ".hpub": "application/prs.hpub+zip", - ".rnd": "application/prs.nprend", - ".rct": "application/prs.nprend", - ".rdf-crypt": "application/prs.rdf-xml-crypt", - ".xsf": "application/prs.xsf+xml", - ".pskcxml": "application/pskc+xml", - ".rdf": "application/rdf+xml", - ".rif": "application/reginfo+xml", - ".rnc": "application/relax-ng-compact-syntax", - ".rld": "application/resource-lists-diff+xml", - ".rl": "application/resource-lists+xml", - ".rfcxml": "application/rfc+xml", - ".rs": "application/rls-services+xml", - ".gbr": "application/rpki-ghostbusters", - ".mft": "application/rpki-manifest", - ".roa": "application/rpki-roa", - ".rtf": "application/rtf", - ".scim": "application/scim+json", - ".scq": "application/scvp-cv-request", - ".scs": "application/scvp-cv-response", - ".spq": "application/scvp-vp-request", - ".spp": "application/scvp-vp-response", - ".sdp": "application/sdp", - ".soc": "application/sgml-open-catalog", - ".shf": "application/shf+xml", - ".siv": "application/sieve", - ".sieve": "application/sieve", - ".cl": "application/simple-filter+xml", - ".smil": "application/smil+xml", - ".smi": "application/smil+xml", - ".sml": "application/smil+xml", - ".rq": "application/sparql-query", - ".srx": "application/sparql-results+xml", - ".sql": "application/sql", - ".gram": "application/srgs", - ".grxml": "application/srgs+xml", - ".sru": "application/sru+xml", - ".ssml": "application/ssml+xml", - ".tau": "application/tamp-apex-update", - ".auc": "application/tamp-apex-update-confirm", - ".tcu": "application/tamp-community-update", - ".cuc": "application/tamp-community-update-confirm", - ".ter": "application/tamp-error", - ".tsa": "application/tamp-sequence-adjust", - ".sac": "application/tamp-sequence-adjust-confirm", - ".tur": "application/tamp-update", - ".tuc": "application/tamp-update-confirm", - ".tei": "application/tei+xml", - ".teiCorpus": "application/tei+xml", - ".odd": "application/tei+xml", - ".tfi": "application/thraud+xml", - ".tsq": "application/timestamp-query", - ".tsr": "application/timestamp-reply", - ".tsd": "application/timestamped-data", - ".trig": "application/trig", - ".ttml": "application/ttml+xml", - ".gsheet": "application/urc-grpsheet+xml", - ".rsheet": "application/urc-ressheet+xml", - ".td": "application/urc-targetdesc+xml", - ".uis": "application/urc-uisocketdesc+xml", - ".plb": "application/vnd.3gpp.pic-bw-large", - ".psb": "application/vnd.3gpp.pic-bw-small", - ".pvb": "application/vnd.3gpp.pic-bw-var", - ".sms": "application/vnd.3gpp2.sms", - ".tcap": "application/vnd.3gpp2.tcap", - ".imgcal": "application/vnd.3lightssoftware.imagescal", - ".pwn": "application/vnd.3M.Post-it-Notes", - ".aso": "application/vnd.accpac.simply.aso", - ".imp": "application/vnd.accpac.simply.imp", - ".acu": "application/vnd.acucobol", - ".atc": "application/vnd.acucorp", - ".acutc": "application/vnd.acucorp", - ".swf": "application/vnd.adobe.flash.movie", - ".fcdt": "application/vnd.adobe.formscentral.fcdt", - ".fxp": "application/vnd.adobe.fxp", - ".fxpl": "application/vnd.adobe.fxp", - ".xdp": "application/vnd.adobe.xdp+xml", - ".xfdf": "application/vnd.adobe.xfdf", - ".ahead": "application/vnd.ahead.space", - ".azf": "application/vnd.airzip.filesecure.azf", - ".azs": "application/vnd.airzip.filesecure.azs", - ".azw3": "application/vnd.amazon.mobi8-ebook", - ".acc": "application/vnd.americandynamics.acc", - ".ami": "application/vnd.amiga.ami", - ".apkg": "application/vnd.anki", - ".cii": "application/vnd.anser-web-certificate-issue-initiation", - ".fti": "application/vnd.anser-web-funds-transfer-initiation", - ".dist": "application/vnd.apple.installer+xml", - ".distz": "application/vnd.apple.installer+xml", - ".pkg": "application/vnd.apple.installer+xml", - ".mpkg": "application/vnd.apple.installer+xml", - ".m3u8": "application/vnd.apple.mpegurl", - ".swi": "application/vnd.aristanetworks.swi", - ".iota": "application/vnd.astraea-software.iota", - ".aep": "application/vnd.audiograph", - ".package": "application/vnd.autopackage", - ".bmml": "application/vnd.balsamiq.bmml+xml", - ".bmpr": "application/vnd.balsamiq.bmpr", - ".mpm": "application/vnd.blueice.multipass", - ".ep": "application/vnd.bluetooth.ep.oob", - ".le": "application/vnd.bluetooth.le.oob", - ".bmi": "application/vnd.bmi", - ".rep": "application/vnd.businessobjects", - ".tlclient": "application/vnd.cendio.thinlinc.clientconf", - ".cdxml": "application/vnd.chemdraw+xml", - ".pgn": "application/vnd.chess-pgn", - ".mmd": "application/vnd.chipnuts.karaoke-mmd", - ".cdy": "application/vnd.cinderella", - ".csl": "application/vnd.citationstyles.style+xml", - ".cla": "application/vnd.claymore", - ".rp9": "application/vnd.cloanto.rp9", - ".c4g": "application/vnd.clonk.c4group", - ".c4d": "application/vnd.clonk.c4group", - ".c4f": "application/vnd.clonk.c4group", - ".c4p": "application/vnd.clonk.c4group", - ".c4u": "application/vnd.clonk.c4group", - ".c11amc": "application/vnd.cluetrust.cartomobile-config", - ".c11amz": "application/vnd.cluetrust.cartomobile-config-pkg", - ".coffee": "application/vnd.coffeescript", - ".cbz": "application/vnd.comicbook+zip", - ".ica": "application/vnd.commerce-battelle", - ".icf": "application/vnd.commerce-battelle", - ".icd": "application/vnd.commerce-battelle", - ".ic0": "application/vnd.commerce-battelle", - ".ic1": "application/vnd.commerce-battelle", - ".ic2": "application/vnd.commerce-battelle", - ".ic3": "application/vnd.commerce-battelle", - ".ic4": "application/vnd.commerce-battelle", - ".ic5": "application/vnd.commerce-battelle", - ".ic6": "application/vnd.commerce-battelle", - ".ic7": "application/vnd.commerce-battelle", - ".ic8": "application/vnd.commerce-battelle", - ".csp": "application/vnd.commonspace", - ".cst": "application/vnd.commonspace", - ".cdbcmsg": "application/vnd.contact.cmsg", - ".ign": "application/vnd.coreos.ignition+json", - ".ignition": "application/vnd.coreos.ignition+json", - ".cmc": "application/vnd.cosmocaller", - ".clkx": "application/vnd.crick.clicker", - ".clkk": "application/vnd.crick.clicker.keyboard", - ".clkp": "application/vnd.crick.clicker.palette", - ".clkt": "application/vnd.crick.clicker.template", - ".clkw": "application/vnd.crick.clicker.wordbank", - ".wbs": "application/vnd.criticaltools.wbs+xml", - ".pml": "application/vnd.ctc-posml", - ".ppd": "application/vnd.cups-ppd", - ".curl": "application/vnd.curl", - ".dart": "application/vnd.dart", - ".rdz": "application/vnd.data-vision.rdz", - ".deb": "application/vnd.debian.binary-package", - ".udeb": "application/vnd.debian.binary-package", - ".uvf": "application/vnd.dece.data", - ".uvvf": "application/vnd.dece.data", - ".uvd": "application/vnd.dece.data", - ".uvvd": "application/vnd.dece.data", - ".uvt": "application/vnd.dece.ttml+xml", - ".uvvt": "application/vnd.dece.ttml+xml", - ".uvx": "application/vnd.dece.unspecified", - ".uvvx": "application/vnd.dece.unspecified", - ".uvz": "application/vnd.dece.zip", - ".uvvz": "application/vnd.dece.zip", - ".fe_launch": "application/vnd.denovo.fcselayout-link", - ".dsm": "application/vnd.desmume.movie", - ".dna": "application/vnd.dna", - ".docjson": "application/vnd.document+json", - ".scld": "application/vnd.doremir.scorecloud-binary-document", - ".dpg": "application/vnd.dpgraph", - ".mwc": "application/vnd.dpgraph", - ".dpgraph": "application/vnd.dpgraph", - ".dfac": "application/vnd.dreamfactory", - ".fla": "application/vnd.dtg.local.flash", - ".ait": "application/vnd.dvb.ait", - ".svc": "application/vnd.dvb.service", - ".geo": "application/vnd.dynageo", - ".dzr": "application/vnd.dzr", - ".mag": "application/vnd.ecowin.chart", - ".nml": "application/vnd.enliven", - ".esf": "application/vnd.epson.esf", - ".msf": "application/vnd.epson.msf", - ".qam": "application/vnd.epson.quickanime", - ".slt": "application/vnd.epson.salt", - ".ssf": "application/vnd.epson.ssf", - ".qcall": "application/vnd.ericsson.quickcall", - ".qca": "application/vnd.ericsson.quickcall", - ".espass": "application/vnd.espass-espass+zip", - ".es3": "application/vnd.eszigno3+xml", - ".et3": "application/vnd.eszigno3+xml", - ".asice": "application/vnd.etsi.asic-e+zip", - ".sce": "application/vnd.etsi.asic-e+zip", - ".asics": "application/vnd.etsi.asic-s+zip", - ".tst": "application/vnd.etsi.timestamp-token", - ".ez2": "application/vnd.ezpix-album", - ".ez3": "application/vnd.ezpix-package", - ".dim": "application/vnd.fastcopy-disk-image", - ".fdf": "application/vnd.fdf", - ".msd": "application/vnd.fdsn.mseed", - ".mseed": "application/vnd.fdsn.mseed", - ".seed": "application/vnd.fdsn.seed", - ".dataless": "application/vnd.fdsn.seed", - ".zfc": "application/vnd.filmit.zfc", - ".gph": "application/vnd.FloGraphIt", - ".ftc": "application/vnd.fluxtime.clip", - ".sfd": "application/vnd.font-fontforge-sfd", - ".fm": "application/vnd.framemaker", - ".fnc": "application/vnd.frogans.fnc", - ".ltf": "application/vnd.frogans.ltf", - ".fsc": "application/vnd.fsc.weblaunch", - ".oas": "application/vnd.fujitsu.oasys", - ".oa2": "application/vnd.fujitsu.oasys2", - ".oa3": "application/vnd.fujitsu.oasys3", - ".fg5": "application/vnd.fujitsu.oasysgp", - ".bh2": "application/vnd.fujitsu.oasysprs", - ".ddd": "application/vnd.fujixerox.ddd", - ".xdw": "application/vnd.fujixerox.docuworks", - ".xbd": "application/vnd.fujixerox.docuworks.binder", - ".xct": "application/vnd.fujixerox.docuworks.container", - ".fzs": "application/vnd.fuzzysheet", - ".txd": "application/vnd.genomatix.tuxedo", - ".g3": "application/vnd.geocube+xml", - ".g³": "application/vnd.geocube+xml", - ".ggb": "application/vnd.geogebra.file", - ".ggt": "application/vnd.geogebra.tool", - ".gex": "application/vnd.geometry-explorer", - ".gre": "application/vnd.geometry-explorer", - ".gxt": "application/vnd.geonext", - ".g2w": "application/vnd.geoplan", - ".g3w": "application/vnd.geospace", - ".gmx": "application/vnd.gmx", - ".kml": "application/vnd.google-earth.kml+xml", - ".kmz": "application/vnd.google-earth.kmz", - ".gqf": "application/vnd.grafeq", - ".gqs": "application/vnd.grafeq", - ".gac": "application/vnd.groove-account", - ".ghf": "application/vnd.groove-help", - ".gim": "application/vnd.groove-identity-message", - ".grv": "application/vnd.groove-injector", - ".gtm": "application/vnd.groove-tool-message", - ".tpl": "application/vnd.groove-tool-template", - ".vcg": "application/vnd.groove-vcard", - ".hal": "application/vnd.hal+xml", - ".zmm": "application/vnd.HandHeld-Entertainment+xml", - ".hbci": "application/vnd.hbci", - ".hbc": "application/vnd.hbci", - ".kom": "application/vnd.hbci", - ".upa": "application/vnd.hbci", - ".pkd": "application/vnd.hbci", - ".bpd": "application/vnd.hbci", - ".hdt": "application/vnd.hdt", - ".les": "application/vnd.hhe.lesson-player", - ".hpgl": "application/vnd.hp-HPGL", - ".hpi": "application/vnd.hp-hpid", - ".hpid": "application/vnd.hp-hpid", - ".hps": "application/vnd.hp-hps", - ".jlt": "application/vnd.hp-jlyt", - ".pcl": "application/vnd.hp-PCL", - ".sfd-hdstx": "application/vnd.hydrostatix.sof-data", - ".x3d": "application/vnd.hzn-3d-crossword", - ".emm": "application/vnd.ibm.electronic-media", - ".mpy": "application/vnd.ibm.MiniPay", - ".list3820": "application/vnd.ibm.modcap", - ".listafp": "application/vnd.ibm.modcap", - ".afp": "application/vnd.ibm.modcap", - ".pseg3820": "application/vnd.ibm.modcap", - ".irm": "application/vnd.ibm.rights-management", - ".sc": "application/vnd.ibm.secure-container", - ".icc": "application/vnd.iccprofile", - ".icm": "application/vnd.iccprofile", - ".1905.1": "application/vnd.ieee.1905", - ".igl": "application/vnd.igloader", - ".imf": "application/vnd.imagemeter.folder+zip", - ".imi": "application/vnd.imagemeter.image+zip", - ".ivp": "application/vnd.immervision-ivp", - ".ivu": "application/vnd.immervision-ivu", - ".imscc": "application/vnd.ims.imsccv1p1", - ".igm": "application/vnd.insors.igm", - ".xpw": "application/vnd.intercon.formnet", - ".xpx": "application/vnd.intercon.formnet", - ".i2g": "application/vnd.intergeo", - ".qbo": "application/vnd.intu.qbo", - ".qfx": "application/vnd.intu.qfx", - ".rcprofile": "application/vnd.ipunplugged.rcprofile", - ".irp": "application/vnd.irepository.package+xml", - ".xpr": "application/vnd.is-xpr", - ".fcs": "application/vnd.isac.fcs", - ".jam": "application/vnd.jam", - ".rms": "application/vnd.jcp.javame.midlet-rms", - ".jisp": "application/vnd.jisp", - ".joda": "application/vnd.joost.joda-archive", - ".ktz": "application/vnd.kahootz", - ".ktr": "application/vnd.kahootz", - ".karbon": "application/vnd.kde.karbon", - ".chrt": "application/vnd.kde.kchart", - ".kfo": "application/vnd.kde.kformula", - ".flw": "application/vnd.kde.kivio", - ".kon": "application/vnd.kde.kontour", - ".kpr": "application/vnd.kde.kpresenter", - ".kpt": "application/vnd.kde.kpresenter", - ".ksp": "application/vnd.kde.kspread", - ".kwd": "application/vnd.kde.kword", - ".kwt": "application/vnd.kde.kword", - ".htke": "application/vnd.kenameaapp", - ".kia": "application/vnd.kidspiration", - ".kne": "application/vnd.Kinar", - ".knp": "application/vnd.Kinar", - ".sdf": "application/vnd.Kinar", - ".skp": "application/vnd.koan", - ".skd": "application/vnd.koan", - ".skm": "application/vnd.koan", - ".skt": "application/vnd.koan", - ".sse": "application/vnd.kodak-descriptor", - ".lasjson": "application/vnd.las.las+json", - ".lasxml": "application/vnd.las.las+xml", - ".lbd": "application/vnd.llamagraphics.life-balance.desktop", - ".lbe": "application/vnd.llamagraphics.life-balance.exchange+xml", - ".123": "application/vnd.lotus-1-2-3", - ".wk4": "application/vnd.lotus-1-2-3", - ".wk3": "application/vnd.lotus-1-2-3", - ".wk1": "application/vnd.lotus-1-2-3", - ".apr": "application/vnd.lotus-approach", - ".vew": "application/vnd.lotus-approach", - ".prz": "application/vnd.lotus-freelance", - ".pre": "application/vnd.lotus-freelance", - ".nsf": "application/vnd.lotus-notes", - ".ntf": "application/vnd.lotus-notes", - ".ndl": "application/vnd.lotus-notes", - ".ns4": "application/vnd.lotus-notes", - ".ns3": "application/vnd.lotus-notes", - ".ns2": "application/vnd.lotus-notes", - ".nsh": "application/vnd.lotus-notes", - ".nsg": "application/vnd.lotus-notes", - ".or3": "application/vnd.lotus-organizer", - ".or2": "application/vnd.lotus-organizer", - ".org": "application/vnd.lotus-organizer", - ".scm": "application/vnd.lotus-screencam", - ".lwp": "application/vnd.lotus-wordpro", - ".sam": "application/vnd.lotus-wordpro", - ".portpkg": "application/vnd.macports.portpkg", - ".mvt": "application/vnd.mapbox-vector-tile", - ".mdc": "application/vnd.marlin.drm.mdcf", - ".mmdb": "application/vnd.maxmind.maxmind-db", - ".mcd": "application/vnd.mcd", - ".mc1": "application/vnd.medcalcdata", - ".cdkey": "application/vnd.mediastation.cdkey", - ".mwf": "application/vnd.MFER", - ".mfm": "application/vnd.mfmp", - ".flo": "application/vnd.micrografx.flo", - ".igx": "application/vnd.micrografx.igx", - ".mif": "application/vnd.mif", - ".daf": "application/vnd.Mobius.DAF", - ".dis": "application/vnd.Mobius.DIS", - ".mbk": "application/vnd.Mobius.MBK", - ".mqy": "application/vnd.Mobius.MQY", - ".msl": "application/vnd.Mobius.MSL", - ".plc": "application/vnd.Mobius.PLC", - ".txf": "application/vnd.Mobius.TXF", - ".mpn": "application/vnd.mophun.application", - ".mpc": "application/vnd.mophun.certificate", - ".xul": "application/vnd.mozilla.xul+xml", - ".3mf": "application/vnd.ms-3mfdocument", - ".cil": "application/vnd.ms-artgalry", - ".asf": "application/vnd.ms-asf", - ".cab": "application/vnd.ms-cab-compressed", - ".xls": "application/vnd.ms-excel", - ".xlm": "application/vnd.ms-excel", - ".xla": "application/vnd.ms-excel", - ".xlc": "application/vnd.ms-excel", - ".xlt": "application/vnd.ms-excel", - ".xlw": "application/vnd.ms-excel", - ".xltm": "application/vnd.ms-excel.template.macroEnabled.12", - ".xlam": "application/vnd.ms-excel.addin.macroEnabled.12", - ".xlsb": "application/vnd.ms-excel.sheet.binary.macroEnabled.12", - ".xlsm": "application/vnd.ms-excel.sheet.macroEnabled.12", - ".eot": "application/vnd.ms-fontobject", - ".chm": "application/vnd.ms-htmlhelp", - ".ims": "application/vnd.ms-ims", - ".lrm": "application/vnd.ms-lrm", - ".thmx": "application/vnd.ms-officetheme", - ".ppt": "application/vnd.ms-powerpoint", - ".pps": "application/vnd.ms-powerpoint", - ".pot": "application/vnd.ms-powerpoint", - ".ppam": "application/vnd.ms-powerpoint.addin.macroEnabled.12", - ".pptm": "application/vnd.ms-powerpoint.presentation.macroEnabled.12", - ".sldm": "application/vnd.ms-powerpoint.slide.macroEnabled.12", - ".ppsm": "application/vnd.ms-powerpoint.slideshow.macroEnabled.12", - ".potm": "application/vnd.ms-powerpoint.template.macroEnabled.12", - ".mpp": "application/vnd.ms-project", - ".mpt": "application/vnd.ms-project", - ".tnef": "application/vnd.ms-tnef", - ".tnf": "application/vnd.ms-tnef", - ".docm": "application/vnd.ms-word.document.macroEnabled.12", - ".dotm": "application/vnd.ms-word.template.macroEnabled.12", - ".wcm": "application/vnd.ms-works", - ".wdb": "application/vnd.ms-works", - ".wks": "application/vnd.ms-works", - ".wps": "application/vnd.ms-works", - ".wpl": "application/vnd.ms-wpl", - ".xps": "application/vnd.ms-xpsdocument", - ".msa": "application/vnd.msa-disk-image", - ".mseq": "application/vnd.mseq", - ".crtr": "application/vnd.multiad.creator", - ".cif": "application/vnd.multiad.creator.cif", - ".mus": "application/vnd.musician", - ".msty": "application/vnd.muvee.style", - ".taglet": "application/vnd.mynfc", - ".entity": "application/vnd.nervana", - ".request": "application/vnd.nervana", - ".bkm": "application/vnd.nervana", - ".kcm": "application/vnd.nervana", - ".nitf": "application/vnd.nitf", - ".nlu": "application/vnd.neurolanguage.nlu", - ".nds": "application/vnd.nintendo.nitro.rom", - ".sfc": "application/vnd.nintendo.snes.rom", - ".smc": "application/vnd.nintendo.snes.rom", - ".nnd": "application/vnd.noblenet-directory", - ".nns": "application/vnd.noblenet-sealer", - ".nnw": "application/vnd.noblenet-web", - ".ac": "application/vnd.nokia.n-gage.ac+xml", - ".ngdat": "application/vnd.nokia.n-gage.data", - ".n-gage": "application/vnd.nokia.n-gage.symbian.install", - ".rpst": "application/vnd.nokia.radio-preset", - ".rpss": "application/vnd.nokia.radio-presets", - ".edm": "application/vnd.novadigm.EDM", - ".edx": "application/vnd.novadigm.EDX", - ".ext": "application/vnd.novadigm.EXT", - ".odc": "application/vnd.oasis.opendocument.chart", - ".otc": "application/vnd.oasis.opendocument.chart-template", - ".odb": "application/vnd.oasis.opendocument.database", - ".odf": "application/vnd.oasis.opendocument.formula", - ".odg": "application/vnd.oasis.opendocument.graphics", - ".otg": "application/vnd.oasis.opendocument.graphics-template", - ".odi": "application/vnd.oasis.opendocument.image", - ".oti": "application/vnd.oasis.opendocument.image-template", - ".odp": "application/vnd.oasis.opendocument.presentation", - ".otp": "application/vnd.oasis.opendocument.presentation-template", - ".ods": "application/vnd.oasis.opendocument.spreadsheet", - ".ots": "application/vnd.oasis.opendocument.spreadsheet-template", - ".odt": "application/vnd.oasis.opendocument.text", - ".odm": "application/vnd.oasis.opendocument.text-master", - ".ott": "application/vnd.oasis.opendocument.text-template", - ".oth": "application/vnd.oasis.opendocument.text-web", - ".xo": "application/vnd.olpc-sugar", - ".dd2": "application/vnd.oma.dd2+xml", - ".tam": "application/vnd.onepager", - ".tamp": "application/vnd.onepagertamp", - ".tamx": "application/vnd.onepagertamx", - ".tat": "application/vnd.onepagertat", - ".tatp": "application/vnd.onepagertatp", - ".tatx": "application/vnd.onepagertatx", - ".obgx": "application/vnd.openblox.game+xml", - ".obg": "application/vnd.openblox.game-binary", - ".oeb": "application/vnd.openeye.oeb", - ".oxt": "application/vnd.openofficeorg.extension", - ".osm": "application/vnd.openstreetmap.data+xml", - ".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation", - ".sldx": "application/vnd.openxmlformats-officedocument.presentationml.slide", - ".ppsx": "application/vnd.openxmlformats-officedocument.presentationml.slideshow", - ".potx": "application/vnd.openxmlformats-officedocument.presentationml.template", - ".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - ".xltx": "application/vnd.openxmlformats-officedocument.spreadsheetml.template", - ".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - ".dotx": "application/vnd.openxmlformats-officedocument.wordprocessingml.template", - ".ndc": "application/vnd.osa.netdeploy", - ".mgp": "application/vnd.osgeo.mapguide.package", - ".dp": "application/vnd.osgi.dp", - ".esa": "application/vnd.osgi.subsystem", - ".oxlicg": "application/vnd.oxli.countgraph", - ".prc": "application/vnd.palm", - ".pdb": "application/vnd.palm", - ".pqa": "application/vnd.palm", - ".oprc": "application/vnd.palm", - ".plp": "application/vnd.panoply", - ".paw": "application/vnd.pawaafile", - ".str": "application/vnd.pg.format", - ".ei6": "application/vnd.pg.osasli", - ".pil": "application/vnd.piaccess.application-license", - ".efif": "application/vnd.picsel", - ".wg": "application/vnd.pmi.widget", - ".plf": "application/vnd.pocketlearn", - ".pbd": "application/vnd.powerbuilder6", - ".preminet": "application/vnd.preminet", - ".box": "application/vnd.previewsystems.box", - ".vbox": "application/vnd.previewsystems.box", - ".mgz": "application/vnd.proteus.magazine", - ".qps": "application/vnd.publishare-delta-tree", - ".ptid": "application/vnd.pvi.ptid1", - ".bar": "application/vnd.qualcomm.brew-app-res", - ".qxd": "application/vnd.Quark.QuarkXPress", - ".qxt": "application/vnd.Quark.QuarkXPress", - ".qwd": "application/vnd.Quark.QuarkXPress", - ".qwt": "application/vnd.Quark.QuarkXPress", - ".qxl": "application/vnd.Quark.QuarkXPress", - ".qxb": "application/vnd.Quark.QuarkXPress", - ".quox": "application/vnd.quobject-quoxdocument", - ".quiz": "application/vnd.quobject-quoxdocument", - ".tree": "application/vnd.rainstor.data", - ".rar": "application/vnd.rar", - ".bed": "application/vnd.realvnc.bed", - ".mxl": "application/vnd.recordare.musicxml", - ".cryptonote": "application/vnd.rig.cryptonote", - ".link66": "application/vnd.route66.link66+xml", - ".st": "application/vnd.sailingtracker.track", - ".scd": "application/vnd.scribus", - ".sla": "application/vnd.scribus", - ".slaz": "application/vnd.scribus", - ".s3df": "application/vnd.sealed.3df", - ".scsf": "application/vnd.sealed.csf", - ".sdoc": "application/vnd.sealed.doc", - ".sdo": "application/vnd.sealed.doc", - ".s1w": "application/vnd.sealed.doc", - ".seml": "application/vnd.sealed.eml", - ".sem": "application/vnd.sealed.eml", - ".smht": "application/vnd.sealed.mht", - ".smh": "application/vnd.sealed.mht", - ".sppt": "application/vnd.sealed.ppt", - ".s1p": "application/vnd.sealed.ppt", - ".stif": "application/vnd.sealed.tiff", - ".sxls": "application/vnd.sealed.xls", - ".sxl": "application/vnd.sealed.xls", - ".s1e": "application/vnd.sealed.xls", - ".stml": "application/vnd.sealedmedia.softseal.html", - ".s1h": "application/vnd.sealedmedia.softseal.html", - ".spdf": "application/vnd.sealedmedia.softseal.pdf", - ".spd": "application/vnd.sealedmedia.softseal.pdf", - ".s1a": "application/vnd.sealedmedia.softseal.pdf", - ".see": "application/vnd.seemail", - ".sema": "application/vnd.sema", - ".semd": "application/vnd.semd", - ".semf": "application/vnd.semf", - ".ifm": "application/vnd.shana.informed.formdata", - ".itp": "application/vnd.shana.informed.formtemplate", - ".iif": "application/vnd.shana.informed.interchange", - ".ipk": "application/vnd.shana.informed.package", - ".twd": "application/vnd.SimTech-MindMapper", - ".twds": "application/vnd.SimTech-MindMapper", - ".mmf": "application/vnd.smaf", - ".notebook": "application/vnd.smart.notebook", - ".teacher": "application/vnd.smart.teacher", - ".fo": "application/vnd.software602.filler.form+xml", - ".zfo": "application/vnd.software602.filler.form-xml-zip", - ".sdkm": "application/vnd.solent.sdkm+xml", - ".sdkd": "application/vnd.solent.sdkm+xml", - ".dxp": "application/vnd.spotfire.dxp", - ".sfs": "application/vnd.spotfire.sfs", - ".smzip": "application/vnd.stepmania.package", - ".sm": "application/vnd.stepmania.stepchart", - ".wadl": "application/vnd.sun.wadl+xml", - ".sus": "application/vnd.sus-calendar", - ".susp": "application/vnd.sus-calendar", - ".xsm": "application/vnd.syncml+xml", - ".bdm": "application/vnd.syncml.dm+wbxml", - ".xdm": "application/vnd.syncml.dm+xml", - ".ddf": "application/vnd.syncml.dmddf+xml", - ".tao": "application/vnd.tao.intent-module-archive", - ".pcap": "application/vnd.tcpdump.pcap", - ".cap": "application/vnd.tcpdump.pcap", - ".dmp": "application/vnd.tcpdump.pcap", - ".qvd": "application/vnd.theqvd", - ".vfr": "application/vnd.tml", - ".viaframe": "application/vnd.tml", - ".tmo": "application/vnd.tmobile-livetv", - ".tpt": "application/vnd.trid.tpt", - ".mxs": "application/vnd.triscape.mxs", - ".tra": "application/vnd.trueapp", - ".ufdl": "application/vnd.ufdl", - ".ufd": "application/vnd.ufdl", - ".frm": "application/vnd.ufdl", - ".utz": "application/vnd.uiq.theme", - ".umj": "application/vnd.umajin", - ".unityweb": "application/vnd.unity", - ".uoml": "application/vnd.uoml+xml", - ".uo": "application/vnd.uoml+xml", - ".urim": "application/vnd.uri-map", - ".urimap": "application/vnd.uri-map", - ".vmt": "application/vnd.valve.source.material", - ".vcx": "application/vnd.vcx", - ".mxi": "application/vnd.vd-study", - ".study-inter": "application/vnd.vd-study", - ".model-inter": "application/vnd.vd-study", - ".vwx": "application/vnd.vectorworks", - ".vsc": "application/vnd.vidsoft.vidconference", - ".vsd": "application/vnd.visio", - ".vst": "application/vnd.visio", - ".vsw": "application/vnd.visio", - ".vss": "application/vnd.visio", - ".vis": "application/vnd.visionary", - ".vsf": "application/vnd.vsf", - ".sic": "application/vnd.wap.sic", - ".slc": "application/vnd.wap.slc", - ".wbxml": "application/vnd.wap.wbxml", - ".wmlc": "application/vnd.wap.wmlc", - ".wmlsc": "application/vnd.wap.wmlscriptc", - ".wtb": "application/vnd.webturbo", - ".p2p": "application/vnd.wfa.p2p", - ".wsc": "application/vnd.wfa.wsc", - ".wmc": "application/vnd.wmc", - ".m": "application/vnd.wolfram.mathematica.package", - ".nbp": "application/vnd.wolfram.player", - ".wpd": "application/vnd.wordperfect", - ".wqd": "application/vnd.wqd", - ".stf": "application/vnd.wt.stf", - ".wv": "application/vnd.wv.csp+wbxml", - ".xar": "application/vnd.xara", - ".xfdl": "application/vnd.xfdl", - ".xfd": "application/vnd.xfdl", - ".cpkg": "application/vnd.xmpie.cpkg", - ".dpkg": "application/vnd.xmpie.dpkg", - ".ppkg": "application/vnd.xmpie.ppkg", - ".xlim": "application/vnd.xmpie.xlim", - ".hvd": "application/vnd.yamaha.hv-dic", - ".hvs": "application/vnd.yamaha.hv-script", - ".hvp": "application/vnd.yamaha.hv-voice", - ".osf": "application/vnd.yamaha.openscoreformat", - ".saf": "application/vnd.yamaha.smaf-audio", - ".spf": "application/vnd.yamaha.smaf-phrase", - ".yme": "application/vnd.yaoweme", - ".cmp": "application/vnd.yellowriver-custom-menu", - ".zir": "application/vnd.zul", - ".zirz": "application/vnd.zul", - ".zaz": "application/vnd.zzazz.deck+xml", - ".vxml": "application/voicexml+xml", - ".wif": "application/watcherinfo+xml", - ".wgt": "application/widget", - ".wsdl": "application/wsdl+xml", - ".wspolicy": "application/wspolicy+xml", - ".xav": "application/xcap-att+xml", - ".xca": "application/xcap-caps+xml", - ".xdf": "application/xcap-diff+xml", - ".xel": "application/xcap-el+xml", - ".xer": "application/xcap-error+xml", - ".xns": "application/xcap-ns+xml", - ".xhtml": "application/xhtml+xml", - ".xhtm": "application/xhtml+xml", - ".xht": "application/xhtml+xml", - ".dtd": "application/xml-dtd", - ".xop": "application/xop+xml", - ".xsl": "application/xslt+xml", - ".xslt": "application/xslt+xml", - ".mxml": "application/xv+xml", - ".xhvml": "application/xv+xml", - ".xvml": "application/xv+xml", - ".xvm": "application/xv+xml", - ".yang": "application/yang", - ".yin": "application/yin+xml", - ".zip": "application/zip", - ".726": "audio/32kadpcm", - ".ac3": "audio/ac3", - ".amr": "audio/AMR", - ".awb": "audio/AMR-WB", - ".acn": "audio/asc", - ".aal": "audio/ATRAC-ADVANCED-LOSSLESS", - ".atx": "audio/ATRAC-X", - ".at3": "audio/ATRAC3", - ".aa3": "audio/ATRAC3", - ".omg": "audio/ATRAC3", - ".au": "audio/basic", - ".snd": "audio/basic", - ".dls": "audio/dls", - ".evc": "audio/EVRC", - ".evb": "audio/EVRCB", - ".enw": "audio/EVRCNW", - ".evw": "audio/EVRCWB", - ".lbc": "audio/iLBC", - ".l16": "audio/L16", - ".mxmf": "audio/mobile-xmf", - ".m4a": "audio/mp4", - ".mp3": "audio/mpeg", - ".mpga": "audio/mpeg", - ".mp1": "audio/mpeg", - ".mp2": "audio/mpeg", - ".oga": "audio/ogg", - ".ogg": "audio/ogg", - ".opus": "audio/ogg", - ".spx": "audio/ogg", - ".sid": "audio/prs.sid", - ".psid": "audio/prs.sid", - ".qcp": "audio/qcelp", - ".smv": "audio/SMV", - ".koz": "audio/vnd.audikoz", - ".uva": "audio/vnd.dece.audio", - ".uvva": "audio/vnd.dece.audio", - ".eol": "audio/vnd.digital-winds", - ".mlp": "audio/vnd.dolby.mlp", - ".dts": "audio/vnd.dts", - ".dtshd": "audio/vnd.dts.hd", - ".plj": "audio/vnd.everad.plj", - ".lvp": "audio/vnd.lucent.voice", - ".pya": "audio/vnd.ms-playready.media.pya", - ".vbk": "audio/vnd.nortel.vbk", - ".ecelp4800": "audio/vnd.nuera.ecelp4800", - ".ecelp7470": "audio/vnd.nuera.ecelp7470", - ".ecelp9600": "audio/vnd.nuera.ecelp9600", - ".rip": "audio/vnd.rip", - ".smp3": "audio/vnd.sealedmedia.softseal.mpeg", - ".smp": "audio/vnd.sealedmedia.softseal.mpeg", - ".s1m": "audio/vnd.sealedmedia.softseal.mpeg", - ".ttc": "font/collection", - ".otf": "font/otf", - ".ttf": "font/ttf", - ".woff": "font/woff", - ".woff2": "font/woff2", - ".bmp": "image/bmp", - ".dib": "image/bmp", - ".cgm": "image/cgm", - ".drle": "image/dicom-rle", - ".emf": "image/emf", - ".fits": "image/fits", - ".fit": "image/fits", - ".fts": "image/fits", - ".gif": "image/gif", - ".ief": "image/ief", - ".jls": "image/jls", - ".jp2": "image/jp2", - ".jpg2": "image/jp2", - ".jpg": "image/jpeg", - ".jpeg": "image/jpeg", - ".jpe": "image/jpeg", - ".jfif": "image/jpeg", - ".jpm": "image/jpm", - ".jpgm": "image/jpm", - ".jpx": "image/jpx", - ".jpf": "image/jpx", - ".ktx": "image/ktx", - ".png": "image/png", - ".btif": "image/prs.btif", - ".btf": "image/prs.btif", - ".pti": "image/prs.pti", - ".svg": "image/svg+xml", - ".svgz": "image/svg+xml", - ".t38": "image/t38", - ".tiff": "image/tiff", - ".tif": "image/tiff", - ".tfx": "image/tiff-fx", - ".psd": "image/vnd.adobe.photoshop", - ".azv": "image/vnd.airzip.accelerator.azv", - ".uvi": "image/vnd.dece.graphic", - ".uvvi": "image/vnd.dece.graphic", - ".uvg": "image/vnd.dece.graphic", - ".uvvg": "image/vnd.dece.graphic", - ".djvu": "image/vnd.djvu", - ".djv": "image/vnd.djvu", - ".dwg": "image/vnd.dwg", - ".dxf": "image/vnd.dxf", - ".fbs": "image/vnd.fastbidsheet", - ".fpx": "image/vnd.fpx", - ".fst": "image/vnd.fst", - ".mmr": "image/vnd.fujixerox.edmics-mmr", - ".rlc": "image/vnd.fujixerox.edmics-rlc", - ".pgb": "image/vnd.globalgraphics.pgb", - ".ico": "image/vnd.microsoft.icon", - ".apng": "image/vnd.mozilla.apng", - ".mdi": "image/vnd.ms-modi", - ".hdr": "image/vnd.radiance", - ".rgbe": "image/vnd.radiance", - ".xyze": "image/vnd.radiance", - ".spng": "image/vnd.sealed.png", - ".spn": "image/vnd.sealed.png", - ".s1n": "image/vnd.sealed.png", - ".sgif": "image/vnd.sealedmedia.softseal.gif", - ".sgi": "image/vnd.sealedmedia.softseal.gif", - ".s1g": "image/vnd.sealedmedia.softseal.gif", - ".sjpg": "image/vnd.sealedmedia.softseal.jpg", - ".sjp": "image/vnd.sealedmedia.softseal.jpg", - ".s1j": "image/vnd.sealedmedia.softseal.jpg", - ".tap": "image/vnd.tencent.tap", - ".vtf": "image/vnd.valve.source.texture", - ".wbmp": "image/vnd.wap.wbmp", - ".xif": "image/vnd.xiff", - ".pcx": "image/vnd.zbrush.pcx", - ".wmf": "image/wmf", - ".u8msg": "message/global", - ".u8dsn": "message/global-delivery-status", - ".u8mdn": "message/global-disposition-notification", - ".u8hdr": "message/global-headers", - ".eml": "message/rfc822", - ".mail": "message/rfc822", - ".art": "message/rfc822", - ".gltf": "model/gltf+json", - ".igs": "model/iges", - ".iges": "model/iges", - ".msh": "model/mesh", - ".mesh": "model/mesh", - ".silo": "model/mesh", - ".dae": "model/vnd.collada+xml", - ".dwf": "model/vnd.dwf", - ".gdl": "model/vnd.gdl", - ".gsm": "model/vnd.gdl", - ".win": "model/vnd.gdl", - ".dor": "model/vnd.gdl", - ".lmp": "model/vnd.gdl", - ".rsm": "model/vnd.gdl", - ".msm": "model/vnd.gdl", - ".ism": "model/vnd.gdl", - ".gtw": "model/vnd.gtw", - ".moml": "model/vnd.moml+xml", - ".mts": "model/vnd.mts", - ".ogex": "model/vnd.opengex", - ".x_b": "model/vnd.parasolid.transmit.binary", - ".xmt_bin": "model/vnd.parasolid.transmit.binary", - ".x_t": "model/vnd.parasolid.transmit.text", - ".xmt_txt": "model/vnd.parasolid.transmit.text", - ".bsp": "model/vnd.valve.source.compiled-map", - ".vtu": "model/vnd.vtu", - ".wrl": "model/vrml", - ".vrml": "model/vrml", - ".x3db": "model/x3d+xml", - ".x3dv": "model/x3d-vrml", - ".x3dvz": "model/x3d-vrml", - ".bmed": "multipart/vnd.bint.med-plus", - ".vpm": "multipart/voice-message", - ".appcache": "text/cache-manifest", - ".manifest": "text/cache-manifest", - ".ics": "text/calendar", - ".ifb": "text/calendar", - ".css": "text/css", - ".csv": "text/csv", - ".csvs": "text/csv-schema", - ".soa": "text/dns", - ".zone": "text/dns", - ".html": "text/html", - ".htm": "text/html", - ".cnd": "text/jcr-cnd", - ".markdown": "text/markdown", - ".md": "text/markdown", - ".miz": "text/mizar", - ".n3": "text/n3", - ".txt": "text/plain", - ".asc": "text/plain", - ".text": "text/plain", - ".pm": "text/plain", - ".el": "text/plain", - ".c": "text/plain", - ".h": "text/plain", - ".cc": "text/plain", - ".hh": "text/plain", - ".cxx": "text/plain", - ".hxx": "text/plain", - ".f90": "text/plain", - ".conf": "text/plain", - ".log": "text/plain", - ".provn": "text/provenance-notation", - ".rst": "text/prs.fallenstein.rst", - ".tag": "text/prs.lines.tag", - ".dsc": "text/prs.lines.tag", - ".rtx": "text/richtext", - ".sgml": "text/sgml", - ".sgm": "text/sgml", - ".tsv": "text/tab-separated-values", - ".t": "text/troff", - ".tr": "text/troff", - ".roff": "text/troff", - ".ttl": "text/turtle", - ".uris": "text/uri-list", - ".uri": "text/uri-list", - ".vcf": "text/vcard", - ".vcard": "text/vcard", - ".a": "text/vnd.a", - ".abc": "text/vnd.abc", - ".ascii": "text/vnd.ascii-art", - ".copyright": "text/vnd.debian.copyright", - ".dms": "text/vnd.DMClientScript", - ".sub": "text/vnd.dvb.subtitle", - ".jtd": "text/vnd.esmertec.theme-descriptor", - ".fly": "text/vnd.fly", - ".flx": "text/vnd.fmi.flexstor", - ".gv": "text/vnd.graphviz", - ".dot": "text/vnd.graphviz", - ".3dml": "text/vnd.in3d.3dml", - ".3dm": "text/vnd.in3d.3dml", - ".spot": "text/vnd.in3d.spot", - ".spo": "text/vnd.in3d.spot", - ".mpf": "text/vnd.ms-mediapackage", - ".ccc": "text/vnd.net2phone.commcenter.command", - ".uric": "text/vnd.si.uricatalogue", - ".jad": "text/vnd.sun.j2me.app-descriptor", - ".ts": "text/vnd.trolltech.linguist", - ".si": "text/vnd.wap.si", - ".sl": "text/vnd.wap.sl", - ".wml": "text/vnd.wap.wml", - ".wmls": "text/vnd.wap.wmlscript", - ".xml": "text/xml", - ".xsd": "text/xml", - ".rng": "text/xml", - ".ent": "text/xml-external-parsed-entity", - ".3gp": "video/3gpp", - ".3gpp": "video/3gpp", - ".3g2": "video/3gpp2", - ".3gpp2": "video/3gpp2", - ".m4s": "video/iso.segment", - ".mj2": "video/mj2", - ".mjp2": "video/mj2", - ".mp4": "video/mp4", - ".mpg4": "video/mp4", - ".m4v": "video/mp4", - ".mpeg": "video/mpeg", - ".mpg": "video/mpeg", - ".mpe": "video/mpeg", - ".m1v": "video/mpeg", - ".m2v": "video/mpeg", - ".ogv": "video/ogg", - ".mov": "video/quicktime", - ".qt": "video/quicktime", - ".uvh": "video/vnd.dece.hd", - ".uvvh": "video/vnd.dece.hd", - ".uvm": "video/vnd.dece.mobile", - ".uvvm": "video/vnd.dece.mobile", - ".uvu": "video/vnd.dece.mp4", - ".uvvu": "video/vnd.dece.mp4", - ".uvp": "video/vnd.dece.pd", - ".uvvp": "video/vnd.dece.pd", - ".uvs": "video/vnd.dece.sd", - ".uvvs": "video/vnd.dece.sd", - ".uvv": "video/vnd.dece.video", - ".uvvv": "video/vnd.dece.video", - ".dvb": "video/vnd.dvb.file", - ".fvt": "video/vnd.fvt", - ".mxu": "video/vnd.mpegurl", - ".m4u": "video/vnd.mpegurl", - ".pyv": "video/vnd.ms-playready.media.pyv", - ".nim": "video/vnd.nokia.interleaved-multimedia", - ".bik": "video/vnd.radgamettools.bink", - ".bk2": "video/vnd.radgamettools.bink", - ".smk": "video/vnd.radgamettools.smacker", - ".smpg": "video/vnd.sealed.mpeg1", - ".s11": "video/vnd.sealed.mpeg1", - ".s14": "video/vnd.sealed.mpeg4", - ".sswf": "video/vnd.sealed.swf", - ".ssw": "video/vnd.sealed.swf", - ".smov": "video/vnd.sealedmedia.softseal.mov", - ".smo": "video/vnd.sealedmedia.softseal.mov", - ".s1q": "video/vnd.sealedmedia.softseal.mov", - ".viv": "video/vnd.vivo", - ".cpt": "application/mac-compactpro", - ".metalink": "application/metalink+xml", - ".owx": "application/owl+xml", - ".rss": "application/rss+xml", - ".apk": "application/vnd.android.package-archive", - ".dd": "application/vnd.oma.dd+xml", - ".dcf": "application/vnd.oma.drm.content", - ".o4a": "application/vnd.oma.drm.dcf", - ".o4v": "application/vnd.oma.drm.dcf", - ".dm": "application/vnd.oma.drm.message", - ".drc": "application/vnd.oma.drm.rights+wbxml", - ".dr": "application/vnd.oma.drm.rights+xml", - ".sxc": "application/vnd.sun.xml.calc", - ".stc": "application/vnd.sun.xml.calc.template", - ".sxd": "application/vnd.sun.xml.draw", - ".std": "application/vnd.sun.xml.draw.template", - ".sxi": "application/vnd.sun.xml.impress", - ".sti": "application/vnd.sun.xml.impress.template", - ".sxm": "application/vnd.sun.xml.math", - ".sxw": "application/vnd.sun.xml.writer", - ".sxg": "application/vnd.sun.xml.writer.global", - ".stw": "application/vnd.sun.xml.writer.template", - ".sis": "application/vnd.symbian.install", - ".mms": "application/vnd.wap.mms-message", - ".anx": "application/x-annodex", - ".bcpio": "application/x-bcpio", - ".torrent": "application/x-bittorrent", - ".bz2": "application/x-bzip2", - ".vcd": "application/x-cdlink", - ".crx": "application/x-chrome-extension", - ".cpio": "application/x-cpio", - ".csh": "application/x-csh", - ".dcr": "application/x-director", - ".dir": "application/x-director", - ".dxr": "application/x-director", - ".dvi": "application/x-dvi", - ".spl": "application/x-futuresplash", - ".gtar": "application/x-gtar", - ".hdf": "application/x-hdf", - ".jar": "application/x-java-archive", - ".jnlp": "application/x-java-jnlp-file", - ".pack": "application/x-java-pack200", - ".kil": "application/x-killustrator", - ".latex": "application/x-latex", - ".nc": "application/x-netcdf", - ".cdf": "application/x-netcdf", - ".pl": "application/x-perl", - ".rpm": "application/x-rpm", - ".sh": "application/x-sh", - ".shar": "application/x-shar", - ".sit": "application/x-stuffit", - ".sv4cpio": "application/x-sv4cpio", - ".sv4crc": "application/x-sv4crc", - ".tar": "application/x-tar", - ".tcl": "application/x-tcl", - ".tex": "application/x-tex", - ".texinfo": "application/x-texinfo", - ".texi": "application/x-texinfo", - ".man": "application/x-troff-man", - ".1": "application/x-troff-man", - ".2": "application/x-troff-man", - ".3": "application/x-troff-man", - ".4": "application/x-troff-man", - ".5": "application/x-troff-man", - ".6": "application/x-troff-man", - ".7": "application/x-troff-man", - ".8": "application/x-troff-man", - ".me": "application/x-troff-me", - ".ms": "application/x-troff-ms", - ".ustar": "application/x-ustar", - ".src": "application/x-wais-source", - ".xpi": "application/x-xpinstall", - ".xspf": "application/x-xspf+xml", - ".xz": "application/x-xz", - ".mid": "audio/midi", - ".midi": "audio/midi", - ".kar": "audio/midi", - ".aif": "audio/x-aiff", - ".aiff": "audio/x-aiff", - ".aifc": "audio/x-aiff", - ".axa": "audio/x-annodex", - ".flac": "audio/x-flac", - ".mka": "audio/x-matroska", - ".mod": "audio/x-mod", - ".ult": "audio/x-mod", - ".uni": "audio/x-mod", - ".m15": "audio/x-mod", - ".mtm": "audio/x-mod", - ".669": "audio/x-mod", - ".med": "audio/x-mod", - ".m3u": "audio/x-mpegurl", - ".wax": "audio/x-ms-wax", - ".wma": "audio/x-ms-wma", - ".ram": "audio/x-pn-realaudio", - ".rm": "audio/x-pn-realaudio", - ".ra": "audio/x-realaudio", - ".s3m": "audio/x-s3m", - ".stm": "audio/x-stm", - ".wav": "audio/x-wav", - ".xyz": "chemical/x-xyz", - ".webp": "image/webp", - ".ras": "image/x-cmu-raster", - ".pnm": "image/x-portable-anymap", - ".pbm": "image/x-portable-bitmap", - ".pgm": "image/x-portable-graymap", - ".ppm": "image/x-portable-pixmap", - ".rgb": "image/x-rgb", - ".tga": "image/x-targa", - ".xbm": "image/x-xbitmap", - ".xpm": "image/x-xpixmap", - ".xwd": "image/x-xwindowdump", - ".sandboxed": "text/html-sandboxed", - ".pod": "text/x-pod", - ".etx": "text/x-setext", - ".webm": "video/webm", - ".axv": "video/x-annodex", - ".flv": "video/x-flv", - ".fxm": "video/x-javafx", - ".mkv": "video/x-matroska", - ".mk3d": "video/x-matroska-3d", - ".asx": "video/x-ms-asf", - ".wm": "video/x-ms-wm", - ".wmv": "video/x-ms-wmv", - ".wmx": "video/x-ms-wmx", - ".wvx": "video/x-ms-wvx", - ".avi": "video/x-msvideo", - ".movie": "video/x-sgi-movie", - ".ice": "x-conference/x-cooltalk", - ".sisx": "x-epoc/x-sisx-app", - } - for ext, name := range mimeTypes { - if err := mime.AddExtensionType(ext, name); err != nil { - panic(err) - } - } -} diff --git a/swarm/api/http/middleware.go b/swarm/api/http/middleware.go deleted file mode 100644 index e63c70c3f3b5..000000000000 --- a/swarm/api/http/middleware.go +++ /dev/null @@ -1,113 +0,0 @@ -package http - -import ( - "fmt" - "net/http" - "runtime/debug" - "strings" - "time" - - "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/swarm/api" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/sctx" - "github.com/ubiq/go-ubiq/swarm/spancontext" - "github.com/pborman/uuid" -) - -// Adapt chains h (main request handler) main handler to adapters (middleware handlers) -// Please note that the order of execution for `adapters` is FIFO (adapters[0] will be executed first) -func Adapt(h http.Handler, adapters ...Adapter) http.Handler { - for i := range adapters { - adapter := adapters[len(adapters)-1-i] - h = adapter(h) - } - return h -} - -type Adapter func(http.Handler) http.Handler - -func SetRequestID(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(SetRUID(r.Context(), uuid.New()[:8])) - metrics.GetOrRegisterCounter(fmt.Sprintf("http.request.%s", r.Method), nil).Inc(1) - log.Info("created ruid for request", "ruid", GetRUID(r.Context()), "method", r.Method, "url", r.RequestURI) - - h.ServeHTTP(w, r) - }) -} - -func SetRequestHost(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - r = r.WithContext(sctx.SetHost(r.Context(), r.Host)) - log.Info("setting request host", "ruid", GetRUID(r.Context()), "host", sctx.GetHost(r.Context())) - - h.ServeHTTP(w, r) - }) -} - -func ParseURI(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - uri, err := api.Parse(strings.TrimLeft(r.URL.Path, "/")) - if err != nil { - w.WriteHeader(http.StatusBadRequest) - respondError(w, r, fmt.Sprintf("invalid URI %q", r.URL.Path), http.StatusBadRequest) - return - } - if uri.Addr != "" && strings.HasPrefix(uri.Addr, "0x") { - uri.Addr = strings.TrimPrefix(uri.Addr, "0x") - - msg := fmt.Sprintf(`The requested hash seems to be prefixed with '0x'. You will be redirected to the correct URL within 5 seconds.
- Please click here if your browser does not redirect you within 5 seconds.`, "/"+uri.String()) - w.WriteHeader(http.StatusNotFound) - w.Write([]byte(msg)) - return - } - - ctx := r.Context() - r = r.WithContext(SetURI(ctx, uri)) - log.Debug("parsed request path", "ruid", GetRUID(r.Context()), "method", r.Method, "uri.Addr", uri.Addr, "uri.Path", uri.Path, "uri.Scheme", uri.Scheme) - - h.ServeHTTP(w, r) - }) -} - -func InitLoggingResponseWriter(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - tn := time.Now() - - writer := newLoggingResponseWriter(w) - h.ServeHTTP(writer, r) - - ts := time.Since(tn) - log.Info("request served", "ruid", GetRUID(r.Context()), "code", writer.statusCode, "time", ts) - metrics.GetOrRegisterResettingTimer(fmt.Sprintf("http.request.%s.time", r.Method), nil).Update(ts) - metrics.GetOrRegisterResettingTimer(fmt.Sprintf("http.request.%s.%d.time", r.Method, writer.statusCode), nil).Update(ts) - }) -} - -func InstrumentOpenTracing(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - uri := GetURI(r.Context()) - if uri == nil || r.Method == "" || (uri != nil && uri.Scheme == "") { - h.ServeHTTP(w, r) // soft fail - return - } - spanName := fmt.Sprintf("http.%s.%s", r.Method, uri.Scheme) - ctx, sp := spancontext.StartSpan(r.Context(), spanName) - - defer sp.Finish() - h.ServeHTTP(w, r.WithContext(ctx)) - }) -} - -func RecoverPanic(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - defer func() { - if err := recover(); err != nil { - log.Error("panic recovery!", "stack trace", string(debug.Stack()), "url", r.URL.String(), "headers", r.Header) - } - }() - h.ServeHTTP(w, r) - }) -} diff --git a/swarm/api/http/response.go b/swarm/api/http/response.go deleted file mode 100644 index b74c09b2c656..000000000000 --- a/swarm/api/http/response.go +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package http - -import ( - "encoding/json" - "fmt" - "html/template" - "net/http" - "strings" - "time" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/swarm/api" -) - -var ( - htmlCounter = metrics.NewRegisteredCounter("api.http.errorpage.html.count", nil) - jsonCounter = metrics.NewRegisteredCounter("api.http.errorpage.json.count", nil) - plaintextCounter = metrics.NewRegisteredCounter("api.http.errorpage.plaintext.count", nil) -) - -type ResponseParams struct { - Msg template.HTML - Code int - Timestamp string - template *template.Template - Details template.HTML -} - -// ShowMultipleChoices is used when a user requests a resource in a manifest which results -// in ambiguous results. It returns a HTML page with clickable links of each of the entry -// in the manifest which fits the request URI ambiguity. -// For example, if the user requests bzz://read and that manifest contains entries -// "readme.md" and "readinglist.txt", a HTML page is returned with this two links. -// This only applies if the manifest has no default entry -func ShowMultipleChoices(w http.ResponseWriter, r *http.Request, list api.ManifestList) { - log.Debug("ShowMultipleChoices", "ruid", GetRUID(r.Context()), "uri", GetURI(r.Context())) - msg := "" - if list.Entries == nil { - respondError(w, r, "Could not resolve", http.StatusInternalServerError) - return - } - requestUri := strings.TrimPrefix(r.RequestURI, "/") - - uri, err := api.Parse(requestUri) - if err != nil { - respondError(w, r, "Bad Request", http.StatusBadRequest) - } - - uri.Scheme = "bzz-list" - msg += fmt.Sprintf("Disambiguation:
Your request may refer to multiple choices.
Click here if your browser does not redirect you within 5 seconds.
", "/"+uri.String()) - respondTemplate(w, r, "error", msg, http.StatusMultipleChoices) -} - -func respondTemplate(w http.ResponseWriter, r *http.Request, templateName, msg string, code int) { - log.Debug("respondTemplate", "ruid", GetRUID(r.Context()), "uri", GetURI(r.Context())) - respond(w, r, &ResponseParams{ - Code: code, - Msg: template.HTML(msg), - Timestamp: time.Now().Format(time.RFC1123), - template: TemplatesMap[templateName], - }) -} - -func respondError(w http.ResponseWriter, r *http.Request, msg string, code int) { - log.Info("respondError", "ruid", GetRUID(r.Context()), "uri", GetURI(r.Context()), "code", code) - respondTemplate(w, r, "error", msg, code) -} - -func respond(w http.ResponseWriter, r *http.Request, params *ResponseParams) { - w.WriteHeader(params.Code) - - if params.Code >= 400 { - w.Header().Del("Cache-Control") - w.Header().Del("ETag") - } - - acceptHeader := r.Header.Get("Accept") - // this cannot be in a switch since an Accept header can have multiple values: "Accept: */*, text/html, application/xhtml+xml, application/xml;q=0.9, */*;q=0.8" - if strings.Contains(acceptHeader, "application/json") { - if err := respondJSON(w, r, params); err != nil { - respondError(w, r, "Internal server error", http.StatusInternalServerError) - } - } else if strings.Contains(acceptHeader, "text/html") { - respondHTML(w, r, params) - } else { - respondPlaintext(w, r, params) //returns nice errors for curl - } -} - -func respondHTML(w http.ResponseWriter, r *http.Request, params *ResponseParams) { - htmlCounter.Inc(1) - log.Info("respondHTML", "ruid", GetRUID(r.Context()), "code", params.Code) - err := params.template.Execute(w, params) - if err != nil { - log.Error(err.Error()) - } -} - -func respondJSON(w http.ResponseWriter, r *http.Request, params *ResponseParams) error { - jsonCounter.Inc(1) - log.Info("respondJSON", "ruid", GetRUID(r.Context()), "code", params.Code) - w.Header().Set("Content-Type", "application/json") - return json.NewEncoder(w).Encode(params) -} - -func respondPlaintext(w http.ResponseWriter, r *http.Request, params *ResponseParams) error { - plaintextCounter.Inc(1) - log.Info("respondPlaintext", "ruid", GetRUID(r.Context()), "code", params.Code) - w.Header().Set("Content-Type", "text/plain") - strToWrite := "Code: " + fmt.Sprintf("%d", params.Code) + "\n" - strToWrite += "Message: " + string(params.Msg) + "\n" - strToWrite += "Timestamp: " + params.Timestamp + "\n" - _, err := w.Write([]byte(strToWrite)) - return err -} diff --git a/swarm/api/http/response_test.go b/swarm/api/http/response_test.go deleted file mode 100644 index 486c19ab0eed..000000000000 --- a/swarm/api/http/response_test.go +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package http - -import ( - "encoding/json" - "io/ioutil" - "net/http" - "strings" - "testing" - - "golang.org/x/net/html" -) - -func TestError(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - var resp *http.Response - var respbody []byte - - url := srv.URL + "/this_should_fail_as_no_bzz_protocol_present" - resp, err := http.Get(url) - - if err != nil { - t.Fatalf("Request failed: %v", err) - } - defer resp.Body.Close() - respbody, err = ioutil.ReadAll(resp.Body) - - if resp.StatusCode != 404 && !strings.Contains(string(respbody), "Invalid URI "/this_should_fail_as_no_bzz_protocol_present": unknown scheme") { - t.Fatalf("Response body does not match, expected: %v, to contain: %v; received code %d, expected code: %d", string(respbody), "Invalid bzz URI: unknown scheme", 400, resp.StatusCode) - } - - _, err = html.Parse(strings.NewReader(string(respbody))) - if err != nil { - t.Fatalf("HTML validation failed for error page returned!") - } -} - -func Test404Page(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - var resp *http.Response - var respbody []byte - - url := srv.URL + "/bzz:/1234567890123456789012345678901234567890123456789012345678901234" - resp, err := http.Get(url) - - if err != nil { - t.Fatalf("Request failed: %v", err) - } - defer resp.Body.Close() - respbody, err = ioutil.ReadAll(resp.Body) - - if resp.StatusCode != 404 || !strings.Contains(string(respbody), "404") { - t.Fatalf("Invalid Status Code received, expected 404, got %d", resp.StatusCode) - } - - _, err = html.Parse(strings.NewReader(string(respbody))) - if err != nil { - t.Fatalf("HTML validation failed for error page returned!") - } -} - -func Test500Page(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - var resp *http.Response - var respbody []byte - - url := srv.URL + "/bzz:/thisShouldFailWith500Code" - resp, err := http.Get(url) - - if err != nil { - t.Fatalf("Request failed: %v", err) - } - defer resp.Body.Close() - respbody, err = ioutil.ReadAll(resp.Body) - - if resp.StatusCode != 404 { - t.Fatalf("Invalid Status Code received, expected 404, got %d", resp.StatusCode) - } - - _, err = html.Parse(strings.NewReader(string(respbody))) - if err != nil { - t.Fatalf("HTML validation failed for error page returned!") - } -} -func Test500PageWith0xHashPrefix(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - var resp *http.Response - var respbody []byte - - url := srv.URL + "/bzz:/0xthisShouldFailWith500CodeAndAHelpfulMessage" - resp, err := http.Get(url) - - if err != nil { - t.Fatalf("Request failed: %v", err) - } - defer resp.Body.Close() - respbody, err = ioutil.ReadAll(resp.Body) - - if resp.StatusCode != 404 { - t.Fatalf("Invalid Status Code received, expected 404, got %d", resp.StatusCode) - } - - if !strings.Contains(string(respbody), "The requested hash seems to be prefixed with") { - t.Fatalf("Did not receive the expected error message") - } - - _, err = html.Parse(strings.NewReader(string(respbody))) - if err != nil { - t.Fatalf("HTML validation failed for error page returned!") - } -} - -func TestJsonResponse(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - var resp *http.Response - var respbody []byte - - url := srv.URL + "/bzz:/thisShouldFailWith500Code/" - req, err := http.NewRequest("GET", url, nil) - if err != nil { - t.Fatalf("Request failed: %v", err) - } - req.Header.Set("Accept", "application/json") - resp, err = http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("Request failed: %v", err) - } - - defer resp.Body.Close() - respbody, err = ioutil.ReadAll(resp.Body) - - if resp.StatusCode != 404 { - t.Fatalf("Invalid Status Code received, expected 404, got %d", resp.StatusCode) - } - - if !isJSON(string(respbody)) { - t.Fatalf("Expected response to be JSON, received invalid JSON: %s", string(respbody)) - } - -} - -func isJSON(s string) bool { - var js map[string]interface{} - return json.Unmarshal([]byte(s), &js) == nil -} diff --git a/swarm/api/http/roundtripper.go b/swarm/api/http/roundtripper.go deleted file mode 100644 index dfd2c64f217c..000000000000 --- a/swarm/api/http/roundtripper.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package http - -import ( - "fmt" - "net/http" - - "github.com/ubiq/go-ubiq/swarm/log" -) - -/* -http roundtripper to register for bzz url scheme -see https://github.com/ubiq/go-ubiq/issues/2040 -Usage: - -import ( - "github.com/ubiq/go-ubiq/common/httpclient" - "github.com/ubiq/go-ubiq/swarm/api/http" -) -client := httpclient.New() -// for (private) swarm proxy running locally -client.RegisterScheme("bzz", &http.RoundTripper{Port: port}) -client.RegisterScheme("bzz-immutable", &http.RoundTripper{Port: port}) -client.RegisterScheme("bzz-raw", &http.RoundTripper{Port: port}) - -The port you give the Roundtripper is the port the swarm proxy is listening on. -If Host is left empty, localhost is assumed. - -Using a public gateway, the above few lines gives you the leanest -bzz-scheme aware read-only http client. You really only ever need this -if you need go-native swarm access to bzz addresses. -*/ - -type RoundTripper struct { - Host string - Port string -} - -func (self *RoundTripper) RoundTrip(req *http.Request) (resp *http.Response, err error) { - host := self.Host - if len(host) == 0 { - host = "localhost" - } - url := fmt.Sprintf("http://%s:%s/%s:/%s/%s", host, self.Port, req.Proto, req.URL.Host, req.URL.Path) - log.Info(fmt.Sprintf("roundtripper: proxying request '%s' to '%s'", req.RequestURI, url)) - reqProxy, err := http.NewRequest(req.Method, url, req.Body) - if err != nil { - return nil, err - } - return http.DefaultClient.Do(reqProxy) -} diff --git a/swarm/api/http/roundtripper_test.go b/swarm/api/http/roundtripper_test.go deleted file mode 100644 index f99c4f35e058..000000000000 --- a/swarm/api/http/roundtripper_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package http - -import ( - "io/ioutil" - "net" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" -) - -func TestRoundTripper(t *testing.T) { - serveMux := http.NewServeMux() - serveMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { - if r.Method == "GET" { - w.Header().Set("Content-Type", "text/plain") - http.ServeContent(w, r, "", time.Unix(0, 0), strings.NewReader(r.RequestURI)) - } else { - http.Error(w, "Method "+r.Method+" is not supported.", http.StatusMethodNotAllowed) - } - }) - - srv := httptest.NewServer(serveMux) - defer srv.Close() - - host, port, _ := net.SplitHostPort(srv.Listener.Addr().String()) - rt := &RoundTripper{Host: host, Port: port} - trans := &http.Transport{} - trans.RegisterProtocol("bzz", rt) - client := &http.Client{Transport: trans} - resp, err := client.Get("bzz://test.com/path") - if err != nil { - t.Errorf("expected no error, got %v", err) - return - } - - defer func() { - if resp != nil { - resp.Body.Close() - } - }() - - content, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Errorf("expected no error, got %v", err) - return - } - if string(content) != "/HTTP/1.1:/test.com/path" { - t.Errorf("incorrect response from http server: expected '%v', got '%v'", "/HTTP/1.1:/test.com/path", string(content)) - } - -} diff --git a/swarm/api/http/sctx.go b/swarm/api/http/sctx.go deleted file mode 100644 index 240802240520..000000000000 --- a/swarm/api/http/sctx.go +++ /dev/null @@ -1,34 +0,0 @@ -package http - -import ( - "context" - - "github.com/ubiq/go-ubiq/swarm/api" - "github.com/ubiq/go-ubiq/swarm/sctx" -) - -type uriKey struct{} - -func GetRUID(ctx context.Context) string { - v, ok := ctx.Value(sctx.HTTPRequestIDKey{}).(string) - if ok { - return v - } - return "xxxxxxxx" -} - -func SetRUID(ctx context.Context, ruid string) context.Context { - return context.WithValue(ctx, sctx.HTTPRequestIDKey{}, ruid) -} - -func GetURI(ctx context.Context) *api.URI { - v, ok := ctx.Value(uriKey{}).(*api.URI) - if ok { - return v - } - return nil -} - -func SetURI(ctx context.Context, uri *api.URI) context.Context { - return context.WithValue(ctx, uriKey{}, uri) -} diff --git a/swarm/api/http/server.go b/swarm/api/http/server.go deleted file mode 100644 index 86f75d834b5f..000000000000 --- a/swarm/api/http/server.go +++ /dev/null @@ -1,887 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -/* -A simple http server interface to Swarm -*/ -package http - -import ( - "bufio" - "bytes" - "encoding/json" - "fmt" - "io" - "io/ioutil" - "mime" - "mime/multipart" - "net/http" - "os" - "path" - "strconv" - "strings" - "time" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/swarm/api" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/ubiq/go-ubiq/swarm/storage/feed" - "github.com/rs/cors" -) - -var ( - postRawCount = metrics.NewRegisteredCounter("api.http.post.raw.count", nil) - postRawFail = metrics.NewRegisteredCounter("api.http.post.raw.fail", nil) - postFilesCount = metrics.NewRegisteredCounter("api.http.post.files.count", nil) - postFilesFail = metrics.NewRegisteredCounter("api.http.post.files.fail", nil) - deleteCount = metrics.NewRegisteredCounter("api.http.delete.count", nil) - deleteFail = metrics.NewRegisteredCounter("api.http.delete.fail", nil) - getCount = metrics.NewRegisteredCounter("api.http.get.count", nil) - getFail = metrics.NewRegisteredCounter("api.http.get.fail", nil) - getFileCount = metrics.NewRegisteredCounter("api.http.get.file.count", nil) - getFileNotFound = metrics.NewRegisteredCounter("api.http.get.file.notfound", nil) - getFileFail = metrics.NewRegisteredCounter("api.http.get.file.fail", nil) - getListCount = metrics.NewRegisteredCounter("api.http.get.list.count", nil) - getListFail = metrics.NewRegisteredCounter("api.http.get.list.fail", nil) -) - -type methodHandler map[string]http.Handler - -func (m methodHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { - v, ok := m[r.Method] - if ok { - v.ServeHTTP(rw, r) - return - } - rw.WriteHeader(http.StatusMethodNotAllowed) -} - -func NewServer(api *api.API, corsString string) *Server { - var allowedOrigins []string - for _, domain := range strings.Split(corsString, ",") { - allowedOrigins = append(allowedOrigins, strings.TrimSpace(domain)) - } - c := cors.New(cors.Options{ - AllowedOrigins: allowedOrigins, - AllowedMethods: []string{http.MethodPost, http.MethodGet, http.MethodDelete, http.MethodPatch, http.MethodPut}, - MaxAge: 600, - AllowedHeaders: []string{"*"}, - }) - - server := &Server{api: api} - - defaultMiddlewares := []Adapter{ - RecoverPanic, - SetRequestID, - SetRequestHost, - InitLoggingResponseWriter, - ParseURI, - InstrumentOpenTracing, - } - - mux := http.NewServeMux() - mux.Handle("/bzz:/", methodHandler{ - "GET": Adapt( - http.HandlerFunc(server.HandleBzzGet), - defaultMiddlewares..., - ), - "POST": Adapt( - http.HandlerFunc(server.HandlePostFiles), - defaultMiddlewares..., - ), - "DELETE": Adapt( - http.HandlerFunc(server.HandleDelete), - defaultMiddlewares..., - ), - }) - mux.Handle("/bzz-raw:/", methodHandler{ - "GET": Adapt( - http.HandlerFunc(server.HandleGet), - defaultMiddlewares..., - ), - "POST": Adapt( - http.HandlerFunc(server.HandlePostRaw), - defaultMiddlewares..., - ), - }) - mux.Handle("/bzz-immutable:/", methodHandler{ - "GET": Adapt( - http.HandlerFunc(server.HandleBzzGet), - defaultMiddlewares..., - ), - }) - mux.Handle("/bzz-hash:/", methodHandler{ - "GET": Adapt( - http.HandlerFunc(server.HandleGet), - defaultMiddlewares..., - ), - }) - mux.Handle("/bzz-list:/", methodHandler{ - "GET": Adapt( - http.HandlerFunc(server.HandleGetList), - defaultMiddlewares..., - ), - }) - mux.Handle("/bzz-feed:/", methodHandler{ - "GET": Adapt( - http.HandlerFunc(server.HandleGetFeed), - defaultMiddlewares..., - ), - "POST": Adapt( - http.HandlerFunc(server.HandlePostFeed), - defaultMiddlewares..., - ), - }) - - mux.Handle("/", methodHandler{ - "GET": Adapt( - http.HandlerFunc(server.HandleRootPaths), - SetRequestID, - InitLoggingResponseWriter, - ), - }) - server.Handler = c.Handler(mux) - - return server -} - -func (s *Server) ListenAndServe(addr string) error { - s.listenAddr = addr - return http.ListenAndServe(addr, s) -} - -// browser API for registering bzz url scheme handlers: -// https://developer.mozilla.org/en/docs/Web-based_protocol_handlers -// electron (chromium) api for registering bzz url scheme handlers: -// https://github.com/atom/electron/blob/master/docs/api/protocol.md -type Server struct { - http.Handler - api *api.API - listenAddr string -} - -func (s *Server) HandleBzzGet(w http.ResponseWriter, r *http.Request) { - log.Debug("handleBzzGet", "ruid", GetRUID(r.Context()), "uri", r.RequestURI) - if r.Header.Get("Accept") == "application/x-tar" { - uri := GetURI(r.Context()) - _, credentials, _ := r.BasicAuth() - reader, err := s.api.GetDirectoryTar(r.Context(), s.api.Decryptor(r.Context(), credentials), uri) - if err != nil { - if isDecryptError(err) { - w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", uri.Address().String())) - respondError(w, r, err.Error(), http.StatusUnauthorized) - return - } - respondError(w, r, fmt.Sprintf("Had an error building the tarball: %v", err), http.StatusInternalServerError) - return - } - defer reader.Close() - - w.Header().Set("Content-Type", "application/x-tar") - - fileName := uri.Addr - if found := path.Base(uri.Path); found != "" && found != "." && found != "/" { - fileName = found - } - w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s.tar\"", fileName)) - - w.WriteHeader(http.StatusOK) - io.Copy(w, reader) - return - } - - s.HandleGetFile(w, r) -} - -func (s *Server) HandleRootPaths(w http.ResponseWriter, r *http.Request) { - switch r.RequestURI { - case "/": - respondTemplate(w, r, "landing-page", "Swarm: Please request a valid ENS or swarm hash with the appropriate bzz scheme", 200) - return - case "/robots.txt": - w.Header().Set("Last-Modified", time.Now().Format(http.TimeFormat)) - fmt.Fprintf(w, "User-agent: *\nDisallow: /") - case "/favicon.ico": - w.WriteHeader(http.StatusOK) - w.Write(faviconBytes) - default: - respondError(w, r, "Not Found", http.StatusNotFound) - } -} - -// HandlePostRaw handles a POST request to a raw bzz-raw:/ URI, stores the request -// body in swarm and returns the resulting storage address as a text/plain response -func (s *Server) HandlePostRaw(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - log.Debug("handle.post.raw", "ruid", ruid) - - postRawCount.Inc(1) - - toEncrypt := false - uri := GetURI(r.Context()) - if uri.Addr == "encrypt" { - toEncrypt = true - } - - if uri.Path != "" { - postRawFail.Inc(1) - respondError(w, r, "raw POST request cannot contain a path", http.StatusBadRequest) - return - } - - if uri.Addr != "" && uri.Addr != "encrypt" { - postRawFail.Inc(1) - respondError(w, r, "raw POST request addr can only be empty or \"encrypt\"", http.StatusBadRequest) - return - } - - if r.Header.Get("Content-Length") == "" { - postRawFail.Inc(1) - respondError(w, r, "missing Content-Length header in request", http.StatusBadRequest) - return - } - - addr, _, err := s.api.Store(r.Context(), r.Body, r.ContentLength, toEncrypt) - if err != nil { - postRawFail.Inc(1) - respondError(w, r, err.Error(), http.StatusInternalServerError) - return - } - - log.Debug("stored content", "ruid", ruid, "key", addr) - - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, addr) -} - -// HandlePostFiles handles a POST request to -// bzz:// which contains either a single file or multiple files -// (either a tar archive or multipart form), adds those files either to an -// existing manifest or to a new manifest under and returns the -// resulting manifest hash as a text/plain response -func (s *Server) HandlePostFiles(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - log.Debug("handle.post.files", "ruid", ruid) - postFilesCount.Inc(1) - - contentType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type")) - if err != nil { - postFilesFail.Inc(1) - respondError(w, r, err.Error(), http.StatusBadRequest) - return - } - - toEncrypt := false - uri := GetURI(r.Context()) - if uri.Addr == "encrypt" { - toEncrypt = true - } - - var addr storage.Address - if uri.Addr != "" && uri.Addr != "encrypt" { - addr, err = s.api.Resolve(r.Context(), uri.Addr) - if err != nil { - postFilesFail.Inc(1) - respondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusInternalServerError) - return - } - log.Debug("resolved key", "ruid", ruid, "key", addr) - } else { - addr, err = s.api.NewManifest(r.Context(), toEncrypt) - if err != nil { - postFilesFail.Inc(1) - respondError(w, r, err.Error(), http.StatusInternalServerError) - return - } - log.Debug("new manifest", "ruid", ruid, "key", addr) - } - - newAddr, err := s.api.UpdateManifest(r.Context(), addr, func(mw *api.ManifestWriter) error { - switch contentType { - case "application/x-tar": - _, err := s.handleTarUpload(r, mw) - if err != nil { - respondError(w, r, fmt.Sprintf("error uploading tarball: %v", err), http.StatusInternalServerError) - return err - } - return nil - case "multipart/form-data": - return s.handleMultipartUpload(r, params["boundary"], mw) - - default: - return s.handleDirectUpload(r, mw) - } - }) - if err != nil { - postFilesFail.Inc(1) - respondError(w, r, fmt.Sprintf("cannot create manifest: %s", err), http.StatusInternalServerError) - return - } - - log.Debug("stored content", "ruid", ruid, "key", newAddr) - - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, newAddr) -} - -func (s *Server) handleTarUpload(r *http.Request, mw *api.ManifestWriter) (storage.Address, error) { - log.Debug("handle.tar.upload", "ruid", GetRUID(r.Context())) - - defaultPath := r.URL.Query().Get("defaultpath") - - key, err := s.api.UploadTar(r.Context(), r.Body, GetURI(r.Context()).Path, defaultPath, mw) - if err != nil { - return nil, err - } - return key, nil -} - -func (s *Server) handleMultipartUpload(r *http.Request, boundary string, mw *api.ManifestWriter) error { - ruid := GetRUID(r.Context()) - log.Debug("handle.multipart.upload", "ruid", ruid) - mr := multipart.NewReader(r.Body, boundary) - for { - part, err := mr.NextPart() - if err == io.EOF { - return nil - } else if err != nil { - return fmt.Errorf("error reading multipart form: %s", err) - } - - var size int64 - var reader io.Reader - if contentLength := part.Header.Get("Content-Length"); contentLength != "" { - size, err = strconv.ParseInt(contentLength, 10, 64) - if err != nil { - return fmt.Errorf("error parsing multipart content length: %s", err) - } - reader = part - } else { - // copy the part to a tmp file to get its size - tmp, err := ioutil.TempFile("", "swarm-multipart") - if err != nil { - return err - } - defer os.Remove(tmp.Name()) - defer tmp.Close() - size, err = io.Copy(tmp, part) - if err != nil { - return fmt.Errorf("error copying multipart content: %s", err) - } - if _, err := tmp.Seek(0, io.SeekStart); err != nil { - return fmt.Errorf("error copying multipart content: %s", err) - } - reader = tmp - } - - // add the entry under the path from the request - name := part.FileName() - if name == "" { - name = part.FormName() - } - uri := GetURI(r.Context()) - path := path.Join(uri.Path, name) - entry := &api.ManifestEntry{ - Path: path, - ContentType: part.Header.Get("Content-Type"), - Size: size, - } - log.Debug("adding path to new manifest", "ruid", ruid, "bytes", entry.Size, "path", entry.Path) - contentKey, err := mw.AddEntry(r.Context(), reader, entry) - if err != nil { - return fmt.Errorf("error adding manifest entry from multipart form: %s", err) - } - log.Debug("stored content", "ruid", ruid, "key", contentKey) - } -} - -func (s *Server) handleDirectUpload(r *http.Request, mw *api.ManifestWriter) error { - ruid := GetRUID(r.Context()) - log.Debug("handle.direct.upload", "ruid", ruid) - key, err := mw.AddEntry(r.Context(), r.Body, &api.ManifestEntry{ - Path: GetURI(r.Context()).Path, - ContentType: r.Header.Get("Content-Type"), - Mode: 0644, - Size: r.ContentLength, - }) - if err != nil { - return err - } - log.Debug("stored content", "ruid", ruid, "key", key) - return nil -} - -// HandleDelete handles a DELETE request to bzz://, removes -// from and returns the resulting manifest hash as a -// text/plain response -func (s *Server) HandleDelete(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - uri := GetURI(r.Context()) - log.Debug("handle.delete", "ruid", ruid) - deleteCount.Inc(1) - newKey, err := s.api.Delete(r.Context(), uri.Addr, uri.Path) - if err != nil { - deleteFail.Inc(1) - respondError(w, r, fmt.Sprintf("could not delete from manifest: %v", err), http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, newKey) -} - -// Handles feed manifest creation and feed updates -// The POST request admits a JSON structure as defined in the feeds package: `feed.updateRequestJSON` -// The requests can be to a) create a feed manifest, b) update a feed or c) both a+b: create a feed manifest and publish a first update -func (s *Server) HandlePostFeed(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - uri := GetURI(r.Context()) - log.Debug("handle.post.feed", "ruid", ruid) - var err error - - // Creation and update must send feed.updateRequestJSON JSON structure - body, err := ioutil.ReadAll(r.Body) - if err != nil { - respondError(w, r, err.Error(), http.StatusInternalServerError) - return - } - - fd, err := s.api.ResolveFeed(r.Context(), uri, r.URL.Query()) - if err != nil { // couldn't parse query string or retrieve manifest - getFail.Inc(1) - httpStatus := http.StatusBadRequest - if err == api.ErrCannotLoadFeedManifest || err == api.ErrCannotResolveFeedURI { - httpStatus = http.StatusNotFound - } - respondError(w, r, fmt.Sprintf("cannot retrieve feed from manifest: %s", err), httpStatus) - return - } - - var updateRequest feed.Request - updateRequest.Feed = *fd - query := r.URL.Query() - - if err := updateRequest.FromValues(query, body); err != nil { // decodes request from query parameters - respondError(w, r, err.Error(), http.StatusBadRequest) - return - } - - switch { - case updateRequest.IsUpdate(): - // Verify that the signature is intact and that the signer is authorized - // to update this feed - // Check this early, to avoid creating a feed and then not being able to set its first update. - if err = updateRequest.Verify(); err != nil { - respondError(w, r, err.Error(), http.StatusForbidden) - return - } - _, err = s.api.FeedsUpdate(r.Context(), &updateRequest) - if err != nil { - respondError(w, r, err.Error(), http.StatusInternalServerError) - return - } - fallthrough - case query.Get("manifest") == "1": - // we create a manifest so we can retrieve feed updates with bzz:// later - // this manifest has a special "feed type" manifest, and saves the - // feed identification used to retrieve feed updates later - m, err := s.api.NewFeedManifest(r.Context(), &updateRequest.Feed) - if err != nil { - respondError(w, r, fmt.Sprintf("failed to create feed manifest: %v", err), http.StatusInternalServerError) - return - } - // the key to the manifest will be passed back to the client - // the client can access the feed directly through its Feed member - // the manifest key can be set as content in the resolver of the ENS name - outdata, err := json.Marshal(m) - if err != nil { - respondError(w, r, fmt.Sprintf("failed to create json response: %s", err), http.StatusInternalServerError) - return - } - fmt.Fprint(w, string(outdata)) - - w.Header().Add("Content-type", "application/json") - default: - respondError(w, r, "Missing signature in feed update request", http.StatusBadRequest) - } -} - -// HandleGetFeed retrieves Swarm feeds updates: -// bzz-feed:// - get latest feed update, given a manifest address -// - or - -// specify user + topic (optional), subtopic name (optional) directly, without manifest: -// bzz-feed://?user=0x...&topic=0x...&name=subtopic name -// topic defaults to 0x000... if not specified. -// name defaults to empty string if not specified. -// thus, empty name and topic refers to the user's default feed. -// -// Optional parameters: -// time=xx - get the latest update before time (in epoch seconds) -// hint.time=xx - hint the lookup algorithm looking for updates at around that time -// hint.level=xx - hint the lookup algorithm looking for updates at around this frequency level -// meta=1 - get feed metadata and status information instead of performing a feed query -// NOTE: meta=1 will be deprecated in the near future -func (s *Server) HandleGetFeed(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - uri := GetURI(r.Context()) - log.Debug("handle.get.feed", "ruid", ruid) - var err error - - fd, err := s.api.ResolveFeed(r.Context(), uri, r.URL.Query()) - if err != nil { // couldn't parse query string or retrieve manifest - getFail.Inc(1) - httpStatus := http.StatusBadRequest - if err == api.ErrCannotLoadFeedManifest || err == api.ErrCannotResolveFeedURI { - httpStatus = http.StatusNotFound - } - respondError(w, r, fmt.Sprintf("cannot retrieve feed information from manifest: %s", err), httpStatus) - return - } - - // determine if the query specifies period and version or it is a metadata query - if r.URL.Query().Get("meta") == "1" { - unsignedUpdateRequest, err := s.api.FeedsNewRequest(r.Context(), fd) - if err != nil { - getFail.Inc(1) - respondError(w, r, fmt.Sprintf("cannot retrieve feed metadata for feed=%s: %s", fd.Hex(), err), http.StatusNotFound) - return - } - rawResponse, err := unsignedUpdateRequest.MarshalJSON() - if err != nil { - respondError(w, r, fmt.Sprintf("cannot encode unsigned feed update request: %v", err), http.StatusInternalServerError) - return - } - w.Header().Add("Content-type", "application/json") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, string(rawResponse)) - return - } - - lookupParams := &feed.Query{Feed: *fd} - if err = lookupParams.FromValues(r.URL.Query()); err != nil { // parse period, version - respondError(w, r, fmt.Sprintf("invalid feed update request:%s", err), http.StatusBadRequest) - return - } - - data, err := s.api.FeedsLookup(r.Context(), lookupParams) - - // any error from the switch statement will end up here - if err != nil { - code, err2 := s.translateFeedError(w, r, "feed lookup fail", err) - respondError(w, r, err2.Error(), code) - return - } - - // All ok, serve the retrieved update - log.Debug("Found update", "feed", fd.Hex(), "ruid", ruid) - w.Header().Set("Content-Type", api.MimeOctetStream) - http.ServeContent(w, r, "", time.Now(), bytes.NewReader(data)) -} - -func (s *Server) translateFeedError(w http.ResponseWriter, r *http.Request, supErr string, err error) (int, error) { - code := 0 - defaultErr := fmt.Errorf("%s: %v", supErr, err) - rsrcErr, ok := err.(*feed.Error) - if !ok && rsrcErr != nil { - code = rsrcErr.Code() - } - switch code { - case storage.ErrInvalidValue: - return http.StatusBadRequest, defaultErr - case storage.ErrNotFound, storage.ErrNotSynced, storage.ErrNothingToReturn, storage.ErrInit: - return http.StatusNotFound, defaultErr - case storage.ErrUnauthorized, storage.ErrInvalidSignature: - return http.StatusUnauthorized, defaultErr - case storage.ErrDataOverflow: - return http.StatusRequestEntityTooLarge, defaultErr - } - - return http.StatusInternalServerError, defaultErr -} - -// HandleGet handles a GET request to -// - bzz-raw:// and responds with the raw content stored at the -// given storage key -// - bzz-hash:// and responds with the hash of the content stored -// at the given storage key as a text/plain response -func (s *Server) HandleGet(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - uri := GetURI(r.Context()) - log.Debug("handle.get", "ruid", ruid, "uri", uri) - getCount.Inc(1) - _, pass, _ := r.BasicAuth() - - addr, err := s.api.ResolveURI(r.Context(), uri, pass) - if err != nil { - getFail.Inc(1) - respondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound) - return - } - w.Header().Set("Cache-Control", "max-age=2147483648, immutable") // url was of type bzz:///path, so we are sure it is immutable. - - log.Debug("handle.get: resolved", "ruid", ruid, "key", addr) - - // if path is set, interpret as a manifest and return the - // raw entry at the given path - - etag := common.Bytes2Hex(addr) - noneMatchEtag := r.Header.Get("If-None-Match") - w.Header().Set("ETag", fmt.Sprintf("%q", etag)) // set etag to manifest key or raw entry key. - if noneMatchEtag != "" { - if bytes.Equal(storage.Address(common.Hex2Bytes(noneMatchEtag)), addr) { - w.WriteHeader(http.StatusNotModified) - return - } - } - - // check the root chunk exists by retrieving the file's size - reader, isEncrypted := s.api.Retrieve(r.Context(), addr) - if _, err := reader.Size(r.Context(), nil); err != nil { - getFail.Inc(1) - respondError(w, r, fmt.Sprintf("root chunk not found %s: %s", addr, err), http.StatusNotFound) - return - } - - w.Header().Set("X-Decrypted", fmt.Sprintf("%v", isEncrypted)) - - switch { - case uri.Raw(): - // allow the request to overwrite the content type using a query - // parameter - if typ := r.URL.Query().Get("content_type"); typ != "" { - w.Header().Set("Content-Type", typ) - } - http.ServeContent(w, r, "", time.Now(), reader) - case uri.Hash(): - w.Header().Set("Content-Type", "text/plain") - w.WriteHeader(http.StatusOK) - fmt.Fprint(w, addr) - } -} - -// HandleGetList handles a GET request to bzz-list:// and returns -// a list of all files contained in under grouped into -// common prefixes using "/" as a delimiter -func (s *Server) HandleGetList(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - uri := GetURI(r.Context()) - _, credentials, _ := r.BasicAuth() - log.Debug("handle.get.list", "ruid", ruid, "uri", uri) - getListCount.Inc(1) - - // ensure the root path has a trailing slash so that relative URLs work - if uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") { - http.Redirect(w, r, r.URL.Path+"/", http.StatusMovedPermanently) - return - } - - addr, err := s.api.Resolve(r.Context(), uri.Addr) - if err != nil { - getListFail.Inc(1) - respondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound) - return - } - log.Debug("handle.get.list: resolved", "ruid", ruid, "key", addr) - - list, err := s.api.GetManifestList(r.Context(), s.api.Decryptor(r.Context(), credentials), addr, uri.Path) - if err != nil { - getListFail.Inc(1) - if isDecryptError(err) { - w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", addr.String())) - respondError(w, r, err.Error(), http.StatusUnauthorized) - return - } - respondError(w, r, err.Error(), http.StatusInternalServerError) - return - } - - // if the client wants HTML (e.g. a browser) then render the list as a - // HTML index with relative URLs - if strings.Contains(r.Header.Get("Accept"), "text/html") { - w.Header().Set("Content-Type", "text/html") - err := TemplatesMap["bzz-list"].Execute(w, &htmlListData{ - URI: &api.URI{ - Scheme: "bzz", - Addr: uri.Addr, - Path: uri.Path, - }, - List: &list, - }) - if err != nil { - getListFail.Inc(1) - log.Error(fmt.Sprintf("error rendering list HTML: %s", err)) - } - return - } - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(&list) -} - -// HandleGetFile handles a GET request to bzz:/// and responds -// with the content of the file at from the given -func (s *Server) HandleGetFile(w http.ResponseWriter, r *http.Request) { - ruid := GetRUID(r.Context()) - uri := GetURI(r.Context()) - _, credentials, _ := r.BasicAuth() - log.Debug("handle.get.file", "ruid", ruid, "uri", r.RequestURI) - getFileCount.Inc(1) - - // ensure the root path has a trailing slash so that relative URLs work - if uri.Path == "" && !strings.HasSuffix(r.URL.Path, "/") { - http.Redirect(w, r, r.URL.Path+"/", http.StatusMovedPermanently) - return - } - var err error - manifestAddr := uri.Address() - - if manifestAddr == nil { - manifestAddr, err = s.api.Resolve(r.Context(), uri.Addr) - if err != nil { - getFileFail.Inc(1) - respondError(w, r, fmt.Sprintf("cannot resolve %s: %s", uri.Addr, err), http.StatusNotFound) - return - } - } else { - w.Header().Set("Cache-Control", "max-age=2147483648, immutable") // url was of type bzz:///path, so we are sure it is immutable. - } - - log.Debug("handle.get.file: resolved", "ruid", ruid, "key", manifestAddr) - - reader, contentType, status, contentKey, err := s.api.Get(r.Context(), s.api.Decryptor(r.Context(), credentials), manifestAddr, uri.Path) - - etag := common.Bytes2Hex(contentKey) - noneMatchEtag := r.Header.Get("If-None-Match") - w.Header().Set("ETag", fmt.Sprintf("%q", etag)) // set etag to actual content key. - if noneMatchEtag != "" { - if bytes.Equal(storage.Address(common.Hex2Bytes(noneMatchEtag)), contentKey) { - w.WriteHeader(http.StatusNotModified) - return - } - } - - if err != nil { - if isDecryptError(err) { - w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", manifestAddr)) - respondError(w, r, err.Error(), http.StatusUnauthorized) - return - } - - switch status { - case http.StatusNotFound: - getFileNotFound.Inc(1) - respondError(w, r, err.Error(), http.StatusNotFound) - default: - getFileFail.Inc(1) - respondError(w, r, err.Error(), http.StatusInternalServerError) - } - return - } - - //the request results in ambiguous files - //e.g. /read with readme.md and readinglist.txt available in manifest - if status == http.StatusMultipleChoices { - list, err := s.api.GetManifestList(r.Context(), s.api.Decryptor(r.Context(), credentials), manifestAddr, uri.Path) - if err != nil { - getFileFail.Inc(1) - if isDecryptError(err) { - w.Header().Set("WWW-Authenticate", fmt.Sprintf("Basic realm=%q", manifestAddr)) - respondError(w, r, err.Error(), http.StatusUnauthorized) - return - } - respondError(w, r, err.Error(), http.StatusInternalServerError) - return - } - - log.Debug(fmt.Sprintf("Multiple choices! --> %v", list), "ruid", ruid) - //show a nice page links to available entries - ShowMultipleChoices(w, r, list) - return - } - - // check the root chunk exists by retrieving the file's size - if _, err := reader.Size(r.Context(), nil); err != nil { - getFileNotFound.Inc(1) - respondError(w, r, fmt.Sprintf("file not found %s: %s", uri, err), http.StatusNotFound) - return - } - - if contentType != "" { - w.Header().Set("Content-Type", contentType) - } - - fileName := uri.Addr - if found := path.Base(uri.Path); found != "" && found != "." && found != "/" { - fileName = found - } - w.Header().Set("Content-Disposition", fmt.Sprintf("inline; filename=\"%s\"", fileName)) - - http.ServeContent(w, r, fileName, time.Now(), newBufferedReadSeeker(reader, getFileBufferSize)) -} - -// The size of buffer used for bufio.Reader on LazyChunkReader passed to -// http.ServeContent in HandleGetFile. -// Warning: This value influences the number of chunk requests and chunker join goroutines -// per file request. -// Recommended value is 4 times the io.Copy default buffer value which is 32kB. -const getFileBufferSize = 4 * 32 * 1024 - -// bufferedReadSeeker wraps bufio.Reader to expose Seek method -// from the provied io.ReadSeeker in newBufferedReadSeeker. -type bufferedReadSeeker struct { - r io.Reader - s io.Seeker -} - -// newBufferedReadSeeker creates a new instance of bufferedReadSeeker, -// out of io.ReadSeeker. Argument `size` is the size of the read buffer. -func newBufferedReadSeeker(readSeeker io.ReadSeeker, size int) bufferedReadSeeker { - return bufferedReadSeeker{ - r: bufio.NewReaderSize(readSeeker, size), - s: readSeeker, - } -} - -func (b bufferedReadSeeker) Read(p []byte) (n int, err error) { - return b.r.Read(p) -} - -func (b bufferedReadSeeker) Seek(offset int64, whence int) (int64, error) { - return b.s.Seek(offset, whence) -} - -type loggingResponseWriter struct { - http.ResponseWriter - statusCode int -} - -func newLoggingResponseWriter(w http.ResponseWriter) *loggingResponseWriter { - return &loggingResponseWriter{w, http.StatusOK} -} - -func (lrw *loggingResponseWriter) WriteHeader(code int) { - lrw.statusCode = code - lrw.ResponseWriter.WriteHeader(code) -} - -func isDecryptError(err error) bool { - return strings.Contains(err.Error(), api.ErrDecrypt.Error()) -} diff --git a/swarm/api/http/server_test.go b/swarm/api/http/server_test.go deleted file mode 100644 index d02741a14270..000000000000 --- a/swarm/api/http/server_test.go +++ /dev/null @@ -1,1313 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package http - -import ( - "archive/tar" - "bytes" - "context" - "encoding/json" - "errors" - "flag" - "fmt" - "io" - "io/ioutil" - "math/big" - "mime/multipart" - "net/http" - "net/url" - "os" - "path" - "strconv" - "strings" - "testing" - "time" - - "github.com/ubiq/go-ubiq/swarm/storage/feed/lookup" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/core/types" - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/swarm/api" - swarm "github.com/ubiq/go-ubiq/swarm/api/client" - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/ubiq/go-ubiq/swarm/storage/feed" - "github.com/ubiq/go-ubiq/swarm/testutil" -) - -func init() { - loglevel := flag.Int("loglevel", 2, "loglevel") - flag.Parse() - log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(true))))) -} - -func serverFunc(api *api.API) TestServer { - return NewServer(api, "") -} - -func newTestSigner() (*feed.GenericSigner, error) { - privKey, err := crypto.HexToECDSA("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - if err != nil { - return nil, err - } - return feed.NewGenericSigner(privKey), nil -} - -// Test the transparent resolving of feed updates with bzz:// scheme -// -// First upload data to bzz:, and store the Swarm hash to the resulting manifest in a feed update. -// This effectively uses a feed to store a pointer to content rather than the content itself -// Retrieving the update with the Swarm hash should return the manifest pointing directly to the data -// and raw retrieve of that hash should return the data -func TestBzzWithFeed(t *testing.T) { - - signer, _ := newTestSigner() - - // Initialize Swarm test server - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - // put together some data for our test: - dataBytes := []byte(` - // - // Create some data our manifest will point to. Data that could be very big and wouldn't fit in a feed update. - // So what we are going to do is upload it to Swarm bzz:// and obtain a **manifest hash** pointing to it: - // - // MANIFEST HASH --> DATA - // - // Then, we store that **manifest hash** into a Swarm Feed update. Once we have done this, - // we can use the **feed manifest hash** in bzz:// instead, this way: bzz://feed-manifest-hash. - // - // FEED MANIFEST HASH --> MANIFEST HASH --> DATA - // - // Given that we can update the feed at any time with a new **manifest hash** but the **feed manifest hash** - // stays constant, we have effectively created a fixed address to changing content. (Applause) - // - // FEED MANIFEST HASH (the same) --> MANIFEST HASH(2) --> DATA(2) ... - // - `) - - // POST data to bzz and get back a content-addressed **manifest hash** pointing to it. - resp, err := http.Post(fmt.Sprintf("%s/bzz:/", srv.URL), "text/plain", bytes.NewReader([]byte(dataBytes))) - if err != nil { - t.Fatal(err) - } - - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - manifestAddressHex, err := ioutil.ReadAll(resp.Body) - - if err != nil { - t.Fatal(err) - } - - manifestAddress := common.FromHex(string(manifestAddressHex)) - - log.Info("added data", "manifest", string(manifestAddressHex)) - - // At this point we have uploaded the data and have a manifest pointing to it - // Now store that manifest address in a feed update. - // We also want a feed manifest, so we can use it to refer to the feed. - - // First, create a topic for our feed: - topic, _ := feed.NewTopic("interesting topic indeed", nil) - - // Create a feed update request: - updateRequest := feed.NewFirstRequest(topic) - - // Store the **manifest address** as data into the feed update. - updateRequest.SetData(manifestAddress) - - // Sign the update - if err := updateRequest.Sign(signer); err != nil { - t.Fatal(err) - } - log.Info("added data", "data", common.ToHex(manifestAddress)) - - // Build the feed update http request: - feedUpdateURL, err := url.Parse(fmt.Sprintf("%s/bzz-feed:/", srv.URL)) - if err != nil { - t.Fatal(err) - } - query := feedUpdateURL.Query() - body := updateRequest.AppendValues(query) // this adds all query parameters and returns the data to be posted - query.Set("manifest", "1") // indicate we want a feed manifest back - feedUpdateURL.RawQuery = query.Encode() - - // submit the feed update request to Swarm - resp, err = http.Post(feedUpdateURL.String(), "application/octet-stream", bytes.NewReader(body)) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - - feedManifestAddressHex, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - feedManifestAddress := &storage.Address{} - err = json.Unmarshal(feedManifestAddressHex, feedManifestAddress) - if err != nil { - t.Fatalf("data %s could not be unmarshaled: %v", feedManifestAddressHex, err) - } - - correctManifestAddrHex := "747c402e5b9dc715a25a4393147512167bab018a007fad7cdcd9adc7fce1ced2" - if feedManifestAddress.Hex() != correctManifestAddrHex { - t.Fatalf("Response feed manifest address mismatch, expected '%s', got '%s'", correctManifestAddrHex, feedManifestAddress.Hex()) - } - - // get bzz manifest transparent feed update resolve - getBzzURL := fmt.Sprintf("%s/bzz:/%s", srv.URL, feedManifestAddress) - resp, err = http.Get(getBzzURL) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - retrievedData, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(retrievedData, []byte(dataBytes)) { - t.Fatalf("retrieved data mismatch, expected %x, got %x", dataBytes, retrievedData) - } -} - -// Test Swarm feeds using the raw update methods -func TestBzzFeed(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - signer, _ := newTestSigner() - - defer srv.Close() - - // data of update 1 - update1Data := testutil.RandomBytes(1, 666) - update1Timestamp := srv.CurrentTime - //data for update 2 - update2Data := []byte("foo") - - topic, _ := feed.NewTopic("foo.eth", nil) - updateRequest := feed.NewFirstRequest(topic) - updateRequest.SetData(update1Data) - - if err := updateRequest.Sign(signer); err != nil { - t.Fatal(err) - } - - // creates feed and sets update 1 - testUrl, err := url.Parse(fmt.Sprintf("%s/bzz-feed:/", srv.URL)) - if err != nil { - t.Fatal(err) - } - urlQuery := testUrl.Query() - body := updateRequest.AppendValues(urlQuery) // this adds all query parameters - urlQuery.Set("manifest", "1") // indicate we want a manifest back - testUrl.RawQuery = urlQuery.Encode() - - resp, err := http.Post(testUrl.String(), "application/octet-stream", bytes.NewReader(body)) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - rsrcResp := &storage.Address{} - err = json.Unmarshal(b, rsrcResp) - if err != nil { - t.Fatalf("data %s could not be unmarshaled: %v", b, err) - } - - correctManifestAddrHex := "bb056a5264c295c2b0f613c8409b9c87ce9d71576ace02458160df4cc894210b" - if rsrcResp.Hex() != correctManifestAddrHex { - t.Fatalf("Response feed manifest mismatch, expected '%s', got '%s'", correctManifestAddrHex, rsrcResp.Hex()) - } - - // get the manifest - testRawUrl := fmt.Sprintf("%s/bzz-raw:/%s", srv.URL, rsrcResp) - resp, err = http.Get(testRawUrl) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - b, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - manifest := &api.Manifest{} - err = json.Unmarshal(b, manifest) - if err != nil { - t.Fatal(err) - } - if len(manifest.Entries) != 1 { - t.Fatalf("Manifest has %d entries", len(manifest.Entries)) - } - correctFeedHex := "0x666f6f2e65746800000000000000000000000000000000000000000000000000c96aaa54e2d44c299564da76e1cd3184a2386b8d" - if manifest.Entries[0].Feed.Hex() != correctFeedHex { - t.Fatalf("Expected manifest Feed '%s', got '%s'", correctFeedHex, manifest.Entries[0].Feed.Hex()) - } - - // take the chance to have bzz: crash on resolving a feed update that does not contain - // a swarm hash: - testBzzUrl := fmt.Sprintf("%s/bzz:/%s", srv.URL, rsrcResp) - resp, err = http.Get(testBzzUrl) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode == http.StatusOK { - t.Fatal("Expected error status since feed update does not contain a Swarm hash. Received 200 OK") - } - _, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - - // get non-existent name, should fail - testBzzResUrl := fmt.Sprintf("%s/bzz-feed:/bar", srv.URL) - resp, err = http.Get(testBzzResUrl) - if err != nil { - t.Fatal(err) - } - - if resp.StatusCode != http.StatusNotFound { - t.Fatalf("Expected get non-existent feed manifest to fail with StatusNotFound (404), got %d", resp.StatusCode) - } - - resp.Body.Close() - - // get latest update through bzz-feed directly - log.Info("get update latest = 1.1", "addr", correctManifestAddrHex) - testBzzResUrl = fmt.Sprintf("%s/bzz-feed:/%s", srv.URL, correctManifestAddrHex) - resp, err = http.Get(testBzzResUrl) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - b, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(update1Data, b) { - t.Fatalf("Expected body '%x', got '%x'", update1Data, b) - } - - // update 2 - // Move the clock ahead 1 second - srv.CurrentTime++ - log.Info("update 2") - - // 1.- get metadata about this feed - testBzzResUrl = fmt.Sprintf("%s/bzz-feed:/%s/", srv.URL, correctManifestAddrHex) - resp, err = http.Get(testBzzResUrl + "?meta=1") - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("Get feed metadata returned %s", resp.Status) - } - b, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - updateRequest = &feed.Request{} - if err = updateRequest.UnmarshalJSON(b); err != nil { - t.Fatalf("Error decoding feed metadata: %s", err) - } - updateRequest.SetData(update2Data) - if err = updateRequest.Sign(signer); err != nil { - t.Fatal(err) - } - testUrl, err = url.Parse(fmt.Sprintf("%s/bzz-feed:/", srv.URL)) - if err != nil { - t.Fatal(err) - } - urlQuery = testUrl.Query() - body = updateRequest.AppendValues(urlQuery) // this adds all query parameters - goodQueryParameters := urlQuery.Encode() // save the query parameters for a second attempt - - // create bad query parameters in which the signature is missing - urlQuery.Del("signature") - testUrl.RawQuery = urlQuery.Encode() - - // 1st attempt with bad query parameters in which the signature is missing - resp, err = http.Post(testUrl.String(), "application/octet-stream", bytes.NewReader(body)) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - expectedCode := http.StatusBadRequest - if resp.StatusCode != expectedCode { - t.Fatalf("Update returned %s. Expected %d", resp.Status, expectedCode) - } - - // 2nd attempt with bad query parameters in which the signature is of incorrect length - urlQuery.Set("signature", "0xabcd") // should be 130 hex chars - resp, err = http.Post(testUrl.String(), "application/octet-stream", bytes.NewReader(body)) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - expectedCode = http.StatusBadRequest - if resp.StatusCode != expectedCode { - t.Fatalf("Update returned %s. Expected %d", resp.Status, expectedCode) - } - - // 3rd attempt, with good query parameters: - testUrl.RawQuery = goodQueryParameters - resp, err = http.Post(testUrl.String(), "application/octet-stream", bytes.NewReader(body)) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - expectedCode = http.StatusOK - if resp.StatusCode != expectedCode { - t.Fatalf("Update returned %s. Expected %d", resp.Status, expectedCode) - } - - // get latest update through bzz-feed directly - log.Info("get update 1.2") - testBzzResUrl = fmt.Sprintf("%s/bzz-feed:/%s", srv.URL, correctManifestAddrHex) - resp, err = http.Get(testBzzResUrl) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - b, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(update2Data, b) { - t.Fatalf("Expected body '%x', got '%x'", update2Data, b) - } - - // test manifest-less queries - log.Info("get first update in update1Timestamp via direct query") - query := feed.NewQuery(&updateRequest.Feed, update1Timestamp, lookup.NoClue) - - urlq, err := url.Parse(fmt.Sprintf("%s/bzz-feed:/", srv.URL)) - if err != nil { - t.Fatal(err) - } - - values := urlq.Query() - query.AppendValues(values) // this adds feed query parameters - urlq.RawQuery = values.Encode() - resp, err = http.Get(urlq.String()) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - b, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(update1Data, b) { - t.Fatalf("Expected body '%x', got '%x'", update1Data, b) - } - -} - -func TestBzzGetPath(t *testing.T) { - testBzzGetPath(false, t) - testBzzGetPath(true, t) -} - -func testBzzGetPath(encrypted bool, t *testing.T) { - var err error - - testmanifest := []string{ - `{"entries":[{"path":"b","hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","contentType":"","status":0},{"path":"c","hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","contentType":"","status":0}]}`, - `{"entries":[{"path":"a","hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","contentType":"","status":0},{"path":"b/","hash":"","contentType":"application/bzz-manifest+json","status":0}]}`, - `{"entries":[{"path":"a/","hash":"","contentType":"application/bzz-manifest+json","status":0}]}`, - } - - testrequests := make(map[string]int) - testrequests["/"] = 2 - testrequests["/a/"] = 1 - testrequests["/a/b/"] = 0 - testrequests["/x"] = 0 - testrequests[""] = 0 - - expectedfailrequests := []string{"", "/x"} - - reader := [3]*bytes.Reader{} - - addr := [3]storage.Address{} - - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - for i, mf := range testmanifest { - reader[i] = bytes.NewReader([]byte(mf)) - var wait func(context.Context) error - ctx := context.TODO() - addr[i], wait, err = srv.FileStore.Store(ctx, reader[i], int64(len(mf)), encrypted) - if err != nil { - t.Fatal(err) - } - for j := i + 1; j < len(testmanifest); j++ { - testmanifest[j] = strings.Replace(testmanifest[j], fmt.Sprintf("", i), addr[i].Hex(), -1) - } - err = wait(ctx) - if err != nil { - t.Fatal(err) - } - } - - rootRef := addr[2].Hex() - - _, err = http.Get(srv.URL + "/bzz-raw:/" + rootRef + "/a") - if err != nil { - t.Fatalf("Failed to connect to proxy: %v", err) - } - - for k, v := range testrequests { - var resp *http.Response - var respbody []byte - - url := srv.URL + "/bzz-raw:/" - if k != "" { - url += rootRef + "/" + k[1:] + "?content_type=text/plain" - } - resp, err = http.Get(url) - if err != nil { - t.Fatalf("Request failed: %v", err) - } - defer resp.Body.Close() - respbody, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("Error while reading response body: %v", err) - } - - if string(respbody) != testmanifest[v] { - isexpectedfailrequest := false - - for _, r := range expectedfailrequests { - if k == r { - isexpectedfailrequest = true - } - } - if !isexpectedfailrequest { - t.Fatalf("Response body does not match, expected: %v, got %v", testmanifest[v], string(respbody)) - } - } - } - - for k, v := range testrequests { - var resp *http.Response - var respbody []byte - - url := srv.URL + "/bzz-hash:/" - if k != "" { - url += rootRef + "/" + k[1:] - } - resp, err = http.Get(url) - if err != nil { - t.Fatalf("Request failed: %v", err) - } - defer resp.Body.Close() - respbody, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("Read request body: %v", err) - } - - if string(respbody) != addr[v].Hex() { - isexpectedfailrequest := false - - for _, r := range expectedfailrequests { - if k == r { - isexpectedfailrequest = true - } - } - if !isexpectedfailrequest { - t.Fatalf("Response body does not match, expected: %v, got %v", addr[v], string(respbody)) - } - } - } - - ref := addr[2].Hex() - - for _, c := range []struct { - path string - json string - pageFragments []string - }{ - { - path: "/", - json: `{"common_prefixes":["a/"]}`, - pageFragments: []string{ - fmt.Sprintf("Swarm index of bzz:/%s/", ref), - `a/`, - }, - }, - { - path: "/a/", - json: `{"common_prefixes":["a/b/"],"entries":[{"hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","path":"a/a","mod_time":"0001-01-01T00:00:00Z"}]}`, - pageFragments: []string{ - fmt.Sprintf("Swarm index of bzz:/%s/a/", ref), - `b/`, - fmt.Sprintf(`a`, ref), - }, - }, - { - path: "/a/b/", - json: `{"entries":[{"hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","path":"a/b/b","mod_time":"0001-01-01T00:00:00Z"},{"hash":"011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce","path":"a/b/c","mod_time":"0001-01-01T00:00:00Z"}]}`, - pageFragments: []string{ - fmt.Sprintf("Swarm index of bzz:/%s/a/b/", ref), - fmt.Sprintf(`b`, ref), - fmt.Sprintf(`c`, ref), - }, - }, - { - path: "/x", - }, - { - path: "", - }, - } { - k := c.path - url := srv.URL + "/bzz-list:/" - if k != "" { - url += rootRef + "/" + k[1:] - } - t.Run("json list "+c.path, func(t *testing.T) { - resp, err := http.Get(url) - if err != nil { - t.Fatalf("HTTP request: %v", err) - } - defer resp.Body.Close() - respbody, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("Read response body: %v", err) - } - - body := strings.TrimSpace(string(respbody)) - if body != c.json { - isexpectedfailrequest := false - - for _, r := range expectedfailrequests { - if k == r { - isexpectedfailrequest = true - } - } - if !isexpectedfailrequest { - t.Errorf("Response list body %q does not match, expected: %v, got %v", k, c.json, body) - } - } - }) - t.Run("html list "+c.path, func(t *testing.T) { - req, err := http.NewRequest(http.MethodGet, url, nil) - if err != nil { - t.Fatalf("New request: %v", err) - } - req.Header.Set("Accept", "text/html") - resp, err := http.DefaultClient.Do(req) - if err != nil { - t.Fatalf("HTTP request: %v", err) - } - defer resp.Body.Close() - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("Read response body: %v", err) - } - - body := string(b) - - for _, f := range c.pageFragments { - if !strings.Contains(body, f) { - isexpectedfailrequest := false - - for _, r := range expectedfailrequests { - if k == r { - isexpectedfailrequest = true - } - } - if !isexpectedfailrequest { - t.Errorf("Response list body %q does not contain %q: body %q", k, f, body) - } - } - } - }) - } - - nonhashtests := []string{ - srv.URL + "/bzz:/name", - srv.URL + "/bzz-immutable:/nonhash", - srv.URL + "/bzz-raw:/nonhash", - srv.URL + "/bzz-list:/nonhash", - srv.URL + "/bzz-hash:/nonhash", - } - - nonhashresponses := []string{ - `cannot resolve name: no DNS to resolve name: "name"`, - `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, - `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, - `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, - `cannot resolve nonhash: no DNS to resolve name: "nonhash"`, - } - - for i, url := range nonhashtests { - var resp *http.Response - var respbody []byte - - resp, err = http.Get(url) - - if err != nil { - t.Fatalf("Request failed: %v", err) - } - defer resp.Body.Close() - respbody, err = ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatalf("ReadAll failed: %v", err) - } - if !strings.Contains(string(respbody), nonhashresponses[i]) { - t.Fatalf("Non-Hash response body does not match, expected: %v, got: %v", nonhashresponses[i], string(respbody)) - } - } -} - -func TestBzzTar(t *testing.T) { - testBzzTar(false, t) - testBzzTar(true, t) -} - -func testBzzTar(encrypted bool, t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - fileNames := []string{"tmp1.txt", "tmp2.lock", "tmp3.rtf"} - fileContents := []string{"tmp1textfilevalue", "tmp2lockfilelocked", "tmp3isjustaplaintextfile"} - - buf := &bytes.Buffer{} - tw := tar.NewWriter(buf) - defer tw.Close() - - for i, v := range fileNames { - size := int64(len(fileContents[i])) - hdr := &tar.Header{ - Name: v, - Mode: 0644, - Size: size, - ModTime: time.Now(), - Xattrs: map[string]string{ - "user.swarm.content-type": "text/plain", - }, - } - if err := tw.WriteHeader(hdr); err != nil { - t.Fatal(err) - } - - // copy the file into the tar stream - n, err := io.Copy(tw, bytes.NewBufferString(fileContents[i])) - if err != nil { - t.Fatal(err) - } else if n != size { - t.Fatal("size mismatch") - } - } - - //post tar stream - url := srv.URL + "/bzz:/" - if encrypted { - url = url + "encrypt" - } - req, err := http.NewRequest("POST", url, buf) - if err != nil { - t.Fatal(err) - } - req.Header.Add("Content-Type", "application/x-tar") - client := &http.Client{} - resp2, err := client.Do(req) - if err != nil { - t.Fatal(err) - } - if resp2.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp2.Status) - } - swarmHash, err := ioutil.ReadAll(resp2.Body) - resp2.Body.Close() - if err != nil { - t.Fatal(err) - } - - // now do a GET to get a tarball back - req, err = http.NewRequest("GET", fmt.Sprintf(srv.URL+"/bzz:/%s", string(swarmHash)), nil) - if err != nil { - t.Fatal(err) - } - req.Header.Add("Accept", "application/x-tar") - resp2, err = client.Do(req) - if err != nil { - t.Fatal(err) - } - defer resp2.Body.Close() - - if h := resp2.Header.Get("Content-Type"); h != "application/x-tar" { - t.Fatalf("Content-Type header expected: application/x-tar, got: %s", h) - } - - expectedFileName := string(swarmHash) + ".tar" - expectedContentDisposition := fmt.Sprintf("inline; filename=\"%s\"", expectedFileName) - if h := resp2.Header.Get("Content-Disposition"); h != expectedContentDisposition { - t.Fatalf("Content-Disposition header expected: %s, got: %s", expectedContentDisposition, h) - } - - file, err := ioutil.TempFile("", "swarm-downloaded-tarball") - if err != nil { - t.Fatal(err) - } - defer os.Remove(file.Name()) - _, err = io.Copy(file, resp2.Body) - if err != nil { - t.Fatalf("error getting tarball: %v", err) - } - file.Sync() - file.Close() - - tarFileHandle, err := os.Open(file.Name()) - if err != nil { - t.Fatal(err) - } - tr := tar.NewReader(tarFileHandle) - - for { - hdr, err := tr.Next() - if err == io.EOF { - break - } else if err != nil { - t.Fatalf("error reading tar stream: %s", err) - } - bb := make([]byte, hdr.Size) - _, err = tr.Read(bb) - if err != nil && err != io.EOF { - t.Fatal(err) - } - passed := false - for i, v := range fileNames { - if v == hdr.Name { - if string(bb) == fileContents[i] { - passed = true - break - } - } - } - if !passed { - t.Fatalf("file %s did not pass content assertion", hdr.Name) - } - } -} - -// TestBzzRootRedirect tests that getting the root path of a manifest without -// a trailing slash gets redirected to include the trailing slash so that -// relative URLs work as expected. -func TestBzzRootRedirect(t *testing.T) { - testBzzRootRedirect(false, t) -} -func TestBzzRootRedirectEncrypted(t *testing.T) { - testBzzRootRedirect(true, t) -} - -func testBzzRootRedirect(toEncrypt bool, t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - // create a manifest with some data at the root path - client := swarm.NewClient(srv.URL) - data := []byte("data") - file := &swarm.File{ - ReadCloser: ioutil.NopCloser(bytes.NewReader(data)), - ManifestEntry: api.ManifestEntry{ - Path: "", - ContentType: "text/plain", - Size: int64(len(data)), - }, - } - hash, err := client.Upload(file, "", toEncrypt) - if err != nil { - t.Fatal(err) - } - - // define a CheckRedirect hook which ensures there is only a single - // redirect to the correct URL - redirected := false - httpClient := http.Client{ - CheckRedirect: func(req *http.Request, via []*http.Request) error { - if redirected { - return errors.New("too many redirects") - } - redirected = true - expectedPath := "/bzz:/" + hash + "/" - if req.URL.Path != expectedPath { - return fmt.Errorf("expected redirect to %q, got %q", expectedPath, req.URL.Path) - } - return nil - }, - } - - // perform the GET request and assert the response - res, err := httpClient.Get(srv.URL + "/bzz:/" + hash) - if err != nil { - t.Fatal(err) - } - defer res.Body.Close() - if !redirected { - t.Fatal("expected GET /bzz:/ to redirect to /bzz:// but it didn't") - } - gotData, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(gotData, data) { - t.Fatalf("expected response to equal %q, got %q", data, gotData) - } -} - -func TestMethodsNotAllowed(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - databytes := "bar" - for _, c := range []struct { - url string - code int - }{ - { - url: fmt.Sprintf("%s/bzz-list:/", srv.URL), - code: http.StatusMethodNotAllowed, - }, { - url: fmt.Sprintf("%s/bzz-hash:/", srv.URL), - code: http.StatusMethodNotAllowed, - }, - { - url: fmt.Sprintf("%s/bzz-immutable:/", srv.URL), - code: http.StatusMethodNotAllowed, - }, - } { - res, _ := http.Post(c.url, "text/plain", bytes.NewReader([]byte(databytes))) - if res.StatusCode != c.code { - t.Fatalf("should have failed. requested url: %s, expected code %d, got %d", c.url, c.code, res.StatusCode) - } - } - -} - -func httpDo(httpMethod string, url string, reqBody io.Reader, headers map[string]string, verbose bool, t *testing.T) (*http.Response, string) { - // Build the Request - req, err := http.NewRequest(httpMethod, url, reqBody) - if err != nil { - t.Fatal(err) - } - for key, value := range headers { - req.Header.Set(key, value) - } - if verbose { - t.Log(req.Method, req.URL, req.Header, req.Body) - } - - // Send Request out - httpClient := &http.Client{} - res, err := httpClient.Do(req) - if err != nil { - t.Fatal(err) - } - - // Read the HTTP Body - buffer, err := ioutil.ReadAll(res.Body) - if err != nil { - t.Fatal(err) - } - defer res.Body.Close() - body := string(buffer) - - return res, body -} - -func TestGet(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - for _, testCase := range []struct { - uri string - method string - headers map[string]string - expectedStatusCode int - assertResponseBody string - verbose bool - }{ - { - uri: fmt.Sprintf("%s/", srv.URL), - method: "GET", - headers: map[string]string{"Accept": "text/html"}, - expectedStatusCode: http.StatusOK, - assertResponseBody: "Swarm: Serverless Hosting Incentivised Peer-To-Peer Storage And Content Distribution", - verbose: false, - }, - { - uri: fmt.Sprintf("%s/", srv.URL), - method: "GET", - headers: map[string]string{"Accept": "application/json"}, - expectedStatusCode: http.StatusOK, - assertResponseBody: "Swarm: Please request a valid ENS or swarm hash with the appropriate bzz scheme", - verbose: false, - }, - { - uri: fmt.Sprintf("%s/robots.txt", srv.URL), - method: "GET", - headers: map[string]string{"Accept": "text/html"}, - expectedStatusCode: http.StatusOK, - assertResponseBody: "User-agent: *\nDisallow: /", - verbose: false, - }, - { - uri: fmt.Sprintf("%s/nonexistent_path", srv.URL), - method: "GET", - headers: map[string]string{}, - expectedStatusCode: http.StatusNotFound, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz:asdf/", srv.URL), - method: "GET", - headers: map[string]string{}, - expectedStatusCode: http.StatusNotFound, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/tbz2/", srv.URL), - method: "GET", - headers: map[string]string{}, - expectedStatusCode: http.StatusNotFound, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz-rack:/", srv.URL), - method: "GET", - headers: map[string]string{}, - expectedStatusCode: http.StatusNotFound, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz-ls", srv.URL), - method: "GET", - headers: map[string]string{}, - expectedStatusCode: http.StatusNotFound, - verbose: false, - }} { - t.Run("GET "+testCase.uri, func(t *testing.T) { - res, body := httpDo(testCase.method, testCase.uri, nil, testCase.headers, testCase.verbose, t) - if res.StatusCode != testCase.expectedStatusCode { - t.Fatalf("expected status code %d but got %d", testCase.expectedStatusCode, res.StatusCode) - } - if testCase.assertResponseBody != "" && !strings.Contains(body, testCase.assertResponseBody) { - t.Fatalf("expected response to be: %s but got: %s", testCase.assertResponseBody, body) - } - }) - } -} - -func TestModify(t *testing.T) { - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - swarmClient := swarm.NewClient(srv.URL) - data := []byte("data") - file := &swarm.File{ - ReadCloser: ioutil.NopCloser(bytes.NewReader(data)), - ManifestEntry: api.ManifestEntry{ - Path: "", - ContentType: "text/plain", - Size: int64(len(data)), - }, - } - - hash, err := swarmClient.Upload(file, "", false) - if err != nil { - t.Fatal(err) - } - - for _, testCase := range []struct { - uri string - method string - headers map[string]string - requestBody []byte - expectedStatusCode int - assertResponseBody string - assertResponseHeaders map[string]string - verbose bool - }{ - { - uri: fmt.Sprintf("%s/bzz:/%s", srv.URL, hash), - method: "DELETE", - headers: map[string]string{}, - expectedStatusCode: http.StatusOK, - assertResponseBody: "8b634aea26eec353ac0ecbec20c94f44d6f8d11f38d4578a4c207a84c74ef731", - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz:/%s", srv.URL, hash), - method: "PUT", - headers: map[string]string{}, - expectedStatusCode: http.StatusMethodNotAllowed, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz-raw:/%s", srv.URL, hash), - method: "PUT", - headers: map[string]string{}, - expectedStatusCode: http.StatusMethodNotAllowed, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz:/%s", srv.URL, hash), - method: "PATCH", - headers: map[string]string{}, - expectedStatusCode: http.StatusMethodNotAllowed, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz-raw:/", srv.URL), - method: "POST", - headers: map[string]string{}, - requestBody: []byte("POSTdata"), - expectedStatusCode: http.StatusOK, - assertResponseHeaders: map[string]string{"Content-Length": "64"}, - verbose: false, - }, - { - uri: fmt.Sprintf("%s/bzz-raw:/encrypt", srv.URL), - method: "POST", - headers: map[string]string{}, - requestBody: []byte("POSTdata"), - expectedStatusCode: http.StatusOK, - assertResponseHeaders: map[string]string{"Content-Length": "128"}, - verbose: false, - }, - } { - t.Run(testCase.method+" "+testCase.uri, func(t *testing.T) { - reqBody := bytes.NewReader(testCase.requestBody) - res, body := httpDo(testCase.method, testCase.uri, reqBody, testCase.headers, testCase.verbose, t) - - if res.StatusCode != testCase.expectedStatusCode { - t.Fatalf("expected status code %d but got %d, %s", testCase.expectedStatusCode, res.StatusCode, body) - } - if testCase.assertResponseBody != "" && !strings.Contains(body, testCase.assertResponseBody) { - t.Log(body) - t.Fatalf("expected response %s but got %s", testCase.assertResponseBody, body) - } - for key, value := range testCase.assertResponseHeaders { - if res.Header.Get(key) != value { - t.Logf("expected %s=%s in HTTP response header but got %s", key, value, res.Header.Get(key)) - } - } - }) - } -} - -func TestMultiPartUpload(t *testing.T) { - // POST /bzz:/ Content-Type: multipart/form-data - verbose := false - // Setup Swarm - srv := NewTestSwarmServer(t, serverFunc, nil) - defer srv.Close() - - url := fmt.Sprintf("%s/bzz:/", srv.URL) - - buf := new(bytes.Buffer) - form := multipart.NewWriter(buf) - form.WriteField("name", "John Doe") - file1, _ := form.CreateFormFile("cv", "cv.txt") - file1.Write([]byte("John Doe's Credentials")) - file2, _ := form.CreateFormFile("profile_picture", "profile.jpg") - file2.Write([]byte("imaginethisisjpegdata")) - form.Close() - - headers := map[string]string{ - "Content-Type": form.FormDataContentType(), - "Content-Length": strconv.Itoa(buf.Len()), - } - res, body := httpDo("POST", url, buf, headers, verbose, t) - - if res.StatusCode != http.StatusOK { - t.Fatalf("expected POST multipart/form-data to return 200, but it returned %d", res.StatusCode) - } - if len(body) != 64 { - t.Fatalf("expected POST multipart/form-data to return a 64 char manifest but the answer was %d chars long", len(body)) - } -} - -// TestBzzGetFileWithResolver tests fetching a file using a mocked ENS resolver -func TestBzzGetFileWithResolver(t *testing.T) { - resolver := newTestResolveValidator("") - srv := NewTestSwarmServer(t, serverFunc, resolver) - defer srv.Close() - fileNames := []string{"dir1/tmp1.txt", "dir2/tmp2.lock", "dir3/tmp3.rtf"} - fileContents := []string{"tmp1textfilevalue", "tmp2lockfilelocked", "tmp3isjustaplaintextfile"} - - buf := &bytes.Buffer{} - tw := tar.NewWriter(buf) - - for i, v := range fileNames { - size := len(fileContents[i]) - hdr := &tar.Header{ - Name: v, - Mode: 0644, - Size: int64(size), - ModTime: time.Now(), - Xattrs: map[string]string{ - "user.swarm.content-type": "text/plain", - }, - } - if err := tw.WriteHeader(hdr); err != nil { - t.Fatal(err) - } - - // copy the file into the tar stream - n, err := io.WriteString(tw, fileContents[i]) - if err != nil { - t.Fatal(err) - } else if n != size { - t.Fatal("size mismatch") - } - } - - if err := tw.Close(); err != nil { - t.Fatal(err) - } - - //post tar stream - url := srv.URL + "/bzz:/" - - req, err := http.NewRequest("POST", url, buf) - if err != nil { - t.Fatal(err) - } - req.Header.Add("Content-Type", "application/x-tar") - client := &http.Client{} - serverResponse, err := client.Do(req) - if err != nil { - t.Fatal(err) - } - if serverResponse.StatusCode != http.StatusOK { - t.Fatalf("err %s", serverResponse.Status) - } - swarmHash, err := ioutil.ReadAll(serverResponse.Body) - serverResponse.Body.Close() - if err != nil { - t.Fatal(err) - } - // set the resolved hash to be the swarm hash of what we've just uploaded - hash := common.HexToHash(string(swarmHash)) - resolver.hash = &hash - for _, v := range []struct { - addr string - path string - expectedStatusCode int - expectedContentType string - expectedFileName string - }{ - { - addr: string(swarmHash), - path: fileNames[0], - expectedStatusCode: http.StatusOK, - expectedContentType: "text/plain", - expectedFileName: path.Base(fileNames[0]), - }, - { - addr: "somebogusensname", - path: fileNames[0], - expectedStatusCode: http.StatusOK, - expectedContentType: "text/plain", - expectedFileName: path.Base(fileNames[0]), - }, - } { - req, err := http.NewRequest("GET", fmt.Sprintf(srv.URL+"/bzz:/%s/%s", v.addr, v.path), nil) - if err != nil { - t.Fatal(err) - } - serverResponse, err := client.Do(req) - if err != nil { - t.Fatal(err) - } - defer serverResponse.Body.Close() - if serverResponse.StatusCode != v.expectedStatusCode { - t.Fatalf("expected %d, got %d", v.expectedStatusCode, serverResponse.StatusCode) - } - - if h := serverResponse.Header.Get("Content-Type"); h != v.expectedContentType { - t.Fatalf("Content-Type header expected: %s, got %s", v.expectedContentType, h) - } - - expectedContentDisposition := fmt.Sprintf("inline; filename=\"%s\"", v.expectedFileName) - if h := serverResponse.Header.Get("Content-Disposition"); h != expectedContentDisposition { - t.Fatalf("Content-Disposition header expected: %s, got: %s", expectedContentDisposition, h) - } - - } -} - -// testResolver implements the Resolver interface and either returns the given -// hash if it is set, or returns a "name not found" error -type testResolveValidator struct { - hash *common.Hash -} - -func newTestResolveValidator(addr string) *testResolveValidator { - r := &testResolveValidator{} - if addr != "" { - hash := common.HexToHash(addr) - r.hash = &hash - } - return r -} - -func (t *testResolveValidator) Resolve(addr string) (common.Hash, error) { - if t.hash == nil { - return common.Hash{}, fmt.Errorf("DNS name not found: %q", addr) - } - return *t.hash, nil -} - -func (t *testResolveValidator) Owner(node [32]byte) (addr common.Address, err error) { - return -} -func (t *testResolveValidator) HeaderByNumber(context.Context, *big.Int) (header *types.Header, err error) { - return -} diff --git a/swarm/api/http/templates.go b/swarm/api/http/templates.go deleted file mode 100644 index 1b195c67c324..000000000000 --- a/swarm/api/http/templates.go +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package http - -import ( - "encoding/hex" - "fmt" - "html/template" - "path" - - "github.com/ubiq/go-ubiq/swarm/api" -) - -type htmlListData struct { - URI *api.URI - List *api.ManifestList -} - -var TemplatesMap = make(map[string]*template.Template) -var faviconBytes []byte - -func init() { - for _, v := range []struct { - templateName string - partial string - funcs template.FuncMap - }{ - { - templateName: "error", - partial: errorResponse, - }, - { - templateName: "bzz-list", - partial: bzzList, - funcs: template.FuncMap{ - "basename": path.Base, - "leaflink": leafLink, - }, - }, - { - templateName: "landing-page", - partial: landing, - }, - } { - TemplatesMap[v.templateName] = template.Must(template.New(v.templateName).Funcs(v.funcs).Parse(baseTemplate + css + v.partial + logo)) - } - - bytes, err := hex.DecodeString(favicon) - if err != nil { - panic(err) - } - faviconBytes = bytes -} - -func leafLink(URI api.URI, manifestEntry api.ManifestEntry) string { - return fmt.Sprintf("/bzz:/%s/%s", URI.Addr, manifestEntry.Path) -} - -const bzzList = `{{ define "content" }} -

Swarm index of {{ .URI }}

-
- - - - - - - - - - - {{ range .List.CommonPrefixes }} - - - - - - {{ end }} - {{ range .List.Entries }} - - - - - - {{ end }} -
PathTypeSize
- {{ basename . }}/ - DIR-
- {{ basename .Path }} - {{ .ContentType }}{{ .Size }}
-
- - {{ end }}` - -const errorResponse = `{{ define "content" }} -
- - -
-

{{.Msg}}

-
- -
-
Error code: {{.Code}}
-
- - -
-{{ end }}` - -const landing = `{{ define "content" }} - - - -
- - - - - -
- -{{ end }}` - -const baseTemplate = ` - - - - - - - - - {{ template "content" . }} - - -` - -const css = `{{ define "css" }} -html { - font-size: 18px; - font-size: 1.13rem; - -webkit-text-size-adjust: 100%; - -ms-text-size-adjust: 100%; - font-family: Helvetica, Arial, sans-serif; -} - -body { - background: #f6f6f6; - color: #333; -} - -a, a:visited, a:active { - color: darkorange; -} - -a.normal-link, a.normal-link:active { color: #0000EE; } -a.normal-link:visited { color: #551A8B; } - -table { - border-collapse: separate; -} - -td { - padding: 3px 10px; -} - - -.container { - max-width: 600px; - margin: 40px auto 40px; - text-align: center; -} - -.separate-block { - margin: 40px 0; - word-wrap: break-word; -} - -.footer { - font-size: 12px; - font-size: 0.75rem; - text-align: center; -} - -.orange { - color: #ffa500; -} - -.top-space { - margin-top: 20px; - margin-bottom: 20px; -} - -/* SVG Logos, editable */ - -.searchbar { - padding: 20px 20px 0; -} - -.logo { - margin: 100px 80px 0; -} - -.logo a img { - max-width: 140px; -} - -/* Tablet < 600p*/ - -@media only screen and (max-width: 600px) {} - -/* Mobile phone < 360p*/ - -@media only screen and (max-width: 360px) { - h1 { - font-size: 20px; - font-size: 1.5rem; - } - h2 { - font-size: 0.88rem; - margin: 0; - } - .logo { - margin: 50px 40px 0; - } - .footer { - font-size: 0.63rem; - text-align: center; - } -} - -input[type=text] { - width: 100%; - box-sizing: border-box; - border: 2px solid #777; - border-radius: 2px; - font-size: 16px; - padding: 12px 20px 12px 20px; - transition: border 250ms ease-in-out; -} - -input[type=text]:focus { - border: 2px solid #ffce73; -} - -.button { - background-color: #ffa500; - margin: 20px 0; - border: none; - border-radius: 2px; - color: #222; - padding: 15px 32px; - text-align: center; - text-decoration: none; - display: inline-block; - font-size: 16px; -} -{{ end }}` - -const logo = `{{ define "logo" }} - -{{ end }}` - -const favicon = `000001000400101000000000200068040000460000002020000000002000a8100000ae0400003030000000002000a825000056150000404000000000200028420000fe3a000028000000100000002000000001002000000000004004000000000000000000000000000000000000ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017e7e7e0362626263545454c548484849ffffff01ffffff01ffffff01ffffff01646464375b5b5bbf4545457758585809ffffff01ffffff01ffffff0164646443626262cf626262ff535353ff454545ff454545b74949492b6868681d626262a5626262fd5c5c5cff464646ff454545dd47474755ffffff01ffffff013f3f3feb565656ff636363ff535353ff464646ff3f3f3fff373737ab393939894d4d4dff626262ff5c5c5cff464646ff424242ff3a3a3af7ffffff01ffffff01383838e9353535ff424242ff474747ff383838ff353535ff363636ab35353587363636ff3a3a3aff4a4a4aff3b3b3bff353535ff363636f5ffffff01ffffff01383838e9303030ff181818ff131313ff232323ff343434ff363636ab35353587343434ff202020ff101010ff1d1d1dff303030ff373737f5ffffff01ffffff01232323c50c0c0cff0d0d0dff131313ff171717ff171717ff2929298b2727276b0f0f0ffd0d0d0dff101010ff171717ff161616ff232323d9ffffff01ffffff014d4d4d030f0f0f650c0c0ce7131313ff161616d51d1d1d4b63636363464646691717173b0d0d0dc50f0f0fff161616ef171717752e2e2e07ffffff01ffffff01ffffff01ffffff011d1d1d0f1515155360606045626262cf636363ff464646ff454545d3484848491414144d24242417ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013c3c3c374f4f4fff636363ff636363ff464646ff464646ff3f3f3fff3c3c3c41ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013636363d353535ff3c3c3cff575757ff363636ff181818ff282828ff37373747ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013636363d363636ff303030ff181818ff292929ff131313ef17171771696969136565653bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01323232371e1e1eff0d0d0dff0c0c0cff363636ff363636a3ffffff0185858515606060ff4747476bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01111111450d0d0dd10c0c0cff1b1b1bff2a2a2a993e3e3e0b30303085292929ff37373787ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636030e0e0e671616166b45454505323232432e2e2ed9151515c31d1d1d2dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014e4e4e05ffffff01ffffff01ffffff01ffffff010000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff28000000200000004000000001002000000000008010000000000000000000000000000000000000ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017272721b646464a54646466f72727205ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0168686845575757b74f4f4f39ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017e7e7e0b6262627d616161f3636363ff424242ff444444d74f4f4f49ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff016c6c6c27636363b5616161ff555555ff434343ff464646a35858581dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff016666665d616161e3626262ff636363ff636363ff444444ff464646ff434343ff454545b95252522bffffff01ffffff01ffffff01ffffff016c6c6c1363636393616161fb636363ff636363ff555555ff464646ff464646ff444444f5464646836666660bffffff01ffffff01ffffff01ffffff01ffffff016a6a6a3f626262c9616161ff636363ff636363ff636363ff636363ff444444ff464646ff464646ff464646ff434343fb48484897545454135b5b5b036868686f616161ef626262ff636363ff636363ff636363ff555555ff464646ff464646ff464646ff454545ff444444e54a4a4a5fffffff01ffffff01ffffff01ffffff013b3b3bd7505050ff646464ff636363ff636363ff636363ff636363ff444444ff464646ff464646ff464646ff454545ff3a3a3aff33333357313131113c3c3cff5a5a5aff646464ff636363ff636363ff636363ff555555ff464646ff464646ff464646ff464646ff424242ff383838f1ffffff01ffffff01ffffff01ffffff013a3a3ad5353535ff3a3a3aff575757ff646464ff626262ff636363ff444444ff464646ff464646ff3d3d3dff353535ff363636ff3636365535353511363636ff343434ff434343ff606060ff636363ff636363ff555555ff464646ff464646ff444444ff393939ff353535ff373737edffffff01ffffff01ffffff01ffffff013a3a3ad5363636ff363636ff343434ff3f3f3fff5d5d5dff646464ff444444ff404040ff363636ff353535ff363636ff363636ff3636365535353511363636ff363636ff363636ff343434ff4a4a4aff636363ff555555ff454545ff3c3c3cff353535ff363636ff363636ff373737edffffff01ffffff01ffffff01ffffff013a3a3ad5363636ff363636ff363636ff363636ff353535ff3f3f3fff363636ff353535ff363636ff363636ff363636ff363636ff3636365535353511363636ff363636ff363636ff363636ff353535ff383838ff3a3a3aff373737ff353535ff363636ff363636ff363636ff373737edffffff01ffffff01ffffff01ffffff013a3a3ad5363636ff363636ff363636ff323232ff181818ff0e0e0eff171717ff282828ff373737ff363636ff363636ff363636ff3636365535353511363636ff363636ff353535ff373737ff292929ff0f0f0fff111111ff1b1b1bff2f2f2fff373737ff363636ff363636ff373737edffffff01ffffff01ffffff01ffffff013a3a3ad5363636ff363636ff1e1e1eff0b0b0bff0d0d0dff0f0f0fff171717ff161616ff191919ff2c2c2cff373737ff363636ff3636365535353511363636ff373737ff2f2f2fff141414ff0b0b0bff0d0d0dff131313ff171717ff151515ff1f1f1fff333333ff363636ff373737edffffff01ffffff01ffffff01ffffff013b3b3bd5252525ff0d0d0dff0c0c0cff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff151515ff1c1c1cff313131ff3535355734343411333333ff1a1a1aff0b0b0bff0d0d0dff0d0d0dff0d0d0dff131313ff171717ff171717ff171717ff161616ff242424ff373737efffffff01ffffff01ffffff01ffffff012020205d0b0b0be50b0b0bff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff131313ff161616b73333331f3b3b3b05111111970a0a0afb0d0d0dff0d0d0dff0d0d0dff0d0d0dff131313ff171717ff171717ff171717ff161616ff141414f51c1c1c7fffffff01ffffff01ffffff01ffffff01ffffff014d4d4d0b1212127f0a0a0af50d0d0dff0d0d0dff0f0f0fff171717ff171717ff151515ff151515d522222249ffffff017373731b51515121ffffff011d1d1d2b101010b50a0a0aff0d0d0dff0d0d0dff131313ff171717ff171717ff131313ff181818a12e2e2e1dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff012c2c2c1b0f0f0fa10a0a0afd0f0f0fff161616ff141414e91b1b1b69656565057878780b6363637b626262f3464646f7454545896969690fffffff011c1c1c470c0c0cd30b0b0bff131313ff141414ff151515c32a2a2a37ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff011d1d1d35111111bd1a1a1a8d2f2f2f11ffffff0166666659616161e1626262ff646464ff474747ff454545ff444444e9494949677b7b7b054040400517171769131313cd24242455ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0169696939626262c7616161ff636363ff636363ff646464ff474747ff464646ff464646ff444444ff454545d14e4e4e45ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01424242615e5e5eff636363ff636363ff636363ff636363ff646464ff474747ff464646ff464646ff464646ff464646ff434343ff3f3f3f77ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0136363679343434ff494949ff636363ff636363ff636363ff646464ff474747ff464646ff464646ff474747ff3d3d3dff353535ff3a3a3a8dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0136363679363636ff353535ff363636ff505050ff646464ff636363ff474747ff484848ff2f2f2fff1c1c1cff323232ff363636ff3a3a3a8dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0136363679363636ff363636ff363636ff353535ff3a3a3aff5a5a5aff393939ff0f0f0fff040404ff111111ff151515ff232323ff3535358fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0136363679363636ff363636ff363636ff363636ff323232ff171717ff2a2a2aff0c0c0cff030303ff111111ff141414fb171717992e2e2e17a3a3a305ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0136363679363636ff363636ff363636ff1f1f1fff0b0b0bff0d0d0dff363636ff383838ff242424ff121212bf2a2a2a2dffffff01ffffff018484842b636363bf6d6d6d2fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0136363679373737ff252525ff0d0d0dff0c0c0cff0d0d0dff0d0d0dff373737ff363636ff353535ff39393949ffffff01ffffff01ffffff0186868629646464ff656565fb6464649b55555505ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff012e2e2e650e0e0eff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0c0c0cff353535ff363636ff353535ff37373749ffffff01ffffff01ffffff0185858529656565ff525252ff353535ff4b4b4b0fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff011c1c1c430d0d0dcf0b0b0bff0d0d0dff0d0d0dff0d0d0dff171717ff282828ff363636ff37373749ffffff01ffffff01ffffff0144444459363636ff353535ff353535ff4e4e4e0fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0162626203161616630b0b0be70c0c0cff0d0d0dff171717ff161616ff171717ed3737372fffffff013e3e3e2b303030b72a2a2aff151515ff262626ff363636ff4b4b4b0fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013636360d101010850a0a0af7141414f91717178f45454511ffffff014c4c4c252c2c2cdb303030ff2d2d2dff151515ff131313ff1b1b1bad5a5a5a07ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff012b2b2b2121212127ffffff01ffffff01ffffff01ffffff0161616109313131752b2b2bf1131313cd26262641ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014e4e4e1359595903ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000028000000300000006000000001002000000000008025000000000000000000000000000000000000ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0173737357545454997c7c7c11ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0176767663515151916c6c6c0dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017676762d636363bb636363ff4d4d4dff434343eb4f4f4f6d7f7f7f05ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0176767635616161c3626262ff494949ff424242e94f4f4f6392929203ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017e7e7e19626262955f5f5ffd626262ff666666ff4f4f4fff464646ff424242ff434343d75a5a5a49ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017777771d6464649f5f5f5fff636363ff656565ff4b4b4bff464646ff424242ff444444d158585841ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff018585850966666677606060ef626262ff636363ff636363ff666666ff4f4f4fff464646ff464646ff464646ff414141ff464646b75d5d5d2dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff018989890d6868687f5f5f5ff5626262ff636363ff636363ff656565ff4b4b4bff464646ff464646ff464646ff404040ff484848b160606027ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff016a6a6a55626262df606060ff636363ff636363ff636363ff636363ff666666ff4f4f4fff464646ff464646ff464646ff464646ff454545ff424242fd484848956a6a6a17ffffff01ffffff01ffffff01ffffff01ffffff016969695f606060e3606060ff636363ff636363ff636363ff636363ff656565ff4b4b4bff464646ff464646ff464646ff464646ff454545ff414141f94a4a4a8d65656513ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff016e6e6e3b656565c15f5f5fff636363ff636363ff636363ff636363ff636363ff636363ff666666ff4f4f4fff464646ff464646ff464646ff464646ff464646ff464646ff444444ff424242ed52525277ffffff01ffffff016c6c6c37676767c95f5f5fff636363ff636363ff636363ff636363ff636363ff636363ff656565ff4b4b4bff464646ff464646ff464646ff464646ff464646ff464646ff434343ff444444e94d4d4d6dffffff01ffffff01ffffff01ffffff01ffffff01ffffff013c3c3cc5454545ff646464ff646464ff636363ff636363ff636363ff636363ff636363ff666666ff4f4f4fff464646ff464646ff464646ff464646ff464646ff464646ff474747ff424242ff333333fb34343409ffffff0131313199494949ff656565ff646464ff636363ff636363ff636363ff636363ff636363ff656565ff4b4b4bff464646ff464646ff464646ff464646ff464646ff464646ff474747ff414141ff373737ebffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf333333ff343434ff4f4f4fff666666ff636363ff636363ff636363ff636363ff666666ff4f4f4fff464646ff464646ff464646ff464646ff474747ff444444ff383838ff343434ff363636f737373707ffffff0135353597343434ff343434ff525252ff666666ff636363ff636363ff636363ff636363ff656565ff4b4b4bff464646ff464646ff464646ff464646ff474747ff444444ff383838ff343434ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf343434ff363636ff333333ff383838ff585858ff676767ff636363ff636363ff666666ff4f4f4fff464646ff464646ff474747ff464646ff3b3b3bff343434ff363636ff363636ff363636f737373707ffffff0135353597363636ff363636ff333333ff383838ff5a5a5aff666666ff636363ff636363ff656565ff4b4b4bff464646ff464646ff474747ff454545ff3a3a3aff343434ff363636ff363636ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf343434ff363636ff363636ff363636ff323232ff3d3d3dff5d5d5dff666666ff666666ff4f4f4fff464646ff474747ff3e3e3eff353535ff353535ff363636ff363636ff363636ff363636f737373707ffffff0135353597363636ff363636ff363636ff363636ff313131ff3f3f3fff5f5f5fff666666ff656565ff4b4b4bff464646ff474747ff3d3d3dff353535ff353535ff363636ff363636ff363636ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf343434ff363636ff363636ff363636ff363636ff353535ff323232ff444444ff676767ff525252ff404040ff363636ff353535ff363636ff363636ff363636ff363636ff363636ff363636f737373707ffffff0135353597363636ff363636ff363636ff363636ff363636ff353535ff323232ff464646ff676767ff4e4e4eff404040ff363636ff353535ff363636ff363636ff363636ff363636ff363636ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf343434ff363636ff363636ff363636ff363636ff363636ff353535ff383838ff2d2d2dff2b2b2bff373737ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636f737373707ffffff0135353597363636ff363636ff363636ff363636ff363636ff363636ff363636ff383838ff2c2c2cff2a2a2aff373737ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf343434ff363636ff363636ff363636ff353535ff383838ff343434ff171717ff090909ff151515ff171717ff2d2d2dff383838ff363636ff363636ff363636ff363636ff363636ff363636f737373707ffffff0135353597363636ff363636ff363636ff363636ff353535ff383838ff333333ff151515ff090909ff151515ff181818ff2f2f2fff383838ff363636ff363636ff363636ff363636ff363636ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf343434ff363636ff363636ff373737ff373737ff1f1f1fff090909ff0c0c0cff0c0c0cff171717ff171717ff141414ff1b1b1bff323232ff383838ff363636ff363636ff363636ff363636f737373707ffffff0135353597363636ff363636ff363636ff373737ff373737ff1d1d1dff0a0a0aff0c0c0cff0c0c0cff171717ff171717ff141414ff1c1c1cff333333ff383838ff353535ff363636ff363636ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf343434ff363636ff393939ff272727ff0c0c0cff0b0b0bff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff161616ff141414ff202020ff353535ff373737ff363636ff363636f737373707ffffff0135353597363636ff363636ff383838ff252525ff0b0b0bff0b0b0bff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff161616ff141414ff222222ff363636ff373737ff363636ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040bf383838ff2d2d2dff101010ff0a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff161616ff141414ff262626ff373737ff373737f737373707ffffff0136363697393939ff2b2b2bff0f0f0fff0a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff161616ff151515ff272727ff383838ff393939e3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013a3a3abd131313ff090909ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff171717ff171717ff151515ff171717ff262626fb38383807ffffff012a2a2a97121212ff090909ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff171717ff171717ff151515ff161616ff2a2a2ae7ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015f5f5f0b1616167b090909ef0a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff171717ff171717ff0f0f0fff181818b74040402dffffff01ffffff014646461118181883080808f30b0b0bff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff171717ff161616ff101010ff181818b141414127ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014d4d4d171212129b090909fd0c0c0cff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff111111ff141414d335353547ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013838381d131313a5060606ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff171717ff171717ff111111ff181818cd2e2e2e3dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01333333310f0f0fbb070707ff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff141414ff121212e72424246d86868603ffffff01ffffff017373732b656565b9464646c95e5e5e3bffffff01ffffff01ffffff01323232370e0e0ec3080808ff0d0d0dff0d0d0dff0c0c0cff171717ff171717ff171717ff121212ff161616e525252563ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff012525254d0e0e0ed9090909ff0c0c0cff171717ff151515ff121212f91d1d1d894d4d4d13ffffff01ffffff0178787815656565935f5f5ffb646464ff484848ff404040ff454545a96a6a6a1fffffff01ffffff01ffffff011b1b1b570e0e0edf080808ff0d0d0dff171717ff151515ff0f0f0ff3212121815656560dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01636363071a1a1a710a0a0aed0f0f0fff1b1b1bad2f2f2f23ffffff01ffffff018d8d8d0566666675616161eb616161ff636363ff646464ff484848ff464646ff454545ff424242f54c4c4c856262620fffffff01ffffff014040400b21212179080808f10f0f0fff1b1b1ba15757571dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014141411740404037ffffff01ffffff01ffffff016a6a6a4d616161db606060ff636363ff636363ff636363ff646464ff484848ff464646ff464646ff464646ff434343ff434343e751515167ffffff01ffffff01ffffff014646461d30303033ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0176767631616161c35f5f5fff636363ff636363ff636363ff636363ff636363ff646464ff484848ff464646ff464646ff464646ff464646ff464646ff424242ff454545d158585841ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015252527f636363ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff646464ff484848ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff434343ff454545a1ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01313131b53b3b3bff5b5b5bff676767ff636363ff636363ff636363ff636363ff636363ff646464ff484848ff464646ff464646ff464646ff464646ff464646ff474747ff444444ff393939ff383838d3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff323232ff404040ff616161ff656565ff626262ff636363ff636363ff646464ff484848ff464646ff464646ff454545ff494949ff474747ff3b3b3bff343434ff353535ff3a3a3ad3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff363636ff353535ff323232ff484848ff656565ff646464ff636363ff646464ff484848ff464646ff474747ff494949ff242424ff282828ff383838ff363636ff363636ff3a3a3ad3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff363636ff363636ff363636ff343434ff343434ff515151ff666666ff656565ff484848ff4b4b4bff323232ff070707ff040404ff151515ff181818ff2f2f2fff383838ff3a3a3ad3ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff363636ff363636ff363636ff363636ff363636ff333333ff383838ff5f5f5fff3c3c3cff0f0f0fff020202ff050505ff050505ff171717ff171717ff141414ff1c1c1cff323232d7ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff363636ff363636ff363636ff363636ff353535ff383838ff343434ff161616ff2a2a2aff0c0c0cff020202ff050505ff050505ff171717ff171717ff101010ff161616bf2e2e2e35ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff363636ff363636ff363636ff373737ff383838ff1f1f1fff0a0a0aff0c0c0cff373737ff3a3a3aff262626ff060606ff040404ff121212ff151515dd30303051ffffff01ffffff01ffffff018787872d6b6b6b47ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff363636ff363636ff393939ff272727ff0d0d0dff0b0b0bff0d0d0dff0d0d0dff373737ff363636ff373737ff383838ff1c1c1cf92020207568686807ffffff01ffffff01ffffff01ffffff018686863d5f5f5fff676767af77777721ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01363636b3363636ff393939ff2e2e2eff101010ff0a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff373737ff363636ff363636ff353535ff373737ebffffff01ffffff01ffffff01ffffff01ffffff01ffffff018686863d626262ff666666ff646464f76969698d9494940fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01383838b5333333ff161616ff090909ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff373737ff363636ff363636ff363636ff353535ebffffff01ffffff01ffffff01ffffff01ffffff01ffffff018686863d626262ff676767ff6b6b6bff555555ff3a3a3a93ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0125252589030303ff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff333333ff383838ff353535ff363636ff353535ebffffff01ffffff01ffffff01ffffff01ffffff01ffffff018585853d666666ff5f5f5fff3c3c3cff313131ff3a3a3a93ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff012d2d2d3f0e0e0ecb080808ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff141414ff222222ff363636ff373737ff353535ebffffff01ffffff01ffffff01ffffff01ffffff01ffffff0177777741414141ff313131ff363636ff353535ff3a3a3a93ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff011e1e1e5f0a0a0ae50a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff171717ff161616ff151515ff282828ff353535f3ffffff01ffffff01ffffff01ffffff016e6e6e0b37373781242424f1191919ff333333ff383838ff343434ff3a3a3a93ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015a5a5a0d1919197f0a0a0af30b0b0bff0d0d0dff0d0d0dff171717ff171717ff161616ff0f0f0ffb24242489ffffff01ffffff01ffffff013e3e3e5d2d2d2de52e2e2eff2b2b2bff151515ff141414ff212121ff363636ff3b3b3b95ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013636361b111111a3080808ff0c0c0cff181818ff0f0f0fff171717b545454525ffffff01ffffff017f7f7f05363636c7282828ff313131ff313131ff2b2b2bff151515ff171717ff161616ff0c0c0cfb3434346bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01303030350f0f0fc7121212d337373741ffffff01ffffff01ffffff01ffffff01ffffff016b6b6b0b3a3a3a7d2c2c2cf12f2f2fff2b2b2bff151515ff101010ff171717bb4646462dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01515151193535359b242424ff131313d72828284bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014e4e4e2b59595905ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff000000000000ffff28000000400000008000000001002000000000000042000000000000000000000000000000000000ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0176767635666666914e4e4e457c7c7c09ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff018080801569696989545454696c6c6c0bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff018484840d70707061616161d5606060fb3d3d3ddf4e4e4e9172727213ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017070704d626262b35f5f5ffb464646f1454545a16a6a6a33ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017676760f67676753646464cf5e5e5eff656565ff626262ff414141ff404040ff444444e54b4b4b7b69696919ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01979797036c6c6c45676767a95d5d5dff616161ff626262ff484848ff424242ff3e3e3efd4e4e4e8958585831ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017e7e7e2b616161a75f5f5fef616161ff636363ff656565ff626262ff424242ff464646ff444444ff414141fd434343b961616153ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017777771969696981606060e7606060ff636363ff636363ff626262ff484848ff464646ff454545ff424242fd414141d95656566569696911ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01858585056e6e6e29656565995f5f5ff1616161ff636363ff636363ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff444444ff3f3f3fff484848af5353534b86868607ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01797979216a6a6a6f616161ed5e5e5eff636363ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff464646ff3e3e3eff474747d75151515762626213ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01838383036f6f6f755f5f5fd3606060ff626262ff636363ff636363ff636363ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff464646ff454545ff434343ff404040e94e4e4e8d5f5f5f1bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff018f8f8f056b6b6b45616161c95f5f5ff7616161ff636363ff636363ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff464646ff464646ff444444ff424242f1434343b16666662dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017070700f6969695f626262d35e5e5eff626262ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff404040ff444444f14d4d4d776a6a6a23ffffff01ffffff01ffffff01ffffff017b7b7b096c6c6c39636363c15f5f5ffb626262ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff434343ff414141f54a4a4aa35b5b5b2d70707007ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0171717143676767a7616161f3616161ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff444444ff414141f7474747cd54545447ffffff01ffffff015b5b5b096b6b6b99646464e1606060ff626262ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff444444ff424242ff414141d552525277ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01404040b33b3b3bff5c5c5cff656565ff646464ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff474747ff454545ff3a3a3aff313131ad34343407ffffff012e2e2e25383838ff535353ff656565ff656565ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff474747ff464646ff3b3b3bff3a3a3ae9ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9313131ff363636ff484848ff636363ff676767ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff404040ff363636ff343434ff353535a537373705ffffff0135353521333333ff333333ff434343ff5c5c5cff686868ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff484848ff414141ff393939ff313131ff3c3c3cdbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff323232ff353535ff4b4b4bff636363ff656565ff636363ff626262ff636363ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff464646ff474747ff464646ff414141ff363636ff343434ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff333333ff313131ff484848ff5e5e5eff666666ff646464ff626262ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff464646ff464646ff474747ff424242ff3a3a3aff343434ff353535ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff343434ff333333ff3d3d3dff555555ff686868ff656565ff626262ff636363ff656565ff626262ff424242ff464646ff464646ff464646ff484848ff444444ff393939ff353535ff353535ff363636ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff353535ff323232ff363636ff515151ff646464ff656565ff636363ff636363ff636363ff626262ff484848ff464646ff464646ff464646ff484848ff454545ff3d3d3dff353535ff343434ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff363636ff363636ff343434ff303030ff3f3f3fff575757ff666666ff656565ff646464ff626262ff424242ff464646ff474747ff454545ff3a3a3aff343434ff353535ff363636ff363636ff363636ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff363636ff363636ff363636ff303030ff373737ff535353ff636363ff656565ff636363ff626262ff484848ff464646ff474747ff454545ff3e3e3eff353535ff343434ff363636ff363636ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff363636ff363636ff363636ff363636ff333333ff333333ff484848ff606060ff696969ff626262ff434343ff474747ff3e3e3eff363636ff353535ff353535ff363636ff363636ff363636ff363636ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff363636ff363636ff363636ff353535ff343434ff333333ff3e3e3eff5d5d5dff686868ff626262ff484848ff474747ff424242ff373737ff353535ff353535ff363636ff363636ff363636ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff323232ff323232ff505050ff616161ff3d3d3dff373737ff343434ff353535ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff343434ff313131ff434343ff606060ff464646ff383838ff343434ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff3a3a3aff2b2b2bff1e1e1eff2d2d2dff383838ff373737ff353535ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff393939ff323232ff1c1c1cff262626ff373737ff383838ff353535ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff363636ff363636ff363636ff353535ff373737ff383838ff303030ff191919ff080808ff101010ff141414ff1a1a1aff303030ff383838ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff363636ff363636ff363636ff353535ff363636ff383838ff363636ff1d1d1dff0b0b0bff0c0c0cff141414ff181818ff292929ff373737ff373737ff363636ff363636ff363636ff363636ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff363636ff363636ff353535ff393939ff363636ff222222ff0c0c0cff0a0a0aff0c0c0cff121212ff171717ff151515ff161616ff212121ff353535ff393939ff363636ff363636ff363636ff363636ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff363636ff363636ff353535ff383838ff3a3a3aff262626ff121212ff0a0a0aff0c0c0cff0f0f0fff171717ff151515ff151515ff1e1e1eff2f2f2fff3a3a3aff363636ff363636ff363636ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff363636ff363636ff363636ff383838ff363636ff262626ff0d0d0dff090909ff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff141414ff151515ff232323ff353535ff383838ff363636ff353535ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff353535ff383838ff383838ff292929ff131313ff080808ff0c0c0cff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff151515ff131313ff202020ff313131ff383838ff363636ff363636ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9343434ff353535ff363636ff3a3a3aff2e2e2eff131313ff0a0a0aff0b0b0bff0d0d0dff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff171717ff161616ff141414ff1a1a1aff2a2a2aff393939ff373737ff363636ff363636ff363636a537373705ffffff0135353521363636ff363636ff363636ff3a3a3aff313131ff1c1c1cff0a0a0aff0a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff161616ff151515ff161616ff282828ff363636ff383838ff363636ff333333ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444a9353535ff383838ff313131ff151515ff080808ff0b0b0bff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff171717ff171717ff171717ff161616ff131313ff1b1b1bff2d2d2dff373737ff373737ff363636a537373705ffffff0134343421363636ff383838ff333333ff1e1e1eff090909ff0a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff171717ff171717ff171717ff131313ff171717ff2a2a2aff363636ff353535ff3d3d3ddbffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01444444af353535ff1e1e1eff0d0d0dff0a0a0aff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff151515ff151515ff222222ff333333ff353535ad30303007ffffff0134343423373737ff282828ff0d0d0dff0a0a0aff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff151515ff141414ff1b1b1bff2e2e2eff3e3e3ee1ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013e3e3e6f0f0f0fd5040404ff0b0b0bff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff101010ff0e0e0ee72f2f2f7347474703ffffff013b3b3b13141414cd050505f70a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff121212ff0c0c0cf12a2a2aa5ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015f5f5f052020202b1a1a1aa1080808f1070707ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff141414ff0c0c0cff212121af2a2a2a496d6d6d07ffffff01ffffff01ffffff01333333231d1d1d730b0b0beb060606ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff171717ff171717ff171717ff171717ff151515ff0e0e0eff181818d72626265546464615ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014d4d4d29121212af080808ef0a0a0aff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff171717ff171717ff171717ff141414ff121212f9141414b93b3b3b4fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0138383819151515890a0a0ae5080808ff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff171717ff171717ff161616ff101010fb151515d72c2c2c614444440dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0133333311262626510f0f0fd7050505ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff121212ff171717ff171717ff171717ff171717ff171717ff101010ff141414e7242424733a3a3a19ffffff01ffffff01ffffff01878787097272725f4d4d4d736a6a6a11ffffff01ffffff01ffffff016060600524242445191919ad040404ff0a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff171717ff171717ff171717ff111111ff0e0e0efd242424873232322dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015c5c5c0d2525255f090909d7080808fb0b0b0bff0d0d0dff0c0c0cff121212ff171717ff171717ff161616ff121212ff121212df2121218965656511ffffff01ffffff01ffffff018080800d6767674b646464d1606060ff454545ff464646df4f4f4f6165656517ffffff01ffffff01ffffff01ffffff012d2d2d4b101010b5060606fb0a0a0aff0d0d0dff0d0d0dff0f0f0fff171717ff171717ff161616ff131313ff101010ef2020209d4242422dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff012c2c2c2d1f1f1f83080808fb080808ff0d0d0dff121212ff171717ff141414ff0f0f0ff91e1e1eb12c2c2c354d4d4d09ffffff01ffffff01ffffff0178787825646464a75f5f5feb616161ff656565ff4a4a4aff414141ff424242f3414141bd69696937ffffff01ffffff01ffffff01ffffff0142424219171717710d0d0de3060606ff0c0c0cff0f0f0fff171717ff151515ff0d0d0dff171717c3292929575656560dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013737372d1212129d080808ef0d0d0dff121212f5191919bf2e2e2e3d70707003ffffff01ffffff018c8c8c037676762564646497606060ed606060ff636363ff636363ff656565ff4a4a4aff444444ff464646ff444444ff404040f74a4a4aad5555553162626207ffffff01ffffff01ffffff014040401125252589090909dd0a0a0aff121212ff141414c738383869ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015b5b5b0b1f1f1f591d1d1daf292929673f3f3f19ffffff01ffffff01ffffff01ffffff016d6d6d715f5f5fcd606060ff626262ff636363ff636363ff636363ff656565ff4a4a4aff444444ff464646ff464646ff454545ff434343ff414141db4f4f4f857b7b7b11ffffff01ffffff01ffffff0153535307222222331d1d1da91b1b1b8d4141412365656503ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff017c7c7c0f6868685d636363cb5e5e5eff626262ff636363ff636363ff636363ff636363ff636363ff656565ff4a4a4aff444444ff464646ff464646ff464646ff464646ff464646ff404040ff454545e14c4c4c6b69696917ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0177777733626262a3606060f3616161ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff4a4a4aff444444ff464646ff464646ff464646ff464646ff464646ff464646ff444444ff424242f9454545b55d5d5d49ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014b4b4b0f5e5e5e85626262ff626262ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff4a4a4aff444444ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff444444ff414141ff454545a16464641dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0132323225333333cf4e4e4eff646464ff666666ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff4a4a4aff444444ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff474747ff404040ff303030e35757573bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd313131ff363636ff515151ff636363ff656565ff636363ff636363ff636363ff636363ff636363ff636363ff656565ff4a4a4aff444444ff464646ff464646ff464646ff464646ff464646ff464646ff464646ff414141ff373737ff343434ff323232e159595939ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff343434ff333333ff3c3c3cff5b5b5bff686868ff636363ff626262ff636363ff636363ff636363ff656565ff4a4a4aff444444ff464646ff464646ff454545ff464646ff4c4c4cff454545ff393939ff353535ff353535ff353535ff323232e159595939ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff363636ff353535ff313131ff3f3f3fff5d5d5dff666666ff646464ff626262ff636363ff656565ff4a4a4aff444444ff454545ff474747ff4a4a4aff404040ff212121ff2f2f2fff373737ff373737ff353535ff363636ff323232e159595939ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff363636ff363636ff353535ff333333ff363636ff484848ff646464ff676767ff626262ff656565ff4a4a4aff444444ff4b4b4bff4a4a4aff262626ff0b0b0bff090909ff171717ff252525ff353535ff393939ff363636ff323232e159595939ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff363636ff363636ff363636ff363636ff363636ff323232ff363636ff4c4c4cff646464ff676767ff4d4d4dff484848ff2c2c2cff0b0b0bff020202ff040404ff0b0b0bff171717ff141414ff161616ff282828ff353535ff343434e359595939ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff363636ff363636ff363636ff363636ff363636ff363636ff343434ff323232ff3f3f3fff5f5f5fff3a3a3aff161616ff030303ff030303ff050505ff040404ff0b0b0bff171717ff171717ff161616ff151515ff1a1a1aff242424e55555553bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff363636ff363636ff363636ff363636ff363636ff353535ff363636ff383838ff2e2e2eff191919ff262626ff111111ff030303ff030303ff050505ff040404ff0b0b0bff171717ff171717ff151515ff111111f9121212cd272727557d7d7d09ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff363636ff363636ff363636ff363636ff363636ff383838ff373737ff242424ff0b0b0bff0a0a0aff393939ff393939ff222222ff080808ff020202ff030303ff0b0b0bff181818ff0f0f0fff151515f32424247935353525ffffff01ffffff01ffffff01a3a3a30fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff363636ff363636ff363636ff383838ff373737ff272727ff0c0c0cff090909ff0c0c0cff0e0e0eff373737ff363636ff3a3a3aff393939ff1e1e1eff080808ff080808ff0f0f0feb232323914040401dffffff01ffffff01ffffff01ffffff01ffffff018282825d626262c36d6d6d4d8d8d8d09ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff363636ff353535ff363636ff3a3a3aff2f2f2fff131313ff0b0b0bff0b0b0bff0d0d0dff0c0c0cff0e0e0eff373737ff363636ff353535ff363636ff393939ff303030ff1c1c1cc92626264d68686807ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01868686515e5e5eff646464e9696969957878781fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723363636cd363636ff373737ff383838ff313131ff161616ff090909ff0b0b0bff0d0d0dff0d0d0dff0d0d0dff0c0c0cff0e0e0eff373737ff363636ff363636ff363636ff353535ff353535ff3c3c3c8fffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0186868651616161ff676767ff646464ff656565f16a6a6a7d7f7f7f25ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0137373723353535cd393939ff373737ff1f1f1fff0d0d0dff0a0a0aff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff0e0e0eff373737ff363636ff363636ff363636ff363636ff353535ff37373791ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0186868651616161ff676767ff666666ff676767ff686868f9555555cd55555511ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0134343425323232cf212121ff0e0e0eff090909ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff0e0e0eff383838ff363636ff363636ff363636ff363636ff353535ff37373791ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0186868651616161ff686868ff696969ff5f5f5fff3d3d3dff303030ff4848481dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01474747132323238f020202ff080808ff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff0c0c0cff2e2e2eff393939ff363636ff353535ff363636ff353535ff37373791ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0185858551666666ff676767ff494949ff353535ff323232ff353535ff4e4e4e1bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0130303045101010af080808f70a0a0aff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff0d0d0dff131313ff1c1c1cff303030ff373737ff363636ff353535ff37373791ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0181818151494949ff363636ff313131ff363636ff353535ff363636ff4e4e4e1bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff0141414113191919690f0f0fdb060606ff0c0c0cff0d0d0dff0d0d0dff0d0d0dff0d0d0dff0c0c0cff0d0d0dff171717ff151515ff161616ff222222ff363636ff383838ff37373791ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff014d4d4d53272727c1242424ff373737ff373737ff353535ff353535ff363636ff4e4e4e1bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01626262091c1c1c830b0b0bd7090909ff0c0c0cff0d0d0dff0d0d0dff0c0c0cff0d0d0dff171717ff171717ff171717ff141414ff151515ff202020ff35353595ffffff01ffffff01ffffff01ffffff017474740540404049343434af2a2a2aff262626ff101010ff191919ff2e2e2eff373737ff363636ff363636ff4e4e4e1bffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015a5a5a073636362d141414a7080808f5080808ff0d0d0dff0c0c0cff0d0d0dff171717ff171717ff171717ff151515ff0e0e0efb1b1b1bbb3d3d3d29ffffff01ffffff01ffffff0151515119393939892a2a2ae92d2d2dff323232ff282828ff141414ff151515ff151515ff1f1f1fff343434ff393939ff4949491dffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff013636362f111111b5070707f30a0a0aff0d0d0dff171717ff141414ff111111f5111111c74343433d70707005ffffff01ffffff017c7c7c034e4e4e632a2a2af7292929ff323232ff313131ff323232ff282828ff141414ff171717ff171717ff151515ff0e0e0efd222222e153535315ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff012d2d2d151f1f1f590e0e0edb040404ff0f0f0fff171717e7262626673f3f3f1dffffff01ffffff01ffffff01ffffff01ffffff01444444293535358b2d2d2deb2b2b2bff313131ff323232ff282828ff141414ff171717ff121212ff0d0d0dff2222229d2626263dbebebe03ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01505050112626266f1d1d1d7f36363617ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01616161213333339d2c2c2ce92f2f2fff282828ff111111ff111111f7191919ab3c3c3c41ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015151510b3b3b3b43383838c51f1f1fff141414d71e1e1e654f4f4f13ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff015858580b4d4d4d4159595909ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff01ffffff010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000` diff --git a/swarm/api/http/test_server.go b/swarm/api/http/test_server.go deleted file mode 100644 index bd06c6f2f1bc..000000000000 --- a/swarm/api/http/test_server.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package http - -import ( - "io/ioutil" - "net/http" - "net/http/httptest" - "os" - "testing" - - "github.com/ubiq/go-ubiq/swarm/api" - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/ubiq/go-ubiq/swarm/storage/feed" -) - -type TestServer interface { - ServeHTTP(http.ResponseWriter, *http.Request) -} - -func NewTestSwarmServer(t *testing.T, serverFunc func(*api.API) TestServer, resolver api.Resolver) *TestSwarmServer { - dir, err := ioutil.TempDir("", "swarm-storage-test") - if err != nil { - t.Fatal(err) - } - storeparams := storage.NewDefaultLocalStoreParams() - storeparams.DbCapacity = 5000000 - storeparams.CacheCapacity = 5000 - storeparams.Init(dir) - localStore, err := storage.NewLocalStore(storeparams, nil) - if err != nil { - os.RemoveAll(dir) - t.Fatal(err) - } - fileStore := storage.NewFileStore(localStore, storage.NewFileStoreParams()) - - // Swarm feeds test setup - feedsDir, err := ioutil.TempDir("", "swarm-feeds-test") - if err != nil { - t.Fatal(err) - } - - rhparams := &feed.HandlerParams{} - rh, err := feed.NewTestHandler(feedsDir, rhparams) - if err != nil { - t.Fatal(err) - } - - a := api.NewAPI(fileStore, resolver, rh.Handler, nil) - srv := httptest.NewServer(serverFunc(a)) - tss := &TestSwarmServer{ - Server: srv, - FileStore: fileStore, - dir: dir, - Hasher: storage.MakeHashFunc(storage.DefaultHash)(), - cleanup: func() { - srv.Close() - rh.Close() - os.RemoveAll(dir) - os.RemoveAll(feedsDir) - }, - CurrentTime: 42, - } - feed.TimestampProvider = tss - return tss -} - -type TestSwarmServer struct { - *httptest.Server - Hasher storage.SwarmHash - FileStore *storage.FileStore - dir string - cleanup func() - CurrentTime uint64 -} - -func (t *TestSwarmServer) Close() { - t.cleanup() -} - -func (t *TestSwarmServer) Now() feed.Timestamp { - return feed.Timestamp{Time: t.CurrentTime} -} diff --git a/swarm/api/inspector.go b/swarm/api/inspector.go deleted file mode 100644 index b884e70b4c30..000000000000 --- a/swarm/api/inspector.go +++ /dev/null @@ -1,58 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package api - -import ( - "context" - - "github.com/ubiq/go-ubiq/swarm/network" - "github.com/ubiq/go-ubiq/swarm/storage" -) - -type Inspector struct { - api *API - hive *network.Hive - netStore *storage.NetStore -} - -func NewInspector(api *API, hive *network.Hive, netStore *storage.NetStore) *Inspector { - return &Inspector{api, hive, netStore} -} - -// Hive prints the kademlia table -func (inspector *Inspector) Hive() string { - return inspector.hive.String() -} - -type HasInfo struct { - Addr string `json:"address"` - Has bool `json:"has"` -} - -// Has checks whether each chunk address is present in the underlying datastore, -// the bool in the returned structs indicates if the underlying datastore has -// the chunk stored with the given address (true), or not (false) -func (inspector *Inspector) Has(chunkAddresses []storage.Address) []HasInfo { - results := make([]HasInfo, 0) - for _, addr := range chunkAddresses { - res := HasInfo{} - res.Addr = addr.String() - res.Has = inspector.netStore.Has(context.Background(), addr) - results = append(results, res) - } - return results -} diff --git a/swarm/api/manifest.go b/swarm/api/manifest.go deleted file mode 100644 index 7eeff31c8473..000000000000 --- a/swarm/api/manifest.go +++ /dev/null @@ -1,585 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package api - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "strings" - "time" - - "github.com/ubiq/go-ubiq/swarm/storage/feed" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/storage" -) - -const ( - ManifestType = "application/bzz-manifest+json" - FeedContentType = "application/bzz-feed" - - manifestSizeLimit = 5 * 1024 * 1024 -) - -// Manifest represents a swarm manifest -type Manifest struct { - Entries []ManifestEntry `json:"entries,omitempty"` -} - -// ManifestEntry represents an entry in a swarm manifest -type ManifestEntry struct { - Hash string `json:"hash,omitempty"` - Path string `json:"path,omitempty"` - ContentType string `json:"contentType,omitempty"` - Mode int64 `json:"mode,omitempty"` - Size int64 `json:"size,omitempty"` - ModTime time.Time `json:"mod_time,omitempty"` - Status int `json:"status,omitempty"` - Access *AccessEntry `json:"access,omitempty"` - Feed *feed.Feed `json:"feed,omitempty"` -} - -// ManifestList represents the result of listing files in a manifest -type ManifestList struct { - CommonPrefixes []string `json:"common_prefixes,omitempty"` - Entries []*ManifestEntry `json:"entries,omitempty"` -} - -// NewManifest creates and stores a new, empty manifest -func (a *API) NewManifest(ctx context.Context, toEncrypt bool) (storage.Address, error) { - var manifest Manifest - data, err := json.Marshal(&manifest) - if err != nil { - return nil, err - } - addr, wait, err := a.Store(ctx, bytes.NewReader(data), int64(len(data)), toEncrypt) - if err != nil { - return nil, err - } - err = wait(ctx) - return addr, err -} - -// Manifest hack for supporting Swarm feeds from the bzz: scheme -// see swarm/api/api.go:API.Get() for more information -func (a *API) NewFeedManifest(ctx context.Context, feed *feed.Feed) (storage.Address, error) { - var manifest Manifest - entry := ManifestEntry{ - Feed: feed, - ContentType: FeedContentType, - } - manifest.Entries = append(manifest.Entries, entry) - data, err := json.Marshal(&manifest) - if err != nil { - return nil, err - } - addr, wait, err := a.Store(ctx, bytes.NewReader(data), int64(len(data)), false) - if err != nil { - return nil, err - } - err = wait(ctx) - return addr, err -} - -// ManifestWriter is used to add and remove entries from an underlying manifest -type ManifestWriter struct { - api *API - trie *manifestTrie - quitC chan bool -} - -func (a *API) NewManifestWriter(ctx context.Context, addr storage.Address, quitC chan bool) (*ManifestWriter, error) { - trie, err := loadManifest(ctx, a.fileStore, addr, quitC, NOOPDecrypt) - if err != nil { - return nil, fmt.Errorf("error loading manifest %s: %s", addr, err) - } - return &ManifestWriter{a, trie, quitC}, nil -} - -// AddEntry stores the given data and adds the resulting address to the manifest -func (m *ManifestWriter) AddEntry(ctx context.Context, data io.Reader, e *ManifestEntry) (addr storage.Address, err error) { - entry := newManifestTrieEntry(e, nil) - if data != nil { - var wait func(context.Context) error - addr, wait, err = m.api.Store(ctx, data, e.Size, m.trie.encrypted) - if err != nil { - return nil, err - } - err = wait(ctx) - if err != nil { - return nil, err - } - entry.Hash = addr.Hex() - } - if entry.Hash == "" { - return addr, errors.New("missing entry hash") - } - m.trie.addEntry(entry, m.quitC) - return addr, nil -} - -// RemoveEntry removes the given path from the manifest -func (m *ManifestWriter) RemoveEntry(path string) error { - m.trie.deleteEntry(path, m.quitC) - return nil -} - -// Store stores the manifest, returning the resulting storage address -func (m *ManifestWriter) Store() (storage.Address, error) { - return m.trie.ref, m.trie.recalcAndStore() -} - -// ManifestWalker is used to recursively walk the entries in the manifest and -// all of its submanifests -type ManifestWalker struct { - api *API - trie *manifestTrie - quitC chan bool -} - -func (a *API) NewManifestWalker(ctx context.Context, addr storage.Address, decrypt DecryptFunc, quitC chan bool) (*ManifestWalker, error) { - trie, err := loadManifest(ctx, a.fileStore, addr, quitC, decrypt) - if err != nil { - return nil, fmt.Errorf("error loading manifest %s: %s", addr, err) - } - return &ManifestWalker{a, trie, quitC}, nil -} - -// ErrSkipManifest is used as a return value from WalkFn to indicate that the -// manifest should be skipped -var ErrSkipManifest = errors.New("skip this manifest") - -// WalkFn is the type of function called for each entry visited by a recursive -// manifest walk -type WalkFn func(entry *ManifestEntry) error - -// Walk recursively walks the manifest calling walkFn for each entry in the -// manifest, including submanifests -func (m *ManifestWalker) Walk(walkFn WalkFn) error { - return m.walk(m.trie, "", walkFn) -} - -func (m *ManifestWalker) walk(trie *manifestTrie, prefix string, walkFn WalkFn) error { - for _, entry := range &trie.entries { - if entry == nil { - continue - } - entry.Path = prefix + entry.Path - err := walkFn(&entry.ManifestEntry) - if err != nil { - if entry.ContentType == ManifestType && err == ErrSkipManifest { - continue - } - return err - } - if entry.ContentType != ManifestType { - continue - } - if err := trie.loadSubTrie(entry, nil); err != nil { - return err - } - if err := m.walk(entry.subtrie, entry.Path, walkFn); err != nil { - return err - } - } - return nil -} - -type manifestTrie struct { - fileStore *storage.FileStore - entries [257]*manifestTrieEntry // indexed by first character of basePath, entries[256] is the empty basePath entry - ref storage.Address // if ref != nil, it is stored - encrypted bool - decrypt DecryptFunc -} - -func newManifestTrieEntry(entry *ManifestEntry, subtrie *manifestTrie) *manifestTrieEntry { - return &manifestTrieEntry{ - ManifestEntry: *entry, - subtrie: subtrie, - } -} - -type manifestTrieEntry struct { - ManifestEntry - - subtrie *manifestTrie -} - -func loadManifest(ctx context.Context, fileStore *storage.FileStore, addr storage.Address, quitC chan bool, decrypt DecryptFunc) (trie *manifestTrie, err error) { // non-recursive, subtrees are downloaded on-demand - log.Trace("manifest lookup", "addr", addr) - // retrieve manifest via FileStore - manifestReader, isEncrypted := fileStore.Retrieve(ctx, addr) - log.Trace("reader retrieved", "addr", addr) - return readManifest(manifestReader, addr, fileStore, isEncrypted, quitC, decrypt) -} - -func readManifest(mr storage.LazySectionReader, addr storage.Address, fileStore *storage.FileStore, isEncrypted bool, quitC chan bool, decrypt DecryptFunc) (trie *manifestTrie, err error) { // non-recursive, subtrees are downloaded on-demand - - // TODO check size for oversized manifests - size, err := mr.Size(mr.Context(), quitC) - if err != nil { // size == 0 - // can't determine size means we don't have the root chunk - log.Trace("manifest not found", "addr", addr) - err = fmt.Errorf("Manifest not Found") - return - } - if size > manifestSizeLimit { - log.Warn("manifest exceeds size limit", "addr", addr, "size", size, "limit", manifestSizeLimit) - err = fmt.Errorf("Manifest size of %v bytes exceeds the %v byte limit", size, manifestSizeLimit) - return - } - manifestData := make([]byte, size) - read, err := mr.Read(manifestData) - if int64(read) < size { - log.Trace("manifest not found", "addr", addr) - if err == nil { - err = fmt.Errorf("Manifest retrieval cut short: read %v, expect %v", read, size) - } - return - } - - log.Debug("manifest retrieved", "addr", addr) - var man struct { - Entries []*manifestTrieEntry `json:"entries"` - } - err = json.Unmarshal(manifestData, &man) - if err != nil { - err = fmt.Errorf("Manifest %v is malformed: %v", addr.Log(), err) - log.Trace("malformed manifest", "addr", addr) - return - } - - log.Trace("manifest entries", "addr", addr, "len", len(man.Entries)) - - trie = &manifestTrie{ - fileStore: fileStore, - encrypted: isEncrypted, - decrypt: decrypt, - } - for _, entry := range man.Entries { - err = trie.addEntry(entry, quitC) - if err != nil { - return - } - } - return -} - -func (mt *manifestTrie) addEntry(entry *manifestTrieEntry, quitC chan bool) error { - mt.ref = nil // trie modified, hash needs to be re-calculated on demand - - if entry.ManifestEntry.Access != nil { - if mt.decrypt == nil { - return errors.New("dont have decryptor") - } - - err := mt.decrypt(&entry.ManifestEntry) - if err != nil { - return err - } - } - - if len(entry.Path) == 0 { - mt.entries[256] = entry - return nil - } - - b := entry.Path[0] - oldentry := mt.entries[b] - if (oldentry == nil) || (oldentry.Path == entry.Path && oldentry.ContentType != ManifestType) { - mt.entries[b] = entry - return nil - } - - cpl := 0 - for (len(entry.Path) > cpl) && (len(oldentry.Path) > cpl) && (entry.Path[cpl] == oldentry.Path[cpl]) { - cpl++ - } - - if (oldentry.ContentType == ManifestType) && (cpl == len(oldentry.Path)) { - if mt.loadSubTrie(oldentry, quitC) != nil { - return nil - } - entry.Path = entry.Path[cpl:] - oldentry.subtrie.addEntry(entry, quitC) - oldentry.Hash = "" - return nil - } - - commonPrefix := entry.Path[:cpl] - - subtrie := &manifestTrie{ - fileStore: mt.fileStore, - encrypted: mt.encrypted, - } - entry.Path = entry.Path[cpl:] - oldentry.Path = oldentry.Path[cpl:] - subtrie.addEntry(entry, quitC) - subtrie.addEntry(oldentry, quitC) - - mt.entries[b] = newManifestTrieEntry(&ManifestEntry{ - Path: commonPrefix, - ContentType: ManifestType, - }, subtrie) - return nil -} - -func (mt *manifestTrie) getCountLast() (cnt int, entry *manifestTrieEntry) { - for _, e := range &mt.entries { - if e != nil { - cnt++ - entry = e - } - } - return -} - -func (mt *manifestTrie) deleteEntry(path string, quitC chan bool) { - mt.ref = nil // trie modified, hash needs to be re-calculated on demand - - if len(path) == 0 { - mt.entries[256] = nil - return - } - - b := path[0] - entry := mt.entries[b] - if entry == nil { - return - } - if entry.Path == path { - mt.entries[b] = nil - return - } - - epl := len(entry.Path) - if (entry.ContentType == ManifestType) && (len(path) >= epl) && (path[:epl] == entry.Path) { - if mt.loadSubTrie(entry, quitC) != nil { - return - } - entry.subtrie.deleteEntry(path[epl:], quitC) - entry.Hash = "" - // remove subtree if it has less than 2 elements - cnt, lastentry := entry.subtrie.getCountLast() - if cnt < 2 { - if lastentry != nil { - lastentry.Path = entry.Path + lastentry.Path - } - mt.entries[b] = lastentry - } - } -} - -func (mt *manifestTrie) recalcAndStore() error { - if mt.ref != nil { - return nil - } - - var buffer bytes.Buffer - buffer.WriteString(`{"entries":[`) - - list := &Manifest{} - for _, entry := range &mt.entries { - if entry != nil { - if entry.Hash == "" { // TODO: paralellize - err := entry.subtrie.recalcAndStore() - if err != nil { - return err - } - entry.Hash = entry.subtrie.ref.Hex() - } - list.Entries = append(list.Entries, entry.ManifestEntry) - } - - } - - manifest, err := json.Marshal(list) - if err != nil { - return err - } - - sr := bytes.NewReader(manifest) - ctx := context.TODO() - addr, wait, err2 := mt.fileStore.Store(ctx, sr, int64(len(manifest)), mt.encrypted) - if err2 != nil { - return err2 - } - err2 = wait(ctx) - mt.ref = addr - return err2 -} - -func (mt *manifestTrie) loadSubTrie(entry *manifestTrieEntry, quitC chan bool) (err error) { - if entry.ManifestEntry.Access != nil { - if mt.decrypt == nil { - return errors.New("dont have decryptor") - } - - err := mt.decrypt(&entry.ManifestEntry) - if err != nil { - return err - } - } - - if entry.subtrie == nil { - hash := common.Hex2Bytes(entry.Hash) - entry.subtrie, err = loadManifest(context.TODO(), mt.fileStore, hash, quitC, mt.decrypt) - entry.Hash = "" // might not match, should be recalculated - } - return -} - -func (mt *manifestTrie) listWithPrefixInt(prefix, rp string, quitC chan bool, cb func(entry *manifestTrieEntry, suffix string)) error { - plen := len(prefix) - var start, stop int - if plen == 0 { - start = 0 - stop = 256 - } else { - start = int(prefix[0]) - stop = start - } - - for i := start; i <= stop; i++ { - select { - case <-quitC: - return fmt.Errorf("aborted") - default: - } - entry := mt.entries[i] - if entry != nil { - epl := len(entry.Path) - if entry.ContentType == ManifestType { - l := plen - if epl < l { - l = epl - } - if prefix[:l] == entry.Path[:l] { - err := mt.loadSubTrie(entry, quitC) - if err != nil { - return err - } - err = entry.subtrie.listWithPrefixInt(prefix[l:], rp+entry.Path[l:], quitC, cb) - if err != nil { - return err - } - } - } else { - if (epl >= plen) && (prefix == entry.Path[:plen]) { - cb(entry, rp+entry.Path[plen:]) - } - } - } - } - return nil -} - -func (mt *manifestTrie) listWithPrefix(prefix string, quitC chan bool, cb func(entry *manifestTrieEntry, suffix string)) (err error) { - return mt.listWithPrefixInt(prefix, "", quitC, cb) -} - -func (mt *manifestTrie) findPrefixOf(path string, quitC chan bool) (entry *manifestTrieEntry, pos int) { - log.Trace(fmt.Sprintf("findPrefixOf(%s)", path)) - - if len(path) == 0 { - return mt.entries[256], 0 - } - - //see if first char is in manifest entries - b := path[0] - entry = mt.entries[b] - if entry == nil { - return mt.entries[256], 0 - } - - epl := len(entry.Path) - log.Trace(fmt.Sprintf("path = %v entry.Path = %v epl = %v", path, entry.Path, epl)) - if len(path) <= epl { - if entry.Path[:len(path)] == path { - if entry.ContentType == ManifestType { - err := mt.loadSubTrie(entry, quitC) - if err == nil && entry.subtrie != nil { - subentries := entry.subtrie.entries - for i := 0; i < len(subentries); i++ { - sub := subentries[i] - if sub != nil && sub.Path == "" { - return sub, len(path) - } - } - } - entry.Status = http.StatusMultipleChoices - } - pos = len(path) - return - } - return nil, 0 - } - if path[:epl] == entry.Path { - log.Trace(fmt.Sprintf("entry.ContentType = %v", entry.ContentType)) - //the subentry is a manifest, load subtrie - if entry.ContentType == ManifestType && (strings.Contains(entry.Path, path) || strings.Contains(path, entry.Path)) { - err := mt.loadSubTrie(entry, quitC) - if err != nil { - return nil, 0 - } - sub, pos := entry.subtrie.findPrefixOf(path[epl:], quitC) - if sub != nil { - entry = sub - pos += epl - return sub, pos - } else if path == entry.Path { - entry.Status = http.StatusMultipleChoices - } - - } else { - //entry is not a manifest, return it - if path != entry.Path { - return nil, 0 - } - } - } - return nil, 0 -} - -// file system manifest always contains regularized paths -// no leading or trailing slashes, only single slashes inside -func RegularSlashes(path string) (res string) { - for i := 0; i < len(path); i++ { - if (path[i] != '/') || ((i > 0) && (path[i-1] != '/')) { - res = res + path[i:i+1] - } - } - if (len(res) > 0) && (res[len(res)-1] == '/') { - res = res[:len(res)-1] - } - return -} - -func (mt *manifestTrie) getEntry(spath string) (entry *manifestTrieEntry, fullpath string) { - path := RegularSlashes(spath) - var pos int - quitC := make(chan bool) - entry, pos = mt.findPrefixOf(path, quitC) - return entry, path[:pos] -} diff --git a/swarm/api/manifest_test.go b/swarm/api/manifest_test.go deleted file mode 100644 index 3136220f5dab..000000000000 --- a/swarm/api/manifest_test.go +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package api - -import ( - "bytes" - "encoding/json" - "fmt" - "io" - "net/http" - "strings" - "testing" - - "github.com/ubiq/go-ubiq/swarm/storage" -) - -func manifest(paths ...string) (manifestReader storage.LazySectionReader) { - var entries []string - for _, path := range paths { - entry := fmt.Sprintf(`{"path":"%s"}`, path) - entries = append(entries, entry) - } - manifest := fmt.Sprintf(`{"entries":[%s]}`, strings.Join(entries, ",")) - return &storage.LazyTestSectionReader{ - SectionReader: io.NewSectionReader(strings.NewReader(manifest), 0, int64(len(manifest))), - } -} - -func testGetEntry(t *testing.T, path, match string, multiple bool, paths ...string) *manifestTrie { - quitC := make(chan bool) - fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams()) - ref := make([]byte, fileStore.HashSize()) - trie, err := readManifest(manifest(paths...), ref, fileStore, false, quitC, NOOPDecrypt) - if err != nil { - t.Errorf("unexpected error making manifest: %v", err) - } - checkEntry(t, path, match, multiple, trie) - return trie -} - -func checkEntry(t *testing.T, path, match string, multiple bool, trie *manifestTrie) { - entry, fullpath := trie.getEntry(path) - if match == "-" && entry != nil { - t.Errorf("expected no match for '%s', got '%s'", path, fullpath) - } else if entry == nil { - if match != "-" { - t.Errorf("expected entry '%s' to match '%s', got no match", match, path) - } - } else if fullpath != match { - t.Errorf("incorrect entry retrieved for '%s'. expected path '%v', got '%s'", path, match, fullpath) - } - - if multiple && entry.Status != http.StatusMultipleChoices { - t.Errorf("Expected %d Multiple Choices Status for path %s, match %s, got %d", http.StatusMultipleChoices, path, match, entry.Status) - } else if !multiple && entry != nil && entry.Status == http.StatusMultipleChoices { - t.Errorf("Were not expecting %d Multiple Choices Status for path %s, match %s, but got it", http.StatusMultipleChoices, path, match) - } -} - -func TestGetEntry(t *testing.T) { - // file system manifest always contains regularized paths - testGetEntry(t, "a", "a", false, "a") - testGetEntry(t, "b", "-", false, "a") - testGetEntry(t, "/a//", "a", false, "a") - // fallback - testGetEntry(t, "/a", "", false, "") - testGetEntry(t, "/a/b", "a/b", false, "a/b") - // longest/deepest math - testGetEntry(t, "read", "read", true, "readme.md", "readit.md") - testGetEntry(t, "rf", "-", false, "readme.md", "readit.md") - testGetEntry(t, "readme", "readme", false, "readme.md") - testGetEntry(t, "readme", "-", false, "readit.md") - testGetEntry(t, "readme.md", "readme.md", false, "readme.md") - testGetEntry(t, "readme.md", "-", false, "readit.md") - testGetEntry(t, "readmeAmd", "-", false, "readit.md") - testGetEntry(t, "readme.mdffff", "-", false, "readme.md") - testGetEntry(t, "ab", "ab", true, "ab/cefg", "ab/cedh", "ab/kkkkkk") - testGetEntry(t, "ab/ce", "ab/ce", true, "ab/cefg", "ab/cedh", "ab/ceuuuuuuuuuu") - testGetEntry(t, "abc", "abc", true, "abcd", "abczzzzef", "abc/def", "abc/e/g") - testGetEntry(t, "a/b", "a/b", true, "a", "a/bc", "a/ba", "a/b/c") - testGetEntry(t, "a/b", "a/b", false, "a", "a/b", "a/bb", "a/b/c") - testGetEntry(t, "//a//b//", "a/b", false, "a", "a/b", "a/bb", "a/b/c") -} - -func TestExactMatch(t *testing.T) { - quitC := make(chan bool) - mf := manifest("shouldBeExactMatch.css", "shouldBeExactMatch.css.map") - fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams()) - ref := make([]byte, fileStore.HashSize()) - trie, err := readManifest(mf, ref, fileStore, false, quitC, nil) - if err != nil { - t.Errorf("unexpected error making manifest: %v", err) - } - entry, _ := trie.getEntry("shouldBeExactMatch.css") - if entry.Path != "" { - t.Errorf("Expected entry to match %s, got: %s", "shouldBeExactMatch.css", entry.Path) - } - if entry.Status == http.StatusMultipleChoices { - t.Errorf("Got status %d, which is unexepcted", http.StatusMultipleChoices) - } -} - -func TestDeleteEntry(t *testing.T) { - -} - -// TestAddFileWithManifestPath tests that adding an entry at a path which -// already exists as a manifest just adds the entry to the manifest rather -// than replacing the manifest with the entry -func TestAddFileWithManifestPath(t *testing.T) { - // create a manifest containing "ab" and "ac" - manifest, _ := json.Marshal(&Manifest{ - Entries: []ManifestEntry{ - {Path: "ab", Hash: "ab"}, - {Path: "ac", Hash: "ac"}, - }, - }) - reader := &storage.LazyTestSectionReader{ - SectionReader: io.NewSectionReader(bytes.NewReader(manifest), 0, int64(len(manifest))), - } - fileStore := storage.NewFileStore(nil, storage.NewFileStoreParams()) - ref := make([]byte, fileStore.HashSize()) - trie, err := readManifest(reader, ref, fileStore, false, nil, NOOPDecrypt) - if err != nil { - t.Fatal(err) - } - checkEntry(t, "ab", "ab", false, trie) - checkEntry(t, "ac", "ac", false, trie) - - // now add path "a" and check we can still get "ab" and "ac" - entry := &manifestTrieEntry{} - entry.Path = "a" - entry.Hash = "a" - trie.addEntry(entry, nil) - checkEntry(t, "ab", "ab", false, trie) - checkEntry(t, "ac", "ac", false, trie) - checkEntry(t, "a", "a", false, trie) -} - -// TestReadManifestOverSizeLimit creates a manifest reader with data longer then -// manifestSizeLimit and checks if readManifest function will return the exact error -// message. -// The manifest data is not in json-encoded format, preventing possbile -// successful parsing attempts if limit check fails. -func TestReadManifestOverSizeLimit(t *testing.T) { - manifest := make([]byte, manifestSizeLimit+1) - reader := &storage.LazyTestSectionReader{ - SectionReader: io.NewSectionReader(bytes.NewReader(manifest), 0, int64(len(manifest))), - } - _, err := readManifest(reader, storage.Address{}, nil, false, nil, NOOPDecrypt) - if err == nil { - t.Fatal("got no error from readManifest") - } - // Error message is part of the http response body - // which justifies exact string validation. - got := err.Error() - want := fmt.Sprintf("Manifest size of %v bytes exceeds the %v byte limit", len(manifest), manifestSizeLimit) - if got != want { - t.Fatalf("got error mesage %q, expected %q", got, want) - } -} diff --git a/swarm/api/storage.go b/swarm/api/storage.go deleted file mode 100644 index d037849d248f..000000000000 --- a/swarm/api/storage.go +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package api - -import ( - "context" - "path" - - "github.com/ubiq/go-ubiq/swarm/storage" -) - -type Response struct { - MimeType string - Status int - Size int64 - // Content []byte - Content string -} - -// implements a service -// -// DEPRECATED: Use the HTTP API instead -type Storage struct { - api *API -} - -func NewStorage(api *API) *Storage { - return &Storage{api} -} - -// Put uploads the content to the swarm with a simple manifest speficying -// its content type -// -// DEPRECATED: Use the HTTP API instead -func (s *Storage) Put(ctx context.Context, content string, contentType string, toEncrypt bool) (storage.Address, func(context.Context) error, error) { - return s.api.Put(ctx, content, contentType, toEncrypt) -} - -// Get retrieves the content from bzzpath and reads the response in full -// It returns the Response object, which serialises containing the -// response body as the value of the Content field -// NOTE: if error is non-nil, sResponse may still have partial content -// the actual size of which is given in len(resp.Content), while the expected -// size is resp.Size -// -// DEPRECATED: Use the HTTP API instead -func (s *Storage) Get(ctx context.Context, bzzpath string) (*Response, error) { - uri, err := Parse(path.Join("bzz:/", bzzpath)) - if err != nil { - return nil, err - } - addr, err := s.api.Resolve(ctx, uri.Addr) - if err != nil { - return nil, err - } - reader, mimeType, status, _, err := s.api.Get(ctx, nil, addr, uri.Path) - if err != nil { - return nil, err - } - quitC := make(chan bool) - expsize, err := reader.Size(ctx, quitC) - if err != nil { - return nil, err - } - body := make([]byte, expsize) - size, err := reader.Read(body) - if int64(size) == expsize { - err = nil - } - return &Response{mimeType, status, expsize, string(body[:size])}, err -} diff --git a/swarm/api/storage_test.go b/swarm/api/storage_test.go deleted file mode 100644 index ef96972b68a6..000000000000 --- a/swarm/api/storage_test.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package api - -import ( - "context" - "testing" -) - -func testStorage(t *testing.T, f func(*Storage, bool)) { - testAPI(t, func(api *API, toEncrypt bool) { - f(NewStorage(api), toEncrypt) - }) -} - -func TestStoragePutGet(t *testing.T) { - testStorage(t, func(api *Storage, toEncrypt bool) { - content := "hello" - exp := expResponse(content, "text/plain", 0) - // exp := expResponse([]byte(content), "text/plain", 0) - ctx := context.TODO() - bzzkey, wait, err := api.Put(ctx, content, exp.MimeType, toEncrypt) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - err = wait(ctx) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - bzzhash := bzzkey.Hex() - // to check put against the API#Get - resp0 := testGet(t, api.api, bzzhash, "") - checkResponse(t, resp0, exp) - - // check storage#Get - resp, err := api.Get(context.TODO(), bzzhash) - if err != nil { - t.Fatalf("unexpected error: %v", err) - } - checkResponse(t, &testResponse{nil, resp}, exp) - }) -} diff --git a/swarm/api/testdata/test0/img/logo.png b/swarm/api/testdata/test0/img/logo.png deleted file mode 100644 index 9557f960535d520687f69a9a65b35e3637828dd9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4119 zcmZ`+do)z*`yVr7W-!bM$ECrRODHiUOk&0v43%))b0kSoRD+_zm{E-!?8dzgxlAOC z&~1>S=`=36*Bs?i8XdQDL`4YSP3yPT`F+>={;}7ypXYs^&-?j2`+fIXPqM3vqpY-= zGz%m}*Bp7T7 z3NglEuxJw)Y%&lABjv(i%BO#?ce92T;6ZyGsj#&x=^alX4;|&S-yS_Gr7VNSB^kxG z!eBT}Co07w=FQ(jds2?HDxuyj%C}%UP=8=8eWdI3Rdn~_rjAn{G`DV1*?KvJb;ebn zXnC5IajD2Ou_o9AGgx?BK0m6ofNPeR;X*~rD3kD)B@EoXg^IkEi9!|-i< z7gerDG>{aTZz{Z}!3_kt$??eE$AMsGay-^3c~siYL?i_z5|RRV3dp=a8Bn5qv4{n>N7I3<zM z#CMSm4#M>w1ILqxF}><$v}x)olvTqqP?$W7?Nv9WiK(X~iZZ7%$!8V3K^vA}|Ce2O zJH87hxMYwrE+%P#d1y?&e;I;ac9AR;lT^V!s1~pZGnxQ0MVu)Q@~$@JBpUA*iM&`A zzBPIaaIQ@=$${4g3*8%KwhrhVWrp9xcYz0&1d{c|Bq>l8kGb@WuSot!Q4y2aUM<({HY`yy7$xIw!XByjybJspslHnnfs|!hi)vWg6?bCh z{2k?Ly*>etBGs*g+mW{?5wmI z>B~EU$om6rpn|mSc}zb2YkaDVz`SRL>^er;0dC8JBVUb)Zj+s}P{_Vpn{Sg#%gHD# z{Kj|_z^1Fq3qvyHkOz3$@VtsBAcn4vqm{ce3tOYDfsA_q0%hfIVQ7TO_J08%%^Al7 zG`hNs(Ek>S!kMB$h#E*op0-3@AWYQ+<5B#KQ7DX71csC4kW4L+8>2%>4v*!hNpL-U z$apac1xjf^pO>LSQlTyFwdiG$bw~u-TzhpNt3yc-6sP;?pbeCEfc^XewP1^8RIhC~ zxcQQ-&KcfT)PVj)AT)9hQHa%|z41RFLoYL7>b(5Q1p<@hu)VgPP@;m)QC=BxK)(`@ zirk|s#2V3B{10I0WuKVb_ODR@m@J3uwcP`5c1@p2SYE1#kmDC%kvn_VR)<8zpX&~N zH)Q!AK*4jz6Bc=*sbrV3zC}Jm!N5fZxufUGlZmey6jHu%6H>wUHfeDc-V$k z_IWPHmEW{S3!h6}TTMT-aJ~yl%ONk?*rjsG32JL!mo;e%w3&R60=?q6_R27(xq*zlTnllE`0>csGNF;PiKI<$XYJ>|hIT2K(@6)Y(V|Hv)#dmTW z%;_Io(C;%9)8tYK4goGO8A%x=XKQf?7=iwX@n?AG{-`n?G=eBxpz$?i#D+8uf_d++ zhBhfKHbYzpR4q2OVS?54S`#ULbB~4SU{(Fr_Y}3yE2hfe8|uRTIXob{sG%)7#N8Xk z(7_?xZ_XIvKvN&i6?pl}1<#*=zCb4pIj-JF3W1#co@1>~JKA)p8*Swr^;8Fkb-y;# z9JyjIre4`U_YQcm81JIa#pa3S$!T+OPK01J)7BD(b^C%uIC!*MCtZ>b&M0S63V2m= z8RA*;{M??CJTxi~s?7y#$Cj;r1x-(LUP#8aE$I4!PVaQmW#_;d#2vt*D8pI;Iup-x zv?BND2t&T@ug=k2KO+@JPN3$<*E!@wqp!kCu>DjdGIqtd{>V%jZXmJo{FB#Fl+l&p ztCDk52j@n9wu!%i6HaN?ek)ylAFcsZ49izG`hbZIkfETcK@%;zJIqoWNortK=0f;_|9Lc&2R;Xnr(bTYdGZ;Nu#2 z%Wpc!Gwl-)Z9eMdwi0yvtAA>Qwr5&5aNm4%%(vTOu!TA8;QX>pKdS_`8t%DpvkB8k z+;;kQj{*-tz>$TQKC_mL+2Lc-?rY=M62|lc zPY8l>st&`>r0U0`ZywSW3(ZHH)g~`e438ZGA>h~SiQ$yA#{=~fdB|!A_ceQnowHpJ zRh}2OvdvM#N4uc+^HBIHH!JTnIM2b_2a&&9Sh_f?EoyUn`ifnyH%tYpx>$VAGL<-`Du)x}rebb6~2?4fcvHwRJcOaOfKK z^Gbu4I_#Uj8|R15IOk&o?dh(qmml*Bvof_{?m;<0l9n2S2Pzcc#27VEEnN|B9`rrj z=Qw%N#%rBZp~F7w*;2`YRIEJ~s&&=YD~Rc1dYtT^yF<8?keWMPq9}(i84xwn6^G4( z=8`SUg%M#pdIPMaN7QOIFTax@fs?6zysVN>Rq}l&pV>(Uk(Sjw(^~2rs3xGa3Hi2c~Plk;DfZr$SE zL_=Ct* z>Z*4O2eK)XqF*n9w?aTzT{YKsb`&|V{{s{I0NZQ2{D-7(C`!t7*?CF9*bDz~KqSlU z#`ZS-ZsX+yMf~t(10pH0pBcNS{wkt)CrhQXDMJNd8L^UX#ObaiE1R)b9E>i2H6bA5 z2Q&(5T{s0i`K%Zbbb&_cy%}?jDISs~es_G!QyDv)CMT&%2x@%yox;^)oLs$rAZBa8Cg6{{YNF8Hj4kqe z%KXioMa^5Mkt59>(2_I*>y%j3>v1p1J9e^Qm8yzDS2J2A1UY!wUO^CCk_}1U1SjUk z>Anp2@u0lH8PFh!y5M}OALo;!!BwDYLqab+6w(HzBvJf+bb$zF|3FdYE`m%5g@Xtk zysg`+Ze57$wcKdkp7oQqj#3ZEZlJg$%WrO=^iw8(Y~%#Kfl>yWS^LP7A%~nIH~$m| z)*#-_JT9B2%t7}|O{(a|_lc=`1WxI6}Qu{cm} z3!nsT#FyQs41V~5q9#5*pSAPh;W5PA_YIo8s92k2=j(6pn!;n}Yn-9hma_<*O`MOr zDt{--S%j^B;aHHhp)E^UxT+=TlrVY=SNp_muRAI1hLLc1gM=01`_5(O7R_69cM%Y4+p<1 z|62XOSv0_LY7O?+U}hm`xC%|rx>5E%QNf3!j*^bhk3t4!YC^O$GO;o;HS-`6NhTH~ qqWLxx6OxI^pkGJk|0@VT6&xB8`@ajS&{6tO0nEwXh03!FWc~*<+c;7H diff --git a/swarm/api/testdata/test0/index.css b/swarm/api/testdata/test0/index.css deleted file mode 100644 index 693b13a37c7e..000000000000 --- a/swarm/api/testdata/test0/index.css +++ /dev/null @@ -1,9 +0,0 @@ -h1 { - color: black; - font-size: 12px; - background-color: orange; - border: 4px solid black; -} -body { - background-color: orange -} diff --git a/swarm/api/testdata/test0/index.html b/swarm/api/testdata/test0/index.html deleted file mode 100644 index 7154c98c4ff3..000000000000 --- a/swarm/api/testdata/test0/index.html +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - -

Swarm Test

- Ubiq logo - - \ No newline at end of file diff --git a/swarm/api/uri.go b/swarm/api/uri.go deleted file mode 100644 index c8c111887f1a..000000000000 --- a/swarm/api/uri.go +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package api - -import ( - "fmt" - "net/url" - "regexp" - "strings" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/swarm/storage" -) - -//matches hex swarm hashes -// TODO: this is bad, it should not be hardcoded how long is a hash -var hashMatcher = regexp.MustCompile("^([0-9A-Fa-f]{64})([0-9A-Fa-f]{64})?$") - -// URI is a reference to content stored in swarm. -type URI struct { - // Scheme has one of the following values: - // - // * bzz - an entry in a swarm manifest - // * bzz-raw - raw swarm content - // * bzz-immutable - immutable URI of an entry in a swarm manifest - // (address is not resolved) - // * bzz-list - list of all files contained in a swarm manifest - // - Scheme string - - // Addr is either a hexadecimal storage address or it an address which - // resolves to a storage address - Addr string - - // addr stores the parsed storage address - addr storage.Address - - // Path is the path to the content within a swarm manifest - Path string -} - -func (u *URI) MarshalJSON() (out []byte, err error) { - return []byte(`"` + u.String() + `"`), nil -} - -func (u *URI) UnmarshalJSON(value []byte) error { - uri, err := Parse(string(value)) - if err != nil { - return err - } - *u = *uri - return nil -} - -// Parse parses rawuri into a URI struct, where rawuri is expected to have one -// of the following formats: -// -// * :/ -// * :/ -// * :// -// * :// -// * :// -// * :/// -// -// with scheme one of bzz, bzz-raw, bzz-immutable, bzz-list or bzz-hash -func Parse(rawuri string) (*URI, error) { - u, err := url.Parse(rawuri) - if err != nil { - return nil, err - } - uri := &URI{Scheme: u.Scheme} - - // check the scheme is valid - switch uri.Scheme { - case "bzz", "bzz-raw", "bzz-immutable", "bzz-list", "bzz-hash", "bzz-feed": - default: - return nil, fmt.Errorf("unknown scheme %q", u.Scheme) - } - - // handle URIs like bzz:/// where the addr and path - // have already been split by url.Parse - if u.Host != "" { - uri.Addr = u.Host - uri.Path = strings.TrimLeft(u.Path, "/") - return uri, nil - } - - // URI is like bzz:// so split the addr and path from - // the raw path (which will be //) - parts := strings.SplitN(strings.TrimLeft(u.Path, "/"), "/", 2) - uri.Addr = parts[0] - if len(parts) == 2 { - uri.Path = parts[1] - } - return uri, nil -} -func (u *URI) Feed() bool { - return u.Scheme == "bzz-feed" -} - -func (u *URI) Raw() bool { - return u.Scheme == "bzz-raw" -} - -func (u *URI) Immutable() bool { - return u.Scheme == "bzz-immutable" -} - -func (u *URI) List() bool { - return u.Scheme == "bzz-list" -} - -func (u *URI) Hash() bool { - return u.Scheme == "bzz-hash" -} - -func (u *URI) String() string { - return u.Scheme + ":/" + u.Addr + "/" + u.Path -} - -func (u *URI) Address() storage.Address { - if u.addr != nil { - return u.addr - } - if hashMatcher.MatchString(u.Addr) { - u.addr = common.Hex2Bytes(u.Addr) - return u.addr - } - return nil -} diff --git a/swarm/api/uri_test.go b/swarm/api/uri_test.go deleted file mode 100644 index ffd428a62cc8..000000000000 --- a/swarm/api/uri_test.go +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package api - -import ( - "bytes" - "reflect" - "testing" - - "github.com/ubiq/go-ubiq/swarm/storage" -) - -func TestParseURI(t *testing.T) { - type test struct { - uri string - expectURI *URI - expectErr bool - expectRaw bool - expectImmutable bool - expectList bool - expectHash bool - expectValidKey bool - expectAddr storage.Address - } - tests := []test{ - { - uri: "", - expectErr: true, - }, - { - uri: "foo", - expectErr: true, - }, - { - uri: "bzz", - expectErr: true, - }, - { - uri: "bzz:", - expectURI: &URI{Scheme: "bzz"}, - }, - { - uri: "bzz-immutable:", - expectURI: &URI{Scheme: "bzz-immutable"}, - expectImmutable: true, - }, - { - uri: "bzz-raw:", - expectURI: &URI{Scheme: "bzz-raw"}, - expectRaw: true, - }, - { - uri: "bzz:/", - expectURI: &URI{Scheme: "bzz"}, - }, - { - uri: "bzz:/abc123", - expectURI: &URI{Scheme: "bzz", Addr: "abc123"}, - }, - { - uri: "bzz:/abc123/path/to/entry", - expectURI: &URI{Scheme: "bzz", Addr: "abc123", Path: "path/to/entry"}, - }, - { - uri: "bzz-raw:/", - expectURI: &URI{Scheme: "bzz-raw"}, - expectRaw: true, - }, - { - uri: "bzz-raw:/abc123", - expectURI: &URI{Scheme: "bzz-raw", Addr: "abc123"}, - expectRaw: true, - }, - { - uri: "bzz-raw:/abc123/path/to/entry", - expectURI: &URI{Scheme: "bzz-raw", Addr: "abc123", Path: "path/to/entry"}, - expectRaw: true, - }, - { - uri: "bzz://", - expectURI: &URI{Scheme: "bzz"}, - }, - { - uri: "bzz://abc123", - expectURI: &URI{Scheme: "bzz", Addr: "abc123"}, - }, - { - uri: "bzz://abc123/path/to/entry", - expectURI: &URI{Scheme: "bzz", Addr: "abc123", Path: "path/to/entry"}, - }, - { - uri: "bzz-hash:", - expectURI: &URI{Scheme: "bzz-hash"}, - expectHash: true, - }, - { - uri: "bzz-hash:/", - expectURI: &URI{Scheme: "bzz-hash"}, - expectHash: true, - }, - { - uri: "bzz-list:", - expectURI: &URI{Scheme: "bzz-list"}, - expectList: true, - }, - { - uri: "bzz-list:/", - expectURI: &URI{Scheme: "bzz-list"}, - expectList: true, - }, - { - uri: "bzz-raw://4378d19c26590f1a818ed7d6a62c3809e149b0999cab5ce5f26233b3b423bf8c", - expectURI: &URI{Scheme: "bzz-raw", - Addr: "4378d19c26590f1a818ed7d6a62c3809e149b0999cab5ce5f26233b3b423bf8c", - }, - expectValidKey: true, - expectRaw: true, - expectAddr: storage.Address{67, 120, 209, 156, 38, 89, 15, 26, - 129, 142, 215, 214, 166, 44, 56, 9, - 225, 73, 176, 153, 156, 171, 92, 229, - 242, 98, 51, 179, 180, 35, 191, 140, - }, - }, - } - for _, x := range tests { - actual, err := Parse(x.uri) - if x.expectErr { - if err == nil { - t.Fatalf("expected %s to error", x.uri) - } - continue - } - if err != nil { - t.Fatalf("error parsing %s: %s", x.uri, err) - } - if !reflect.DeepEqual(actual, x.expectURI) { - t.Fatalf("expected %s to return %#v, got %#v", x.uri, x.expectURI, actual) - } - if actual.Raw() != x.expectRaw { - t.Fatalf("expected %s raw to be %t, got %t", x.uri, x.expectRaw, actual.Raw()) - } - if actual.Immutable() != x.expectImmutable { - t.Fatalf("expected %s immutable to be %t, got %t", x.uri, x.expectImmutable, actual.Immutable()) - } - if actual.List() != x.expectList { - t.Fatalf("expected %s list to be %t, got %t", x.uri, x.expectList, actual.List()) - } - if actual.Hash() != x.expectHash { - t.Fatalf("expected %s hash to be %t, got %t", x.uri, x.expectHash, actual.Hash()) - } - if x.expectValidKey { - if actual.Address() == nil { - t.Fatalf("expected %s to return a valid key, got nil", x.uri) - } else { - if !bytes.Equal(x.expectAddr, actual.Address()) { - t.Fatalf("expected %s to be decoded to %v", x.expectURI.Addr, x.expectAddr) - } - } - } - } -} diff --git a/swarm/bmt/bmt.go b/swarm/bmt/bmt.go deleted file mode 100644 index 18eab5a2bcb8..000000000000 --- a/swarm/bmt/bmt.go +++ /dev/null @@ -1,690 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Package bmt provides a binary merkle tree implementation used for swarm chunk hash -package bmt - -import ( - "fmt" - "hash" - "strings" - "sync" - "sync/atomic" -) - -/* -Binary Merkle Tree Hash is a hash function over arbitrary datachunks of limited size. -It is defined as the root hash of the binary merkle tree built over fixed size segments -of the underlying chunk using any base hash function (e.g., keccak 256 SHA3). -Chunks with data shorter than the fixed size are hashed as if they had zero padding. - -BMT hash is used as the chunk hash function in swarm which in turn is the basis for the -128 branching swarm hash http://swarm-guide.readthedocs.io/en/latest/architecture.html#swarm-hash - -The BMT is optimal for providing compact inclusion proofs, i.e. prove that a -segment is a substring of a chunk starting at a particular offset. -The size of the underlying segments is fixed to the size of the base hash (called the resolution -of the BMT hash), Using Keccak256 SHA3 hash is 32 bytes, the EVM word size to optimize for on-chain BMT verification -as well as the hash size optimal for inclusion proofs in the merkle tree of the swarm hash. - -Two implementations are provided: - -* RefHasher is optimized for code simplicity and meant as a reference implementation - that is simple to understand -* Hasher is optimized for speed taking advantage of concurrency with minimalistic - control structure to coordinate the concurrent routines - - BMT Hasher implements the following interfaces - * standard golang hash.Hash - synchronous, reusable - * SwarmHash - SumWithSpan provided - * io.Writer - synchronous left-to-right datawriter - * AsyncWriter - concurrent section writes and asynchronous Sum call -*/ - -const ( - // PoolSize is the maximum number of bmt trees used by the hashers, i.e, - // the maximum number of concurrent BMT hashing operations performed by the same hasher - PoolSize = 8 -) - -// BaseHasherFunc is a hash.Hash constructor function used for the base hash of the BMT. -// implemented by Keccak256 SHA3 sha3.NewLegacyKeccak256 -type BaseHasherFunc func() hash.Hash - -// Hasher a reusable hasher for fixed maximum size chunks representing a BMT -// - implements the hash.Hash interface -// - reuses a pool of trees for amortised memory allocation and resource control -// - supports order-agnostic concurrent segment writes and section (double segment) writes -// as well as sequential read and write -// - the same hasher instance must not be called concurrently on more than one chunk -// - the same hasher instance is synchronously reuseable -// - Sum gives back the tree to the pool and guaranteed to leave -// the tree and itself in a state reusable for hashing a new chunk -// - generates and verifies segment inclusion proofs (TODO:) -type Hasher struct { - pool *TreePool // BMT resource pool - bmt *tree // prebuilt BMT resource for flowcontrol and proofs -} - -// New creates a reusable BMT Hasher that -// pulls a new tree from a resource pool for hashing each chunk -func New(p *TreePool) *Hasher { - return &Hasher{ - pool: p, - } -} - -// TreePool provides a pool of trees used as resources by the BMT Hasher. -// A tree popped from the pool is guaranteed to have a clean state ready -// for hashing a new chunk. -type TreePool struct { - lock sync.Mutex - c chan *tree // the channel to obtain a resource from the pool - hasher BaseHasherFunc // base hasher to use for the BMT levels - SegmentSize int // size of leaf segments, stipulated to be = hash size - SegmentCount int // the number of segments on the base level of the BMT - Capacity int // pool capacity, controls concurrency - Depth int // depth of the bmt trees = int(log2(segmentCount))+1 - Size int // the total length of the data (count * size) - count int // current count of (ever) allocated resources - zerohashes [][]byte // lookup table for predictable padding subtrees for all levels -} - -// NewTreePool creates a tree pool with hasher, segment size, segment count and capacity -// on Hasher.getTree it reuses free trees or creates a new one if capacity is not reached -func NewTreePool(hasher BaseHasherFunc, segmentCount, capacity int) *TreePool { - // initialises the zerohashes lookup table - depth := calculateDepthFor(segmentCount) - segmentSize := hasher().Size() - zerohashes := make([][]byte, depth+1) - zeros := make([]byte, segmentSize) - zerohashes[0] = zeros - h := hasher() - for i := 1; i < depth+1; i++ { - zeros = doSum(h, nil, zeros, zeros) - zerohashes[i] = zeros - } - return &TreePool{ - c: make(chan *tree, capacity), - hasher: hasher, - SegmentSize: segmentSize, - SegmentCount: segmentCount, - Capacity: capacity, - Size: segmentCount * segmentSize, - Depth: depth, - zerohashes: zerohashes, - } -} - -// Drain drains the pool until it has no more than n resources -func (p *TreePool) Drain(n int) { - p.lock.Lock() - defer p.lock.Unlock() - for len(p.c) > n { - <-p.c - p.count-- - } -} - -// Reserve is blocking until it returns an available tree -// it reuses free trees or creates a new one if size is not reached -// TODO: should use a context here -func (p *TreePool) reserve() *tree { - p.lock.Lock() - defer p.lock.Unlock() - var t *tree - if p.count == p.Capacity { - return <-p.c - } - select { - case t = <-p.c: - default: - t = newTree(p.SegmentSize, p.Depth, p.hasher) - p.count++ - } - return t -} - -// release gives back a tree to the pool. -// this tree is guaranteed to be in reusable state -func (p *TreePool) release(t *tree) { - p.c <- t // can never fail ... -} - -// tree is a reusable control structure representing a BMT -// organised in a binary tree -// Hasher uses a TreePool to obtain a tree for each chunk hash -// the tree is 'locked' while not in the pool -type tree struct { - leaves []*node // leaf nodes of the tree, other nodes accessible via parent links - cursor int // index of rightmost currently open segment - offset int // offset (cursor position) within currently open segment - section []byte // the rightmost open section (double segment) - result chan []byte // result channel - span []byte // The span of the data subsumed under the chunk -} - -// node is a reuseable segment hasher representing a node in a BMT -type node struct { - isLeft bool // whether it is left side of the parent double segment - parent *node // pointer to parent node in the BMT - state int32 // atomic increment impl concurrent boolean toggle - left, right []byte // this is where the two children sections are written - hasher hash.Hash // preconstructed hasher on nodes -} - -// newNode constructs a segment hasher node in the BMT (used by newTree) -func newNode(index int, parent *node, hasher hash.Hash) *node { - return &node{ - parent: parent, - isLeft: index%2 == 0, - hasher: hasher, - } -} - -// Draw draws the BMT (badly) -func (t *tree) draw(hash []byte) string { - var left, right []string - var anc []*node - for i, n := range t.leaves { - left = append(left, fmt.Sprintf("%v", hashstr(n.left))) - if i%2 == 0 { - anc = append(anc, n.parent) - } - right = append(right, fmt.Sprintf("%v", hashstr(n.right))) - } - anc = t.leaves - var hashes [][]string - for l := 0; len(anc) > 0; l++ { - var nodes []*node - hash := []string{""} - for i, n := range anc { - hash = append(hash, fmt.Sprintf("%v|%v", hashstr(n.left), hashstr(n.right))) - if i%2 == 0 && n.parent != nil { - nodes = append(nodes, n.parent) - } - } - hash = append(hash, "") - hashes = append(hashes, hash) - anc = nodes - } - hashes = append(hashes, []string{"", fmt.Sprintf("%v", hashstr(hash)), ""}) - total := 60 - del := " " - var rows []string - for i := len(hashes) - 1; i >= 0; i-- { - var textlen int - hash := hashes[i] - for _, s := range hash { - textlen += len(s) - } - if total < textlen { - total = textlen + len(hash) - } - delsize := (total - textlen) / (len(hash) - 1) - if delsize > len(del) { - delsize = len(del) - } - row := fmt.Sprintf("%v: %v", len(hashes)-i-1, strings.Join(hash, del[:delsize])) - rows = append(rows, row) - - } - rows = append(rows, strings.Join(left, " ")) - rows = append(rows, strings.Join(right, " ")) - return strings.Join(rows, "\n") + "\n" -} - -// newTree initialises a tree by building up the nodes of a BMT -// - segment size is stipulated to be the size of the hash -func newTree(segmentSize, depth int, hashfunc func() hash.Hash) *tree { - n := newNode(0, nil, hashfunc()) - prevlevel := []*node{n} - // iterate over levels and creates 2^(depth-level) nodes - // the 0 level is on double segment sections so we start at depth - 2 since - count := 2 - for level := depth - 2; level >= 0; level-- { - nodes := make([]*node, count) - for i := 0; i < count; i++ { - parent := prevlevel[i/2] - var hasher hash.Hash - if level == 0 { - hasher = hashfunc() - } - nodes[i] = newNode(i, parent, hasher) - } - prevlevel = nodes - count *= 2 - } - // the datanode level is the nodes on the last level - return &tree{ - leaves: prevlevel, - result: make(chan []byte), - section: make([]byte, 2*segmentSize), - } -} - -// methods needed to implement hash.Hash - -// Size returns the size -func (h *Hasher) Size() int { - return h.pool.SegmentSize -} - -// BlockSize returns the block size -func (h *Hasher) BlockSize() int { - return 2 * h.pool.SegmentSize -} - -// Sum returns the BMT root hash of the buffer -// using Sum presupposes sequential synchronous writes (io.Writer interface) -// hash.Hash interface Sum method appends the byte slice to the underlying -// data before it calculates and returns the hash of the chunk -// caller must make sure Sum is not called concurrently with Write, writeSection -func (h *Hasher) Sum(b []byte) (s []byte) { - t := h.getTree() - // write the last section with final flag set to true - go h.writeSection(t.cursor, t.section, true, true) - // wait for the result - s = <-t.result - span := t.span - // release the tree resource back to the pool - h.releaseTree() - // b + sha3(span + BMT(pure_chunk)) - if len(span) == 0 { - return append(b, s...) - } - return doSum(h.pool.hasher(), b, span, s) -} - -// methods needed to implement the SwarmHash and the io.Writer interfaces - -// Write calls sequentially add to the buffer to be hashed, -// with every full segment calls writeSection in a go routine -func (h *Hasher) Write(b []byte) (int, error) { - l := len(b) - if l == 0 || l > h.pool.Size { - return 0, nil - } - t := h.getTree() - secsize := 2 * h.pool.SegmentSize - // calculate length of missing bit to complete current open section - smax := secsize - t.offset - // if at the beginning of chunk or middle of the section - if t.offset < secsize { - // fill up current segment from buffer - copy(t.section[t.offset:], b) - // if input buffer consumed and open section not complete, then - // advance offset and return - if smax == 0 { - smax = secsize - } - if l <= smax { - t.offset += l - return l, nil - } - } else { - // if end of a section - if t.cursor == h.pool.SegmentCount*2 { - return 0, nil - } - } - // read full sections and the last possibly partial section from the input buffer - for smax < l { - // section complete; push to tree asynchronously - go h.writeSection(t.cursor, t.section, true, false) - // reset section - t.section = make([]byte, secsize) - // copy from input buffer at smax to right half of section - copy(t.section, b[smax:]) - // advance cursor - t.cursor++ - // smax here represents successive offsets in the input buffer - smax += secsize - } - t.offset = l - smax + secsize - return l, nil -} - -// Reset needs to be called before writing to the hasher -func (h *Hasher) Reset() { - h.releaseTree() -} - -// methods needed to implement the SwarmHash interface - -// ResetWithLength needs to be called before writing to the hasher -// the argument is supposed to be the byte slice binary representation of -// the length of the data subsumed under the hash, i.e., span -func (h *Hasher) ResetWithLength(span []byte) { - h.Reset() - h.getTree().span = span -} - -// releaseTree gives back the Tree to the pool whereby it unlocks -// it resets tree, segment and index -func (h *Hasher) releaseTree() { - t := h.bmt - if t == nil { - return - } - h.bmt = nil - go func() { - t.cursor = 0 - t.offset = 0 - t.span = nil - t.section = make([]byte, h.pool.SegmentSize*2) - select { - case <-t.result: - default: - } - h.pool.release(t) - }() -} - -// NewAsyncWriter extends Hasher with an interface for concurrent segment/section writes -func (h *Hasher) NewAsyncWriter(double bool) *AsyncHasher { - secsize := h.pool.SegmentSize - if double { - secsize *= 2 - } - write := func(i int, section []byte, final bool) { - h.writeSection(i, section, double, final) - } - return &AsyncHasher{ - Hasher: h, - double: double, - secsize: secsize, - write: write, - } -} - -// SectionWriter is an asynchronous segment/section writer interface -type SectionWriter interface { - Reset() // standard init to be called before reuse - Write(index int, data []byte) // write into section of index - Sum(b []byte, length int, span []byte) []byte // returns the hash of the buffer - SectionSize() int // size of the async section unit to use -} - -// AsyncHasher extends BMT Hasher with an asynchronous segment/section writer interface -// AsyncHasher is unsafe and does not check indexes and section data lengths -// it must be used with the right indexes and length and the right number of sections -// -// behaviour is undefined if -// * non-final sections are shorter or longer than secsize -// * if final section does not match length -// * write a section with index that is higher than length/secsize -// * set length in Sum call when length/secsize < maxsec -// -// * if Sum() is not called on a Hasher that is fully written -// a process will block, can be terminated with Reset -// * it will not leak processes if not all sections are written but it blocks -// and keeps the resource which can be released calling Reset() -type AsyncHasher struct { - *Hasher // extends the Hasher - mtx sync.Mutex // to lock the cursor access - double bool // whether to use double segments (call Hasher.writeSection) - secsize int // size of base section (size of hash or double) - write func(i int, section []byte, final bool) -} - -// methods needed to implement AsyncWriter - -// SectionSize returns the size of async section unit to use -func (sw *AsyncHasher) SectionSize() int { - return sw.secsize -} - -// Write writes the i-th section of the BMT base -// this function can and is meant to be called concurrently -// it sets max segment threadsafely -func (sw *AsyncHasher) Write(i int, section []byte) { - sw.mtx.Lock() - defer sw.mtx.Unlock() - t := sw.getTree() - // cursor keeps track of the rightmost section written so far - // if index is lower than cursor then just write non-final section as is - if i < t.cursor { - // if index is not the rightmost, safe to write section - go sw.write(i, section, false) - return - } - // if there is a previous rightmost section safe to write section - if t.offset > 0 { - if i == t.cursor { - // i==cursor implies cursor was set by Hash call so we can write section as final one - // since it can be shorter, first we copy it to the padded buffer - t.section = make([]byte, sw.secsize) - copy(t.section, section) - go sw.write(i, t.section, true) - return - } - // the rightmost section just changed, so we write the previous one as non-final - go sw.write(t.cursor, t.section, false) - } - // set i as the index of the righmost section written so far - // set t.offset to cursor*secsize+1 - t.cursor = i - t.offset = i*sw.secsize + 1 - t.section = make([]byte, sw.secsize) - copy(t.section, section) -} - -// Sum can be called any time once the length and the span is known -// potentially even before all segments have been written -// in such cases Sum will block until all segments are present and -// the hash for the length can be calculated. -// -// b: digest is appended to b -// length: known length of the input (unsafe; undefined if out of range) -// meta: metadata to hash together with BMT root for the final digest -// e.g., span for protection against existential forgery -func (sw *AsyncHasher) Sum(b []byte, length int, meta []byte) (s []byte) { - sw.mtx.Lock() - t := sw.getTree() - if length == 0 { - sw.mtx.Unlock() - s = sw.pool.zerohashes[sw.pool.Depth] - } else { - // for non-zero input the rightmost section is written to the tree asynchronously - // if the actual last section has been written (t.cursor == length/t.secsize) - maxsec := (length - 1) / sw.secsize - if t.offset > 0 { - go sw.write(t.cursor, t.section, maxsec == t.cursor) - } - // set cursor to maxsec so final section is written when it arrives - t.cursor = maxsec - t.offset = length - result := t.result - sw.mtx.Unlock() - // wait for the result or reset - s = <-result - } - // relesase the tree back to the pool - sw.releaseTree() - // if no meta is given just append digest to b - if len(meta) == 0 { - return append(b, s...) - } - // hash together meta and BMT root hash using the pools - return doSum(sw.pool.hasher(), b, meta, s) -} - -// writeSection writes the hash of i-th section into level 1 node of the BMT tree -func (h *Hasher) writeSection(i int, section []byte, double bool, final bool) { - // select the leaf node for the section - var n *node - var isLeft bool - var hasher hash.Hash - var level int - t := h.getTree() - if double { - level++ - n = t.leaves[i] - hasher = n.hasher - isLeft = n.isLeft - n = n.parent - // hash the section - section = doSum(hasher, nil, section) - } else { - n = t.leaves[i/2] - hasher = n.hasher - isLeft = i%2 == 0 - } - // write hash into parent node - if final { - // for the last segment use writeFinalNode - h.writeFinalNode(level, n, hasher, isLeft, section) - } else { - h.writeNode(n, hasher, isLeft, section) - } -} - -// writeNode pushes the data to the node -// if it is the first of 2 sisters written, the routine terminates -// if it is the second, it calculates the hash and writes it -// to the parent node recursively -// since hashing the parent is synchronous the same hasher can be used -func (h *Hasher) writeNode(n *node, bh hash.Hash, isLeft bool, s []byte) { - level := 1 - for { - // at the root of the bmt just write the result to the result channel - if n == nil { - h.getTree().result <- s - return - } - // otherwise assign child hash to left or right segment - if isLeft { - n.left = s - } else { - n.right = s - } - // the child-thread first arriving will terminate - if n.toggle() { - return - } - // the thread coming second now can be sure both left and right children are written - // so it calculates the hash of left|right and pushes it to the parent - s = doSum(bh, nil, n.left, n.right) - isLeft = n.isLeft - n = n.parent - level++ - } -} - -// writeFinalNode is following the path starting from the final datasegment to the -// BMT root via parents -// for unbalanced trees it fills in the missing right sister nodes using -// the pool's lookup table for BMT subtree root hashes for all-zero sections -// otherwise behaves like `writeNode` -func (h *Hasher) writeFinalNode(level int, n *node, bh hash.Hash, isLeft bool, s []byte) { - - for { - // at the root of the bmt just write the result to the result channel - if n == nil { - if s != nil { - h.getTree().result <- s - } - return - } - var noHash bool - if isLeft { - // coming from left sister branch - // when the final section's path is going via left child node - // we include an all-zero subtree hash for the right level and toggle the node. - n.right = h.pool.zerohashes[level] - if s != nil { - n.left = s - // if a left final node carries a hash, it must be the first (and only thread) - // so the toggle is already in passive state no need no call - // yet thread needs to carry on pushing hash to parent - noHash = false - } else { - // if again first thread then propagate nil and calculate no hash - noHash = n.toggle() - } - } else { - // right sister branch - if s != nil { - // if hash was pushed from right child node, write right segment change state - n.right = s - // if toggle is true, we arrived first so no hashing just push nil to parent - noHash = n.toggle() - - } else { - // if s is nil, then thread arrived first at previous node and here there will be two, - // so no need to do anything and keep s = nil for parent - noHash = true - } - } - // the child-thread first arriving will just continue resetting s to nil - // the second thread now can be sure both left and right children are written - // it calculates the hash of left|right and pushes it to the parent - if noHash { - s = nil - } else { - s = doSum(bh, nil, n.left, n.right) - } - // iterate to parent - isLeft = n.isLeft - n = n.parent - level++ - } -} - -// getTree obtains a BMT resource by reserving one from the pool and assigns it to the bmt field -func (h *Hasher) getTree() *tree { - if h.bmt != nil { - return h.bmt - } - t := h.pool.reserve() - h.bmt = t - return t -} - -// atomic bool toggle implementing a concurrent reusable 2-state object -// atomic addint with %2 implements atomic bool toggle -// it returns true if the toggler just put it in the active/waiting state -func (n *node) toggle() bool { - return atomic.AddInt32(&n.state, 1)%2 == 1 -} - -// calculates the hash of the data using hash.Hash -func doSum(h hash.Hash, b []byte, data ...[]byte) []byte { - h.Reset() - for _, v := range data { - h.Write(v) - } - return h.Sum(b) -} - -// hashstr is a pretty printer for bytes used in tree.draw -func hashstr(b []byte) string { - end := len(b) - if end > 4 { - end = 4 - } - return fmt.Sprintf("%x", b[:end]) -} - -// calculateDepthFor calculates the depth (number of levels) in the BMT tree -func calculateDepthFor(n int) (d int) { - c := 2 - for ; c < n; c *= 2 { - d++ - } - return d + 1 -} diff --git a/swarm/bmt/bmt_r.go b/swarm/bmt/bmt_r.go deleted file mode 100644 index 0cb6c146f5d7..000000000000 --- a/swarm/bmt/bmt_r.go +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Package bmt is a simple nonconcurrent reference implementation for hashsize segment based -// Binary Merkle tree hash on arbitrary but fixed maximum chunksize -// -// This implementation does not take advantage of any paralellisms and uses -// far more memory than necessary, but it is easy to see that it is correct. -// It can be used for generating test cases for optimized implementations. -// There is extra check on reference hasher correctness in bmt_test.go -// * TestRefHasher -// * testBMTHasherCorrectness function -package bmt - -import ( - "hash" -) - -// RefHasher is the non-optimized easy-to-read reference implementation of BMT -type RefHasher struct { - maxDataLength int // c * hashSize, where c = 2 ^ ceil(log2(count)), where count = ceil(length / hashSize) - sectionLength int // 2 * hashSize - hasher hash.Hash // base hash func (Keccak256 SHA3) -} - -// NewRefHasher returns a new RefHasher -func NewRefHasher(hasher BaseHasherFunc, count int) *RefHasher { - h := hasher() - hashsize := h.Size() - c := 2 - for ; c < count; c *= 2 { - } - return &RefHasher{ - sectionLength: 2 * hashsize, - maxDataLength: c * hashsize, - hasher: h, - } -} - -// Hash returns the BMT hash of the byte slice -// implements the SwarmHash interface -func (rh *RefHasher) Hash(data []byte) []byte { - // if data is shorter than the base length (maxDataLength), we provide padding with zeros - d := make([]byte, rh.maxDataLength) - length := len(data) - if length > rh.maxDataLength { - length = rh.maxDataLength - } - copy(d, data[:length]) - return rh.hash(d, rh.maxDataLength) -} - -// data has length maxDataLength = segmentSize * 2^k -// hash calls itself recursively on both halves of the given slice -// concatenates the results, and returns the hash of that -// if the length of d is 2 * segmentSize then just returns the hash of that section -func (rh *RefHasher) hash(data []byte, length int) []byte { - var section []byte - if length == rh.sectionLength { - // section contains two data segments (d) - section = data - } else { - // section contains hashes of left and right BMT subtreea - // to be calculated by calling hash recursively on left and right half of d - length /= 2 - section = append(rh.hash(data[:length], length), rh.hash(data[length:], length)...) - } - rh.hasher.Reset() - rh.hasher.Write(section) - return rh.hasher.Sum(nil) -} diff --git a/swarm/bmt/bmt_test.go b/swarm/bmt/bmt_test.go deleted file mode 100644 index 286b065b73cf..000000000000 --- a/swarm/bmt/bmt_test.go +++ /dev/null @@ -1,583 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package bmt - -import ( - "bytes" - "encoding/binary" - "fmt" - "math/rand" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/ubiq/go-ubiq/swarm/testutil" - "golang.org/x/crypto/sha3" -) - -// the actual data length generated (could be longer than max datalength of the BMT) -const BufferSize = 4128 - -const ( - // segmentCount is the maximum number of segments of the underlying chunk - // Should be equal to max-chunk-data-size / hash-size - // Currently set to 128 == 4096 (default chunk size) / 32 (sha3.keccak256 size) - segmentCount = 128 -) - -var counts = []int{1, 2, 3, 4, 5, 8, 9, 15, 16, 17, 32, 37, 42, 53, 63, 64, 65, 111, 127, 128} - -// calculates the Keccak256 SHA3 hash of the data -func sha3hash(data ...[]byte) []byte { - h := sha3.NewLegacyKeccak256() - return doSum(h, nil, data...) -} - -// TestRefHasher tests that the RefHasher computes the expected BMT hash for -// some small data lengths -func TestRefHasher(t *testing.T) { - // the test struct is used to specify the expected BMT hash for - // segment counts between from and to and lengths from 1 to datalength - type test struct { - from int - to int - expected func([]byte) []byte - } - - var tests []*test - // all lengths in [0,64] should be: - // - // sha3hash(data) - // - tests = append(tests, &test{ - from: 1, - to: 2, - expected: func(d []byte) []byte { - data := make([]byte, 64) - copy(data, d) - return sha3hash(data) - }, - }) - - // all lengths in [3,4] should be: - // - // sha3hash( - // sha3hash(data[:64]) - // sha3hash(data[64:]) - // ) - // - tests = append(tests, &test{ - from: 3, - to: 4, - expected: func(d []byte) []byte { - data := make([]byte, 128) - copy(data, d) - return sha3hash(sha3hash(data[:64]), sha3hash(data[64:])) - }, - }) - - // all segmentCounts in [5,8] should be: - // - // sha3hash( - // sha3hash( - // sha3hash(data[:64]) - // sha3hash(data[64:128]) - // ) - // sha3hash( - // sha3hash(data[128:192]) - // sha3hash(data[192:]) - // ) - // ) - // - tests = append(tests, &test{ - from: 5, - to: 8, - expected: func(d []byte) []byte { - data := make([]byte, 256) - copy(data, d) - return sha3hash(sha3hash(sha3hash(data[:64]), sha3hash(data[64:128])), sha3hash(sha3hash(data[128:192]), sha3hash(data[192:]))) - }, - }) - - // run the tests - for i, x := range tests { - for segmentCount := x.from; segmentCount <= x.to; segmentCount++ { - for length := 1; length <= segmentCount*32; length++ { - t.Run(fmt.Sprintf("%d_segments_%d_bytes", segmentCount, length), func(t *testing.T) { - data := testutil.RandomBytes(i, length) - expected := x.expected(data) - actual := NewRefHasher(sha3.NewLegacyKeccak256, segmentCount).Hash(data) - if !bytes.Equal(actual, expected) { - t.Fatalf("expected %x, got %x", expected, actual) - } - }) - } - } - } -} - -// tests if hasher responds with correct hash comparing the reference implementation return value -func TestHasherEmptyData(t *testing.T) { - hasher := sha3.NewLegacyKeccak256 - var data []byte - for _, count := range counts { - t.Run(fmt.Sprintf("%d_segments", count), func(t *testing.T) { - pool := NewTreePool(hasher, count, PoolSize) - defer pool.Drain(0) - bmt := New(pool) - rbmt := NewRefHasher(hasher, count) - refHash := rbmt.Hash(data) - expHash := syncHash(bmt, nil, data) - if !bytes.Equal(expHash, refHash) { - t.Fatalf("hash mismatch with reference. expected %x, got %x", refHash, expHash) - } - }) - } -} - -// tests sequential write with entire max size written in one go -func TestSyncHasherCorrectness(t *testing.T) { - data := testutil.RandomBytes(1, BufferSize) - hasher := sha3.NewLegacyKeccak256 - size := hasher().Size() - - var err error - for _, count := range counts { - t.Run(fmt.Sprintf("segments_%v", count), func(t *testing.T) { - max := count * size - var incr int - capacity := 1 - pool := NewTreePool(hasher, count, capacity) - defer pool.Drain(0) - for n := 0; n <= max; n += incr { - incr = 1 + rand.Intn(5) - bmt := New(pool) - err = testHasherCorrectness(bmt, hasher, data, n, count) - if err != nil { - t.Fatal(err) - } - } - }) - } -} - -// tests order-neutral concurrent writes with entire max size written in one go -func TestAsyncCorrectness(t *testing.T) { - data := testutil.RandomBytes(1, BufferSize) - hasher := sha3.NewLegacyKeccak256 - size := hasher().Size() - whs := []whenHash{first, last, random} - - for _, double := range []bool{false, true} { - for _, wh := range whs { - for _, count := range counts { - t.Run(fmt.Sprintf("double_%v_hash_when_%v_segments_%v", double, wh, count), func(t *testing.T) { - max := count * size - var incr int - capacity := 1 - pool := NewTreePool(hasher, count, capacity) - defer pool.Drain(0) - for n := 1; n <= max; n += incr { - incr = 1 + rand.Intn(5) - bmt := New(pool) - d := data[:n] - rbmt := NewRefHasher(hasher, count) - exp := rbmt.Hash(d) - got := syncHash(bmt, nil, d) - if !bytes.Equal(got, exp) { - t.Fatalf("wrong sync hash for datalength %v: expected %x (ref), got %x", n, exp, got) - } - sw := bmt.NewAsyncWriter(double) - got = asyncHashRandom(sw, nil, d, wh) - if !bytes.Equal(got, exp) { - t.Fatalf("wrong async hash for datalength %v: expected %x, got %x", n, exp, got) - } - } - }) - } - } - } -} - -// Tests that the BMT hasher can be synchronously reused with poolsizes 1 and PoolSize -func TestHasherReuse(t *testing.T) { - t.Run(fmt.Sprintf("poolsize_%d", 1), func(t *testing.T) { - testHasherReuse(1, t) - }) - t.Run(fmt.Sprintf("poolsize_%d", PoolSize), func(t *testing.T) { - testHasherReuse(PoolSize, t) - }) -} - -// tests if bmt reuse is not corrupting result -func testHasherReuse(poolsize int, t *testing.T) { - hasher := sha3.NewLegacyKeccak256 - pool := NewTreePool(hasher, segmentCount, poolsize) - defer pool.Drain(0) - bmt := New(pool) - - for i := 0; i < 100; i++ { - data := testutil.RandomBytes(1, BufferSize) - n := rand.Intn(bmt.Size()) - err := testHasherCorrectness(bmt, hasher, data, n, segmentCount) - if err != nil { - t.Fatal(err) - } - } -} - -// Tests if pool can be cleanly reused even in concurrent use by several hasher -func TestBMTConcurrentUse(t *testing.T) { - hasher := sha3.NewLegacyKeccak256 - pool := NewTreePool(hasher, segmentCount, PoolSize) - defer pool.Drain(0) - cycles := 100 - errc := make(chan error) - - for i := 0; i < cycles; i++ { - go func() { - bmt := New(pool) - data := testutil.RandomBytes(1, BufferSize) - n := rand.Intn(bmt.Size()) - errc <- testHasherCorrectness(bmt, hasher, data, n, 128) - }() - } -LOOP: - for { - select { - case <-time.NewTimer(5 * time.Second).C: - t.Fatal("timed out") - case err := <-errc: - if err != nil { - t.Fatal(err) - } - cycles-- - if cycles == 0 { - break LOOP - } - } - } -} - -// Tests BMT Hasher io.Writer interface is working correctly -// even multiple short random write buffers -func TestBMTWriterBuffers(t *testing.T) { - hasher := sha3.NewLegacyKeccak256 - - for _, count := range counts { - t.Run(fmt.Sprintf("%d_segments", count), func(t *testing.T) { - errc := make(chan error) - pool := NewTreePool(hasher, count, PoolSize) - defer pool.Drain(0) - n := count * 32 - bmt := New(pool) - data := testutil.RandomBytes(1, n) - rbmt := NewRefHasher(hasher, count) - refHash := rbmt.Hash(data) - expHash := syncHash(bmt, nil, data) - if !bytes.Equal(expHash, refHash) { - t.Fatalf("hash mismatch with reference. expected %x, got %x", refHash, expHash) - } - attempts := 10 - f := func() error { - bmt := New(pool) - bmt.Reset() - var buflen int - for offset := 0; offset < n; offset += buflen { - buflen = rand.Intn(n-offset) + 1 - read, err := bmt.Write(data[offset : offset+buflen]) - if err != nil { - return err - } - if read != buflen { - return fmt.Errorf("incorrect read. expected %v bytes, got %v", buflen, read) - } - } - hash := bmt.Sum(nil) - if !bytes.Equal(hash, expHash) { - return fmt.Errorf("hash mismatch. expected %x, got %x", hash, expHash) - } - return nil - } - - for j := 0; j < attempts; j++ { - go func() { - errc <- f() - }() - } - timeout := time.NewTimer(2 * time.Second) - for { - select { - case err := <-errc: - if err != nil { - t.Fatal(err) - } - attempts-- - if attempts == 0 { - return - } - case <-timeout.C: - t.Fatalf("timeout") - } - } - }) - } -} - -// helper function that compares reference and optimised implementations on -// correctness -func testHasherCorrectness(bmt *Hasher, hasher BaseHasherFunc, d []byte, n, count int) (err error) { - span := make([]byte, 8) - if len(d) < n { - n = len(d) - } - binary.BigEndian.PutUint64(span, uint64(n)) - data := d[:n] - rbmt := NewRefHasher(hasher, count) - exp := sha3hash(span, rbmt.Hash(data)) - got := syncHash(bmt, span, data) - if !bytes.Equal(got, exp) { - return fmt.Errorf("wrong hash: expected %x, got %x", exp, got) - } - return err -} - -// -func BenchmarkBMT(t *testing.B) { - for size := 4096; size >= 128; size /= 2 { - t.Run(fmt.Sprintf("%v_size_%v", "SHA3", size), func(t *testing.B) { - benchmarkSHA3(t, size) - }) - t.Run(fmt.Sprintf("%v_size_%v", "Baseline", size), func(t *testing.B) { - benchmarkBMTBaseline(t, size) - }) - t.Run(fmt.Sprintf("%v_size_%v", "REF", size), func(t *testing.B) { - benchmarkRefHasher(t, size) - }) - t.Run(fmt.Sprintf("%v_size_%v", "BMT", size), func(t *testing.B) { - benchmarkBMT(t, size) - }) - } -} - -type whenHash = int - -const ( - first whenHash = iota - last - random -) - -func BenchmarkBMTAsync(t *testing.B) { - whs := []whenHash{first, last, random} - for size := 4096; size >= 128; size /= 2 { - for _, wh := range whs { - for _, double := range []bool{false, true} { - t.Run(fmt.Sprintf("double_%v_hash_when_%v_size_%v", double, wh, size), func(t *testing.B) { - benchmarkBMTAsync(t, size, wh, double) - }) - } - } - } -} - -func BenchmarkPool(t *testing.B) { - caps := []int{1, PoolSize} - for size := 4096; size >= 128; size /= 2 { - for _, c := range caps { - t.Run(fmt.Sprintf("poolsize_%v_size_%v", c, size), func(t *testing.B) { - benchmarkPool(t, c, size) - }) - } - } -} - -// benchmarks simple sha3 hash on chunks -func benchmarkSHA3(t *testing.B, n int) { - data := testutil.RandomBytes(1, n) - hasher := sha3.NewLegacyKeccak256 - h := hasher() - - t.ReportAllocs() - t.ResetTimer() - for i := 0; i < t.N; i++ { - doSum(h, nil, data) - } -} - -// benchmarks the minimum hashing time for a balanced (for simplicity) BMT -// by doing count/segmentsize parallel hashings of 2*segmentsize bytes -// doing it on n PoolSize each reusing the base hasher -// the premise is that this is the minimum computation needed for a BMT -// therefore this serves as a theoretical optimum for concurrent implementations -func benchmarkBMTBaseline(t *testing.B, n int) { - hasher := sha3.NewLegacyKeccak256 - hashSize := hasher().Size() - data := testutil.RandomBytes(1, hashSize) - - t.ReportAllocs() - t.ResetTimer() - for i := 0; i < t.N; i++ { - count := int32((n-1)/hashSize + 1) - wg := sync.WaitGroup{} - wg.Add(PoolSize) - var i int32 - for j := 0; j < PoolSize; j++ { - go func() { - defer wg.Done() - h := hasher() - for atomic.AddInt32(&i, 1) < count { - doSum(h, nil, data) - } - }() - } - wg.Wait() - } -} - -// benchmarks BMT Hasher -func benchmarkBMT(t *testing.B, n int) { - data := testutil.RandomBytes(1, n) - hasher := sha3.NewLegacyKeccak256 - pool := NewTreePool(hasher, segmentCount, PoolSize) - bmt := New(pool) - - t.ReportAllocs() - t.ResetTimer() - for i := 0; i < t.N; i++ { - syncHash(bmt, nil, data) - } -} - -// benchmarks BMT hasher with asynchronous concurrent segment/section writes -func benchmarkBMTAsync(t *testing.B, n int, wh whenHash, double bool) { - data := testutil.RandomBytes(1, n) - hasher := sha3.NewLegacyKeccak256 - pool := NewTreePool(hasher, segmentCount, PoolSize) - bmt := New(pool).NewAsyncWriter(double) - idxs, segments := splitAndShuffle(bmt.SectionSize(), data) - rand.Shuffle(len(idxs), func(i int, j int) { - idxs[i], idxs[j] = idxs[j], idxs[i] - }) - - t.ReportAllocs() - t.ResetTimer() - for i := 0; i < t.N; i++ { - asyncHash(bmt, nil, n, wh, idxs, segments) - } -} - -// benchmarks 100 concurrent bmt hashes with pool capacity -func benchmarkPool(t *testing.B, poolsize, n int) { - data := testutil.RandomBytes(1, n) - hasher := sha3.NewLegacyKeccak256 - pool := NewTreePool(hasher, segmentCount, poolsize) - cycles := 100 - - t.ReportAllocs() - t.ResetTimer() - wg := sync.WaitGroup{} - for i := 0; i < t.N; i++ { - wg.Add(cycles) - for j := 0; j < cycles; j++ { - go func() { - defer wg.Done() - bmt := New(pool) - syncHash(bmt, nil, data) - }() - } - wg.Wait() - } -} - -// benchmarks the reference hasher -func benchmarkRefHasher(t *testing.B, n int) { - data := testutil.RandomBytes(1, n) - hasher := sha3.NewLegacyKeccak256 - rbmt := NewRefHasher(hasher, 128) - - t.ReportAllocs() - t.ResetTimer() - for i := 0; i < t.N; i++ { - rbmt.Hash(data) - } -} - -// Hash hashes the data and the span using the bmt hasher -func syncHash(h *Hasher, span, data []byte) []byte { - h.ResetWithLength(span) - h.Write(data) - return h.Sum(nil) -} - -func splitAndShuffle(secsize int, data []byte) (idxs []int, segments [][]byte) { - l := len(data) - n := l / secsize - if l%secsize > 0 { - n++ - } - for i := 0; i < n; i++ { - idxs = append(idxs, i) - end := (i + 1) * secsize - if end > l { - end = l - } - section := data[i*secsize : end] - segments = append(segments, section) - } - rand.Shuffle(n, func(i int, j int) { - idxs[i], idxs[j] = idxs[j], idxs[i] - }) - return idxs, segments -} - -// splits the input data performs a random shuffle to mock async section writes -func asyncHashRandom(bmt SectionWriter, span []byte, data []byte, wh whenHash) (s []byte) { - idxs, segments := splitAndShuffle(bmt.SectionSize(), data) - return asyncHash(bmt, span, len(data), wh, idxs, segments) -} - -// mock for async section writes for BMT SectionWriter -// requires a permutation (a random shuffle) of list of all indexes of segments -// and writes them in order to the appropriate section -// the Sum function is called according to the wh parameter (first, last, random [relative to segment writes]) -func asyncHash(bmt SectionWriter, span []byte, l int, wh whenHash, idxs []int, segments [][]byte) (s []byte) { - bmt.Reset() - if l == 0 { - return bmt.Sum(nil, l, span) - } - c := make(chan []byte, 1) - hashf := func() { - c <- bmt.Sum(nil, l, span) - } - maxsize := len(idxs) - var r int - if wh == random { - r = rand.Intn(maxsize) - } - for i, idx := range idxs { - bmt.Write(idx, segments[idx]) - if (wh == first || wh == random) && i == r { - go hashf() - } - } - if wh == last { - return bmt.Sum(nil, l, span) - } - return <-c -} diff --git a/swarm/chunk/chunk.go b/swarm/chunk/chunk.go deleted file mode 100644 index 1449efccd0ef..000000000000 --- a/swarm/chunk/chunk.go +++ /dev/null @@ -1,5 +0,0 @@ -package chunk - -const ( - DefaultSize = 4096 -) diff --git a/swarm/dev/.dockerignore b/swarm/dev/.dockerignore deleted file mode 100644 index f9e69b37f369..000000000000 --- a/swarm/dev/.dockerignore +++ /dev/null @@ -1,2 +0,0 @@ -bin/* -cluster/* diff --git a/swarm/dev/.gitignore b/swarm/dev/.gitignore deleted file mode 100644 index f9e69b37f369..000000000000 --- a/swarm/dev/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -bin/* -cluster/* diff --git a/swarm/dev/Dockerfile b/swarm/dev/Dockerfile deleted file mode 100644 index 728bdab1fb30..000000000000 --- a/swarm/dev/Dockerfile +++ /dev/null @@ -1,42 +0,0 @@ -FROM ubuntu:xenial - -# install build + test dependencies -RUN apt-get update && \ - apt-get install --yes --no-install-recommends \ - ca-certificates \ - curl \ - fuse \ - g++ \ - gcc \ - git \ - iproute2 \ - iputils-ping \ - less \ - libc6-dev \ - make \ - pkg-config \ - && \ - apt-get clean - -# install Go -ENV GO_VERSION 1.8.1 -RUN curl -fSLo golang.tar.gz "https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz" && \ - tar -xzf golang.tar.gz -C /usr/local && \ - rm golang.tar.gz -ENV GOPATH /go -ENV PATH $GOPATH/bin:/usr/local/go/bin:$PATH - -# install docker CLI -RUN curl -fSLo docker.tar.gz https://get.docker.com/builds/Linux/x86_64/docker-17.04.0-ce.tgz && \ - tar -xzf docker.tar.gz -C /usr/local/bin --strip-components=1 docker/docker && \ - rm docker.tar.gz - -# install jq -RUN curl -fSLo /usr/local/bin/jq https://github.com/stedolan/jq/releases/download/jq-1.5/jq-linux64 && \ - chmod +x /usr/local/bin/jq - -# install govendor -RUN go get -u github.com/kardianos/govendor - -# add custom bashrc -ADD bashrc /root/.bashrc diff --git a/swarm/dev/Makefile b/swarm/dev/Makefile deleted file mode 100644 index c0156b9076e0..000000000000 --- a/swarm/dev/Makefile +++ /dev/null @@ -1,14 +0,0 @@ -.PHONY: build cluster test - -default: build - -build: - go build -o bin/swarm github.com/ubiq/go-ubiq/cmd/swarm - go build -o bin/gubiq github.com/ubiq/go-ubiq/cmd/gubiq - go build -o bin/bootnode github.com/ubiq/go-ubiq/cmd/bootnode - -cluster: build - scripts/boot-cluster.sh - -test: - go test -v github.com/ubiq/go-ubiq/swarm/... diff --git a/swarm/dev/README.md b/swarm/dev/README.md deleted file mode 100644 index 249f0f995fcb..000000000000 --- a/swarm/dev/README.md +++ /dev/null @@ -1,20 +0,0 @@ -Swarm development environment -============================= - -The Swarm development environment is a Linux bash shell which can be run in a -Docker container and provides a predictable build and test environment. - -### Start the Docker container - -Run the `run.sh` script to build the Docker image and run it, you will then be -at a bash prompt inside the `swarm/dev` directory. - -### Build binaries - -Run `make` to build the `swarm`, `gubiq` and `bootnode` binaries into the -`swarm/dev/bin` directory. - -### Boot a cluster - -Run `make cluster` to start a 3 node Swarm cluster, or run -`scripts/boot-cluster.sh --size N` to boot a cluster of size N. diff --git a/swarm/dev/bashrc b/swarm/dev/bashrc deleted file mode 100644 index 1895bc6d28d9..000000000000 --- a/swarm/dev/bashrc +++ /dev/null @@ -1,21 +0,0 @@ -export ROOT="${GOPATH}/src/github.com/ubiq/go-ubiq" -export PATH="${ROOT}/swarm/dev/bin:${PATH}" - -cd "${ROOT}/swarm/dev" - -cat <&2 <&2 - exit 1 - fi - name="$2" - shift 2 - ;; - -d | --docker-args) - if [[ -z "$2" ]]; then - echo "ERROR: --docker-args flag requires an argument" >&2 - exit 1 - fi - docker_args="$2" - shift 2 - ;; - *) - break - ;; - esac - done - - if [[ $# -ne 0 ]]; then - usage - echo "ERROR: invalid arguments" >&2 - exit 1 - fi -} - -build_image() { - docker build --tag "${name}" "${ROOT}/swarm/dev" -} - -run_image() { - exec docker run \ - --privileged \ - --interactive \ - --tty \ - --rm \ - --hostname "${name}" \ - --name "${name}" \ - --volume "${ROOT}:/go/src/github.com/ubiq/go-ubiq" \ - --volume "/var/run/docker.sock:/var/run/docker.sock" \ - ${docker_args} \ - "${name}" \ - /bin/bash -} - -main "$@" diff --git a/swarm/dev/scripts/boot-cluster.sh b/swarm/dev/scripts/boot-cluster.sh deleted file mode 100755 index ff609f3dd79a..000000000000 --- a/swarm/dev/scripts/boot-cluster.sh +++ /dev/null @@ -1,288 +0,0 @@ -#!/bin/bash -# -# A script to boot a dev swarm cluster on a Linux host (typically in a Docker -# container started with swarm/dev/run.sh). -# -# The cluster contains a bootnode, a gubiq node and multiple swarm nodes, with -# each node having its own data directory in a base directory passed with the -# --dir flag (default is swarm/dev/cluster). -# -# To avoid using different ports for each node and to make networking more -# realistic, each node gets its own network namespace with IPs assigned from -# the 192.168.33.0/24 subnet: -# -# bootnode: 192.168.33.2 -# gubiq: 192.168.33.3 -# swarm: 192.168.33.10{1,2,...,n} - -set -e - -ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" -source "${ROOT}/swarm/dev/scripts/util.sh" - -# DEFAULT_BASE_DIR is the default base directory to store node data -DEFAULT_BASE_DIR="${ROOT}/swarm/dev/cluster" - -# DEFAULT_CLUSTER_SIZE is the default swarm cluster size -DEFAULT_CLUSTER_SIZE=3 - -# Linux bridge configuration for connecting the node network namespaces -BRIDGE_NAME="swarmbr0" -BRIDGE_IP="192.168.33.1" - -# static bootnode configuration -BOOTNODE_IP="192.168.33.2" -BOOTNODE_PORT="30301" -BOOTNODE_KEY="32078f313bea771848db70745225c52c00981589ad6b5b49163f0f5ee852617d" -BOOTNODE_PUBKEY="760c4460e5336ac9bbd87952a3c7ec4363fc0a97bd31c86430806e287b437fd1b01abc6e1db640cf3106b520344af1d58b00b57823db3e1407cbc433e1b6d04d" -BOOTNODE_URL="enode://${BOOTNODE_PUBKEY}@${BOOTNODE_IP}:${BOOTNODE_PORT}" - -# static gubiq configuration -GUBIQ_IP="192.168.33.3" -GUBIQ_RPC_PORT="8588" -GUBIQ_RPC_URL="http://${GUBIQ_IP}:${GUBIQ_RPC_PORT}" - -usage() { - cat >&2 < "${key_file}" - - local args=( - --addr "${BOOTNODE_IP}:${BOOTNODE_PORT}" - --nodekey "${key_file}" - --verbosity "6" - ) - - start_node "bootnode" "${BOOTNODE_IP}" "$(which bootnode)" ${args[@]} -} - -# start_gubiq_node starts a gubiq node with --datadir pointing at /gubiq -# and a single, unlocked account with password "gubiq" -start_gubiq_node() { - local dir="${base_dir}/gubiq" - mkdir -p "${dir}" - - local password="gubiq" - echo "${password}" > "${dir}/password" - - # create an account if necessary - if [[ ! -e "${dir}/keystore" ]]; then - info "creating gubiq account" - create_account "${dir}" "${password}" - fi - - # get the account address - local address="$(jq --raw-output '.address' ${dir}/keystore/*)" - if [[ -z "${address}" ]]; then - fail "failed to get gubiq account address" - fi - - local args=( - --datadir "${dir}" - --networkid "321" - --bootnodes "${BOOTNODE_URL}" - --unlock "${address}" - --password "${dir}/password" - --rpc - --rpcaddr "${GUBIQ_IP}" - --rpcport "${GUBIQ_RPC_PORT}" - --verbosity "6" - ) - - start_node "gubiq" "${GUBIQ_IP}" "$(which gubiq)" ${args[@]} -} - -start_swarm_nodes() { - for i in $(seq 1 ${cluster_size}); do - start_swarm_node "${i}" - done -} - -# start_swarm_node starts a swarm node with a name like "swarmNN" (where NN is -# a zero-padded integer like "07"), --datadir pointing at / -# (e.g. /swarm07) and a single account with as the password -start_swarm_node() { - local num=$1 - local name="swarm$(printf '%02d' ${num})" - local ip="192.168.33.1$(printf '%02d' ${num})" - - local dir="${base_dir}/${name}" - mkdir -p "${dir}" - - local password="${name}" - echo "${password}" > "${dir}/password" - - # create an account if necessary - if [[ ! -e "${dir}/keystore" ]]; then - info "creating account for ${name}" - create_account "${dir}" "${password}" - fi - - # get the account address - local address="$(jq --raw-output '.address' ${dir}/keystore/*)" - if [[ -z "${address}" ]]; then - fail "failed to get swarm account address" - fi - - local args=( - --bootnodes "${BOOTNODE_URL}" - --datadir "${dir}" - --identity "${name}" - --ens-api "${GUBIQ_RPC_URL}" - --bzznetworkid "321" - --bzzaccount "${address}" - --password "${dir}/password" - --verbosity "6" - ) - - start_node "${name}" "${ip}" "$(which swarm)" ${args[@]} -} - -# start_node runs the node command as a daemon in a network namespace -start_node() { - local name="$1" - local ip="$2" - local path="$3" - local cmd_args=${@:4} - - info "starting ${name} with IP ${ip}" - - create_node_network "${name}" "${ip}" - - # add a marker to the log file - cat >> "${log_dir}/${name}.log" <>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> -Starting ${name} node - $(date) ->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> - -EOF - - # run the command in the network namespace using start-stop-daemon to - # daemonise the process, sending all output to the log file - local daemon_args=( - --start - --background - --no-close - --make-pidfile - --pidfile "${pid_dir}/${name}.pid" - --exec "${path}" - ) - if ! ip netns exec "${name}" start-stop-daemon ${daemon_args[@]} -- $cmd_args &>> "${log_dir}/${name}.log"; then - fail "could not start ${name}, check ${log_dir}/${name}.log" - fi -} - -# create_node_network creates a network namespace and connects it to the Linux -# bridge using a veth pair -create_node_network() { - local name="$1" - local ip="$2" - - # create the namespace - ip netns add "${name}" - - # create the veth pair - local veth0="veth${name}0" - local veth1="veth${name}1" - ip link add name "${veth0}" type veth peer name "${veth1}" - - # add one end to the bridge - ip link set dev "${veth0}" master "${BRIDGE_NAME}" - ip link set dev "${veth0}" up - - # add the other end to the namespace, rename it eth0 and give it the ip - ip link set dev "${veth1}" netns "${name}" - ip netns exec "${name}" ip link set dev "${veth1}" name "eth0" - ip netns exec "${name}" ip link set dev "eth0" up - ip netns exec "${name}" ip address add "${ip}/24" dev "eth0" -} - -create_account() { - local dir=$1 - local password=$2 - - gubiq --datadir "${dir}" --password /dev/stdin account new <<< "${password}" -} - -main "$@" diff --git a/swarm/dev/scripts/random-uploads.sh b/swarm/dev/scripts/random-uploads.sh deleted file mode 100755 index 563a51befcf9..000000000000 --- a/swarm/dev/scripts/random-uploads.sh +++ /dev/null @@ -1,96 +0,0 @@ -#!/bin/bash -# -# A script to upload random data to a swarm cluster. -# -# Example: -# -# random-uploads.sh --addr 192.168.33.101:8500 --size 40k --count 1000 - -set -e - -ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" -source "${ROOT}/swarm/dev/scripts/util.sh" - -DEFAULT_ADDR="localhost:8500" -DEFAULT_UPLOAD_SIZE="40k" -DEFAULT_UPLOAD_COUNT="1000" - -usage() { - cat >&2 </dev/null -} - -parse_args() { - while true; do - case "$1" in - -h | --help) - usage - exit 0 - ;; - -a | --addr) - if [[ -z "$2" ]]; then - fail "--addr flag requires an argument" - fi - addr="$2" - shift 2 - ;; - -s | --size) - if [[ -z "$2" ]]; then - fail "--size flag requires an argument" - fi - upload_size="$2" - shift 2 - ;; - -c | --count) - if [[ -z "$2" ]]; then - fail "--count flag requires an argument" - fi - upload_count="$2" - shift 2 - ;; - *) - break - ;; - esac - done - - if [[ $# -ne 0 ]]; then - usage - fail "ERROR: invalid arguments: $@" - fi -} - -main "$@" diff --git a/swarm/dev/scripts/stop-cluster.sh b/swarm/dev/scripts/stop-cluster.sh deleted file mode 100755 index 82752830a86f..000000000000 --- a/swarm/dev/scripts/stop-cluster.sh +++ /dev/null @@ -1,98 +0,0 @@ -#!/bin/bash -# -# A script to shutdown a dev swarm cluster. - -set -e - -ROOT="$(cd "$(dirname "$0")/../../.." && pwd)" -source "${ROOT}/swarm/dev/scripts/util.sh" - -DEFAULT_BASE_DIR="${ROOT}/swarm/dev/cluster" - -usage() { - cat >&2 </dev/null; then - ip link delete dev "veth${name}0" - fi -} - -delete_network() { - if ip link show "swarmbr0" &>/dev/null; then - ip link delete dev "swarmbr0" - fi -} - -main "$@" diff --git a/swarm/dev/scripts/util.sh b/swarm/dev/scripts/util.sh deleted file mode 100644 index f17a12e420d0..000000000000 --- a/swarm/dev/scripts/util.sh +++ /dev/null @@ -1,53 +0,0 @@ -# shared shell functions - -info() { - local msg="$@" - local timestamp="$(date +%H:%M:%S)" - say "===> ${timestamp} ${msg}" "green" -} - -warn() { - local msg="$@" - local timestamp=$(date +%H:%M:%S) - say "===> ${timestamp} WARN: ${msg}" "yellow" >&2 -} - -fail() { - local msg="$@" - say "ERROR: ${msg}" "red" >&2 - exit 1 -} - -# say prints the given message to STDOUT, using the optional color if -# STDOUT is a terminal. -# -# usage: -# -# say "foo" - prints "foo" -# say "bar" "red" - prints "bar" in red -# say "baz" "green" - prints "baz" in green -# say "qux" "red" | tee - prints "qux" with no colour -# -say() { - local msg=$1 - local color=$2 - - if [[ -n "${color}" ]] && [[ -t 1 ]]; then - case "${color}" in - red) - echo -e "\033[1;31m${msg}\033[0m" - ;; - green) - echo -e "\033[1;32m${msg}\033[0m" - ;; - yellow) - echo -e "\033[1;33m${msg}\033[0m" - ;; - *) - echo "${msg}" - ;; - esac - else - echo "${msg}" - fi -} diff --git a/swarm/docker/Dockerfile b/swarm/docker/Dockerfile deleted file mode 100644 index cb11697c22d0..000000000000 --- a/swarm/docker/Dockerfile +++ /dev/null @@ -1,32 +0,0 @@ -FROM golang:1.11-alpine as builder - -ARG VERSION - -RUN apk add --update git gcc g++ linux-headers -RUN mkdir -p $GOPATH/src/github.com/ethereum && \ - cd $GOPATH/src/github.com/ethereum && \ - git clone https://github.com/ethersphere/go-ethereum && \ - cd $GOPATH/src/github.com/ubiq/go-ubiq && \ - git checkout ${VERSION} && \ - go install -ldflags "-X main.gitCommit=${VERSION}" ./cmd/swarm && \ - go install -ldflags "-X main.gitCommit=${VERSION}" ./cmd/swarm/swarm-smoke && \ - go install -ldflags "-X main.gitCommit=${VERSION}" ./cmd/swarm/global-store && \ - go install -ldflags "-X main.gitCommit=${VERSION}" ./cmd/gubiq - - -FROM alpine:3.8 as swarm-smoke -WORKDIR / -COPY --from=builder /go/bin/swarm-smoke / -ADD run-smoke.sh /run-smoke.sh -ENTRYPOINT ["/run-smoke.sh"] - -FROM alpine:3.8 as swarm-global-store -WORKDIR / -COPY --from=builder /go/bin/global-store / -ENTRYPOINT ["/global-store"] - -FROM alpine:3.8 as swarm -WORKDIR / -COPY --from=builder /go/bin/swarm /go/bin/gubiq / -ADD run.sh /run.sh -ENTRYPOINT ["/run.sh"] diff --git a/swarm/docker/run-smoke.sh b/swarm/docker/run-smoke.sh deleted file mode 100755 index ba57a7ecd654..000000000000 --- a/swarm/docker/run-smoke.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -set -o errexit -set -o pipefail -set -o nounset - -/swarm-smoke $@ 2>&1 || true diff --git a/swarm/docker/run.sh b/swarm/docker/run.sh deleted file mode 100755 index 1f06ee882b6a..000000000000 --- a/swarm/docker/run.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh - -set -o errexit -set -o pipefail -set -o nounset - -PASSWORD=${PASSWORD:-} -DATADIR=${DATADIR:-/root/.ubiq/} - -if [ "$PASSWORD" == "" ]; then echo "Password must be set, in order to use swarm non-interactively." && exit 1; fi - -echo $PASSWORD > /password - -KEYFILE=`find $DATADIR | grep UTC | head -n 1` || true -if [ ! -f "$KEYFILE" ]; then echo "No keyfile found. Generating..." && /gubiq --datadir $DATADIR --password /password account new; fi -KEYFILE=`find $DATADIR | grep UTC | head -n 1` || true -if [ ! -f "$KEYFILE" ]; then echo "Could not find nor generate a BZZ keyfile." && exit 1; else echo "Found keyfile $KEYFILE"; fi - -VERSION=`/swarm version` -echo "Running Swarm:" -echo $VERSION - -export BZZACCOUNT="`echo -n $KEYFILE | tail -c 40`" || true -if [ "$BZZACCOUNT" == "" ]; then echo "Could not parse BZZACCOUNT from keyfile." && exit 1; fi - -exec /swarm --bzzaccount=$BZZACCOUNT --password /password --datadir $DATADIR $@ 2>&1 diff --git a/swarm/fuse/fuse_dir.go b/swarm/fuse/fuse_dir.go deleted file mode 100644 index abb8231f6e04..000000000000 --- a/swarm/fuse/fuse_dir.go +++ /dev/null @@ -1,161 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build linux darwin freebsd - -package fuse - -import ( - "os" - "path/filepath" - "sync" - - "bazil.org/fuse" - "bazil.org/fuse/fs" - "github.com/ubiq/go-ubiq/swarm/log" - "golang.org/x/net/context" -) - -var ( - _ fs.Node = (*SwarmDir)(nil) - _ fs.NodeRequestLookuper = (*SwarmDir)(nil) - _ fs.HandleReadDirAller = (*SwarmDir)(nil) - _ fs.NodeCreater = (*SwarmDir)(nil) - _ fs.NodeRemover = (*SwarmDir)(nil) - _ fs.NodeMkdirer = (*SwarmDir)(nil) -) - -type SwarmDir struct { - inode uint64 - name string - path string - directories []*SwarmDir - files []*SwarmFile - - mountInfo *MountInfo - lock *sync.RWMutex -} - -func NewSwarmDir(fullpath string, minfo *MountInfo) *SwarmDir { - log.Debug("swarmfs", "NewSwarmDir", fullpath) - newdir := &SwarmDir{ - inode: NewInode(), - name: filepath.Base(fullpath), - path: fullpath, - directories: []*SwarmDir{}, - files: []*SwarmFile{}, - mountInfo: minfo, - lock: &sync.RWMutex{}, - } - return newdir -} - -func (sd *SwarmDir) Attr(ctx context.Context, a *fuse.Attr) error { - sd.lock.RLock() - defer sd.lock.RUnlock() - a.Inode = sd.inode - a.Mode = os.ModeDir | 0700 - a.Uid = uint32(os.Getuid()) - a.Gid = uint32(os.Getegid()) - return nil -} - -func (sd *SwarmDir) Lookup(ctx context.Context, req *fuse.LookupRequest, resp *fuse.LookupResponse) (fs.Node, error) { - log.Debug("swarmfs", "Lookup", req.Name) - for _, n := range sd.files { - if n.name == req.Name { - return n, nil - } - } - for _, n := range sd.directories { - if n.name == req.Name { - return n, nil - } - } - return nil, fuse.ENOENT -} - -func (sd *SwarmDir) ReadDirAll(ctx context.Context) ([]fuse.Dirent, error) { - log.Debug("swarmfs ReadDirAll") - var children []fuse.Dirent - for _, file := range sd.files { - children = append(children, fuse.Dirent{Inode: file.inode, Type: fuse.DT_File, Name: file.name}) - } - for _, dir := range sd.directories { - children = append(children, fuse.Dirent{Inode: dir.inode, Type: fuse.DT_Dir, Name: dir.name}) - } - return children, nil -} - -func (sd *SwarmDir) Create(ctx context.Context, req *fuse.CreateRequest, resp *fuse.CreateResponse) (fs.Node, fs.Handle, error) { - log.Debug("swarmfs Create", "path", sd.path, "req.Name", req.Name) - - newFile := NewSwarmFile(sd.path, req.Name, sd.mountInfo) - newFile.fileSize = 0 // 0 means, file is not in swarm yet and it is just created - - sd.lock.Lock() - defer sd.lock.Unlock() - sd.files = append(sd.files, newFile) - - return newFile, newFile, nil -} - -func (sd *SwarmDir) Remove(ctx context.Context, req *fuse.RemoveRequest) error { - log.Debug("swarmfs Remove", "path", sd.path, "req.Name", req.Name) - - if req.Dir && sd.directories != nil { - newDirs := []*SwarmDir{} - for _, dir := range sd.directories { - if dir.name == req.Name { - removeDirectoryFromSwarm(dir) - } else { - newDirs = append(newDirs, dir) - } - } - if len(sd.directories) > len(newDirs) { - sd.lock.Lock() - defer sd.lock.Unlock() - sd.directories = newDirs - } - return nil - } else if !req.Dir && sd.files != nil { - newFiles := []*SwarmFile{} - for _, f := range sd.files { - if f.name == req.Name { - removeFileFromSwarm(f) - } else { - newFiles = append(newFiles, f) - } - } - if len(sd.files) > len(newFiles) { - sd.lock.Lock() - defer sd.lock.Unlock() - sd.files = newFiles - } - return nil - } - return fuse.ENOENT -} - -func (sd *SwarmDir) Mkdir(ctx context.Context, req *fuse.MkdirRequest) (fs.Node, error) { - log.Debug("swarmfs Mkdir", "path", sd.path, "req.Name", req.Name) - newDir := NewSwarmDir(filepath.Join(sd.path, req.Name), sd.mountInfo) - sd.lock.Lock() - defer sd.lock.Unlock() - sd.directories = append(sd.directories, newDir) - - return newDir, nil -} diff --git a/swarm/fuse/fuse_file.go b/swarm/fuse/fuse_file.go deleted file mode 100644 index e3f8419c77ba..000000000000 --- a/swarm/fuse/fuse_file.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build linux darwin freebsd - -package fuse - -import ( - "errors" - "io" - "os" - "sync" - - "bazil.org/fuse" - "bazil.org/fuse/fs" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/storage" - "golang.org/x/net/context" -) - -const ( - MaxAppendFileSize = 10485760 // 10Mb -) - -var ( - errInvalidOffset = errors.New("Invalid offset during write") - errFileSizeMaxLimixReached = errors.New("File size exceeded max limit") -) - -var ( - _ fs.Node = (*SwarmFile)(nil) - _ fs.HandleReader = (*SwarmFile)(nil) - _ fs.HandleWriter = (*SwarmFile)(nil) -) - -type SwarmFile struct { - inode uint64 - name string - path string - addr storage.Address - fileSize int64 - reader storage.LazySectionReader - - mountInfo *MountInfo - lock *sync.RWMutex -} - -func NewSwarmFile(path, fname string, minfo *MountInfo) *SwarmFile { - newFile := &SwarmFile{ - inode: NewInode(), - name: fname, - path: path, - addr: nil, - fileSize: -1, // -1 means , file already exists in swarm and you need to just get the size from swarm - reader: nil, - - mountInfo: minfo, - lock: &sync.RWMutex{}, - } - return newFile -} - -func (sf *SwarmFile) Attr(ctx context.Context, a *fuse.Attr) error { - log.Debug("swarmfs Attr", "path", sf.path) - sf.lock.Lock() - defer sf.lock.Unlock() - a.Inode = sf.inode - //TODO: need to get permission as argument - a.Mode = 0700 - a.Uid = uint32(os.Getuid()) - a.Gid = uint32(os.Getegid()) - - if sf.fileSize == -1 { - reader, _ := sf.mountInfo.swarmApi.Retrieve(ctx, sf.addr) - quitC := make(chan bool) - size, err := reader.Size(ctx, quitC) - if err != nil { - log.Error("Couldnt get size of file %s : %v", sf.path, err) - return err - } - sf.fileSize = size - log.Trace("swarmfs Attr", "size", size) - close(quitC) - } - a.Size = uint64(sf.fileSize) - return nil -} - -func (sf *SwarmFile) Read(ctx context.Context, req *fuse.ReadRequest, resp *fuse.ReadResponse) error { - log.Debug("swarmfs Read", "path", sf.path, "req.String", req.String()) - sf.lock.RLock() - defer sf.lock.RUnlock() - if sf.reader == nil { - sf.reader, _ = sf.mountInfo.swarmApi.Retrieve(ctx, sf.addr) - } - buf := make([]byte, req.Size) - n, err := sf.reader.ReadAt(buf, req.Offset) - if err == io.ErrUnexpectedEOF || err == io.EOF { - err = nil - } - resp.Data = buf[:n] - sf.reader = nil - - return err -} - -func (sf *SwarmFile) Write(ctx context.Context, req *fuse.WriteRequest, resp *fuse.WriteResponse) error { - log.Debug("swarmfs Write", "path", sf.path, "req.String", req.String()) - if sf.fileSize == 0 && req.Offset == 0 { - // A new file is created - err := addFileToSwarm(sf, req.Data, len(req.Data)) - if err != nil { - return err - } - resp.Size = len(req.Data) - } else if req.Offset <= sf.fileSize { - totalSize := sf.fileSize + int64(len(req.Data)) - if totalSize > MaxAppendFileSize { - log.Warn("swarmfs Append file size reached (%v) : (%v)", sf.fileSize, len(req.Data)) - return errFileSizeMaxLimixReached - } - - err := appendToExistingFileInSwarm(sf, req.Data, req.Offset, int64(len(req.Data))) - if err != nil { - return err - } - resp.Size = len(req.Data) - } else { - log.Warn("swarmfs Invalid write request size(%v) : off(%v)", sf.fileSize, req.Offset) - return errInvalidOffset - } - return nil -} diff --git a/swarm/fuse/fuse_root.go b/swarm/fuse/fuse_root.go deleted file mode 100644 index b2262d1c5a0d..000000000000 --- a/swarm/fuse/fuse_root.go +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build linux darwin freebsd - -package fuse - -import ( - "bazil.org/fuse/fs" -) - -var ( - _ fs.Node = (*SwarmDir)(nil) -) - -type SwarmRoot struct { - root *SwarmDir -} - -func (filesystem *SwarmRoot) Root() (fs.Node, error) { - return filesystem.root, nil -} diff --git a/swarm/fuse/swarmfs.go b/swarm/fuse/swarmfs.go deleted file mode 100644 index 1f6d5529e18d..000000000000 --- a/swarm/fuse/swarmfs.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package fuse - -import ( - "sync" - "time" - - "github.com/ubiq/go-ubiq/swarm/api" -) - -const ( - Swarmfs_Version = "0.1" - mountTimeout = time.Second * 5 - unmountTimeout = time.Second * 10 - maxFuseMounts = 5 -) - -var ( - swarmfs *SwarmFS // Swarm file system singleton - swarmfsLock sync.Once - - inode uint64 = 1 // global inode - inodeLock sync.RWMutex -) - -type SwarmFS struct { - swarmApi *api.API - activeMounts map[string]*MountInfo - swarmFsLock *sync.RWMutex -} - -func NewSwarmFS(api *api.API) *SwarmFS { - swarmfsLock.Do(func() { - swarmfs = &SwarmFS{ - swarmApi: api, - swarmFsLock: &sync.RWMutex{}, - activeMounts: map[string]*MountInfo{}, - } - }) - return swarmfs - -} - -// Inode numbers need to be unique, they are used for caching inside fuse -func NewInode() uint64 { - inodeLock.Lock() - defer inodeLock.Unlock() - inode += 1 - return inode -} diff --git a/swarm/fuse/swarmfs_fallback.go b/swarm/fuse/swarmfs_fallback.go deleted file mode 100644 index 4864c8689c27..000000000000 --- a/swarm/fuse/swarmfs_fallback.go +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build !linux,!darwin,!freebsd - -package fuse - -import ( - "errors" -) - -var errNoFUSE = errors.New("FUSE is not supported on this platform") - -func isFUSEUnsupportedError(err error) bool { - return err == errNoFUSE -} - -type MountInfo struct { - MountPoint string - StartManifest string - LatestManifest string -} - -func (self *SwarmFS) Mount(mhash, mountpoint string) (*MountInfo, error) { - return nil, errNoFUSE -} - -func (self *SwarmFS) Unmount(mountpoint string) (bool, error) { - return false, errNoFUSE -} - -func (self *SwarmFS) Listmounts() ([]*MountInfo, error) { - return nil, errNoFUSE -} - -func (self *SwarmFS) Stop() error { - return nil -} diff --git a/swarm/fuse/swarmfs_test.go b/swarm/fuse/swarmfs_test.go deleted file mode 100644 index c5762e3cd995..000000000000 --- a/swarm/fuse/swarmfs_test.go +++ /dev/null @@ -1,1671 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build linux darwin freebsd - -package fuse - -import ( - "bytes" - "flag" - "fmt" - "io" - "io/ioutil" - "math/rand" - "os" - "path/filepath" - "testing" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/swarm/api" - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/ubiq/go-ubiq/swarm/testutil" - colorable "github.com/mattn/go-colorable" -) - -var ( - loglevel = flag.Int("loglevel", 4, "verbosity of logs") - rawlog = flag.Bool("rawlog", false, "turn off terminal formatting in logs") - longrunning = flag.Bool("longrunning", false, "do run long-running tests") -) - -func init() { - flag.Parse() - log.PrintOrigins(true) - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(!*rawlog)))) -} - -type fileInfo struct { - perm uint64 - uid int - gid int - contents []byte -} - -//create files from the map of name and content provided and upload them to swarm via api -func createTestFilesAndUploadToSwarm(t *testing.T, api *api.API, files map[string]fileInfo, uploadDir string, toEncrypt bool) string { - - //iterate the map - for fname, finfo := range files { - actualPath := filepath.Join(uploadDir, fname) - filePath := filepath.Dir(actualPath) - - //create directory - err := os.MkdirAll(filePath, 0777) - if err != nil { - t.Fatalf("Error creating directory '%v' : %v", filePath, err) - } - - //create file - fd, err1 := os.OpenFile(actualPath, os.O_RDWR|os.O_CREATE, os.FileMode(finfo.perm)) - if err1 != nil { - t.Fatalf("Error creating file %v: %v", actualPath, err1) - } - - //write content to file - _, err = fd.Write(finfo.contents) - if err != nil { - t.Fatalf("Error writing to file '%v' : %v", filePath, err) - } - /* - Note @holisticode: It's not clear why the Chown command was added to the test suite. - Some files are initialized with different permissions in the individual test, - resulting in errors on Chown which were not checked. - After adding the checks tests would fail. - - What's then the reason to have this check in the first place? - Disabling for now - - err = fd.Chown(finfo.uid, finfo.gid) - if err != nil { - t.Fatalf("Error chown file '%v' : %v", filePath, err) - } - */ - err = fd.Chmod(os.FileMode(finfo.perm)) - if err != nil { - t.Fatalf("Error chmod file '%v' : %v", filePath, err) - } - err = fd.Sync() - if err != nil { - t.Fatalf("Error sync file '%v' : %v", filePath, err) - } - err = fd.Close() - if err != nil { - t.Fatalf("Error closing file '%v' : %v", filePath, err) - } - } - - //upload directory to swarm and return hash - bzzhash, err := Upload(uploadDir, "", api, toEncrypt) - if err != nil { - t.Fatalf("Error uploading directory %v: %vm encryption: %v", uploadDir, err, toEncrypt) - } - - return bzzhash -} - -//mount a swarm hash as a directory on files system via FUSE -func mountDir(t *testing.T, api *api.API, files map[string]fileInfo, bzzHash string, mountDir string) *SwarmFS { - swarmfs := NewSwarmFS(api) - _, err := swarmfs.Mount(bzzHash, mountDir) - if isFUSEUnsupportedError(err) { - t.Skip("FUSE not supported:", err) - } else if err != nil { - t.Fatalf("Error mounting hash %v: %v", bzzHash, err) - } - - //check directory is mounted - found := false - mi := swarmfs.Listmounts() - for _, minfo := range mi { - minfo.lock.RLock() - if minfo.MountPoint == mountDir { - if minfo.StartManifest != bzzHash || - minfo.LatestManifest != bzzHash || - minfo.fuseConnection == nil { - minfo.lock.RUnlock() - t.Fatalf("Error mounting: exp(%s): act(%s)", bzzHash, minfo.StartManifest) - } - found = true - } - minfo.lock.RUnlock() - } - - // Test listMounts - if !found { - t.Fatalf("Error getting mounts information for %v: %v", mountDir, err) - } - - // Check if file and their attributes are as expected - compareGeneratedFileWithFileInMount(t, files, mountDir) - - return swarmfs -} - -// Check if file and their attributes are as expected -func compareGeneratedFileWithFileInMount(t *testing.T, files map[string]fileInfo, mountDir string) { - err := filepath.Walk(mountDir, func(path string, f os.FileInfo, err error) error { - if f.IsDir() { - return nil - } - fname := path[len(mountDir)+1:] - if _, ok := files[fname]; !ok { - t.Fatalf(" file %v present in mount dir and is not expected", fname) - } - return nil - }) - if err != nil { - t.Fatalf("Error walking dir %v", mountDir) - } - - for fname, finfo := range files { - destinationFile := filepath.Join(mountDir, fname) - - dfinfo, err := os.Stat(destinationFile) - if err != nil { - t.Fatalf("Destination file %v missing in mount: %v", fname, err) - } - - if int64(len(finfo.contents)) != dfinfo.Size() { - t.Fatalf("file %v Size mismatch source (%v) vs destination(%v)", fname, int64(len(finfo.contents)), dfinfo.Size()) - } - - if dfinfo.Mode().Perm().String() != "-rwx------" { - t.Fatalf("file %v Permission mismatch source (-rwx------) vs destination(%v)", fname, dfinfo.Mode().Perm()) - } - - fileContents, err := ioutil.ReadFile(filepath.Join(mountDir, fname)) - if err != nil { - t.Fatalf("Could not readfile %v : %v", fname, err) - } - if !bytes.Equal(fileContents, finfo.contents) { - t.Fatalf("File %v contents mismatch: %v , %v", fname, fileContents, finfo.contents) - } - // TODO: check uid and gid - } -} - -//check mounted file with provided content -func checkFile(t *testing.T, testMountDir, fname string, contents []byte) { - destinationFile := filepath.Join(testMountDir, fname) - dfinfo, err1 := os.Stat(destinationFile) - if err1 != nil { - t.Fatalf("Could not stat file %v", destinationFile) - } - if dfinfo.Size() != int64(len(contents)) { - t.Fatalf("Mismatch in size actual(%v) vs expected(%v)", dfinfo.Size(), int64(len(contents))) - } - - fd, err2 := os.OpenFile(destinationFile, os.O_RDONLY, os.FileMode(0665)) - if err2 != nil { - t.Fatalf("Could not open file %v", destinationFile) - } - newcontent := make([]byte, len(contents)) - _, err := fd.Read(newcontent) - if err != nil { - t.Fatalf("Could not read from file %v", err) - } - err = fd.Close() - if err != nil { - t.Fatalf("Could not close file %v", err) - } - - if !bytes.Equal(contents, newcontent) { - t.Fatalf("File content mismatch expected (%v): received (%v) ", contents, newcontent) - } -} - -func isDirEmpty(name string) bool { - f, err := os.Open(name) - if err != nil { - return false - } - defer f.Close() - - _, err = f.Readdirnames(1) - - return err == io.EOF -} - -type testAPI struct { - api *api.API -} - -type testData struct { - testDir string - testUploadDir string - testMountDir string - bzzHash string - files map[string]fileInfo - toEncrypt bool - swarmfs *SwarmFS -} - -//create the root dir of a test -func (ta *testAPI) initSubtest(name string) (*testData, error) { - var err error - d := &testData{} - d.testDir, err = ioutil.TempDir(os.TempDir(), name) - if err != nil { - return nil, fmt.Errorf("Couldn't create test dir: %v", err) - } - return d, nil -} - -//upload data and mount directory -func (ta *testAPI) uploadAndMount(dat *testData, t *testing.T) (*testData, error) { - //create upload dir - err := os.MkdirAll(dat.testUploadDir, 0777) - if err != nil { - return nil, fmt.Errorf("Couldn't create upload dir: %v", err) - } - //create mount dir - err = os.MkdirAll(dat.testMountDir, 0777) - if err != nil { - return nil, fmt.Errorf("Couldn't create mount dir: %v", err) - } - //upload the file - dat.bzzHash = createTestFilesAndUploadToSwarm(t, ta.api, dat.files, dat.testUploadDir, dat.toEncrypt) - log.Debug("Created test files and uploaded to Swarm") - //mount the directory - dat.swarmfs = mountDir(t, ta.api, dat.files, dat.bzzHash, dat.testMountDir) - log.Debug("Mounted swarm fs") - return dat, nil -} - -//add a directory to the test directory tree -func addDir(root string, name string) (string, error) { - d := filepath.Join(root, name) - err := os.MkdirAll(d, 0777) - if err != nil { - return "", fmt.Errorf("Couldn't create dir inside test dir: %v", err) - } - return d, nil -} - -func (ta *testAPI) mountListAndUnmountEncrypted(t *testing.T) { - log.Debug("Starting mountListAndUnmountEncrypted test") - ta.mountListAndUnmount(t, true) - log.Debug("Test mountListAndUnmountEncrypted terminated") -} - -func (ta *testAPI) mountListAndUnmountNonEncrypted(t *testing.T) { - log.Debug("Starting mountListAndUnmountNonEncrypted test") - ta.mountListAndUnmount(t, false) - log.Debug("Test mountListAndUnmountNonEncrypted terminated") -} - -//mount a directory unmount and check the directory is empty afterwards -func (ta *testAPI) mountListAndUnmount(t *testing.T, toEncrypt bool) { - dat, err := ta.initSubtest("mountListAndUnmount") - if err != nil { - t.Fatalf("Couldn't initialize subtest dirs: %v", err) - } - defer os.RemoveAll(dat.testDir) - - dat.toEncrypt = toEncrypt - dat.testUploadDir = filepath.Join(dat.testDir, "testUploadDir") - dat.testMountDir = filepath.Join(dat.testDir, "testMountDir") - dat.files = make(map[string]fileInfo) - - dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)} - dat.files["2.txt"] = fileInfo{0711, 333, 444, testutil.RandomBytes(2, 10)} - dat.files["3.txt"] = fileInfo{0622, 333, 444, testutil.RandomBytes(3, 100)} - dat.files["4.txt"] = fileInfo{0533, 333, 444, testutil.RandomBytes(4, 1024)} - dat.files["5.txt"] = fileInfo{0544, 333, 444, testutil.RandomBytes(5, 10)} - dat.files["6.txt"] = fileInfo{0555, 333, 444, testutil.RandomBytes(6, 10)} - dat.files["7.txt"] = fileInfo{0666, 333, 444, testutil.RandomBytes(7, 10)} - dat.files["8.txt"] = fileInfo{0777, 333, 333, testutil.RandomBytes(8, 10)} - dat.files["11.txt"] = fileInfo{0777, 333, 444, testutil.RandomBytes(9, 10)} - dat.files["111.txt"] = fileInfo{0777, 333, 444, testutil.RandomBytes(10, 10)} - dat.files["two/2.txt"] = fileInfo{0777, 333, 444, testutil.RandomBytes(11, 10)} - dat.files["two/2/2.txt"] = fileInfo{0777, 333, 444, testutil.RandomBytes(12, 10)} - dat.files["two/2./2.txt"] = fileInfo{0777, 444, 444, testutil.RandomBytes(13, 10)} - dat.files["twice/2.txt"] = fileInfo{0777, 444, 333, testutil.RandomBytes(14, 200)} - dat.files["one/two/three/four/five/six/seven/eight/nine/10.txt"] = fileInfo{0777, 333, 444, testutil.RandomBytes(15, 10240)} - dat.files["one/two/three/four/five/six/six"] = fileInfo{0777, 333, 444, testutil.RandomBytes(16, 10)} - - dat, err = ta.uploadAndMount(dat, t) - if err != nil { - t.Fatalf("Error during upload of files to swarm / mount of swarm dir: %v", err) - } - defer dat.swarmfs.Stop() - // Check unmount - _, err = dat.swarmfs.Unmount(dat.testMountDir) - if err != nil { - t.Fatalf("could not unmount %v", dat.bzzHash) - } - log.Debug("Unmount successful") - if !isDirEmpty(dat.testMountDir) { - t.Fatalf("unmount didnt work for %v", dat.testMountDir) - } - log.Debug("subtest terminated") -} - -func (ta *testAPI) maxMountsEncrypted(t *testing.T) { - log.Debug("Starting maxMountsEncrypted test") - ta.runMaxMounts(t, true) - log.Debug("Test maxMountsEncrypted terminated") -} - -func (ta *testAPI) maxMountsNonEncrypted(t *testing.T) { - log.Debug("Starting maxMountsNonEncrypted test") - ta.runMaxMounts(t, false) - log.Debug("Test maxMountsNonEncrypted terminated") -} - -//mount several different directories until the maximum has been reached -func (ta *testAPI) runMaxMounts(t *testing.T, toEncrypt bool) { - dat, err := ta.initSubtest("runMaxMounts") - if err != nil { - t.Fatalf("Couldn't initialize subtest dirs: %v", err) - } - defer os.RemoveAll(dat.testDir) - - dat.toEncrypt = toEncrypt - dat.testUploadDir = filepath.Join(dat.testDir, "max-upload1") - dat.testMountDir = filepath.Join(dat.testDir, "max-mount1") - dat.files = make(map[string]fileInfo) - dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)} - - dat, err = ta.uploadAndMount(dat, t) - if err != nil { - t.Fatalf("Error during upload of files to swarm / mount of swarm dir: %v", err) - } - defer dat.swarmfs.Stop() - - dat.testUploadDir = filepath.Join(dat.testDir, "max-upload2") - dat.testMountDir = filepath.Join(dat.testDir, "max-mount2") - dat.files["2.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)} - - dat, err = ta.uploadAndMount(dat, t) - if err != nil { - t.Fatalf("Error during upload of files to swarm / mount of swarm dir: %v", err) - } - - dat.testUploadDir = filepath.Join(dat.testDir, "max-upload3") - dat.testMountDir = filepath.Join(dat.testDir, "max-mount3") - dat.files["3.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)} - - dat, err = ta.uploadAndMount(dat, t) - if err != nil { - t.Fatalf("Error during upload of files to swarm / mount of swarm dir: %v", err) - } - - dat.testUploadDir = filepath.Join(dat.testDir, "max-upload4") - dat.testMountDir = filepath.Join(dat.testDir, "max-mount4") - dat.files["4.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)} - - dat, err = ta.uploadAndMount(dat, t) - if err != nil { - t.Fatalf("Error during upload of files to swarm / mount of swarm dir: %v", err) - } - - dat.testUploadDir = filepath.Join(dat.testDir, "max-upload5") - dat.testMountDir = filepath.Join(dat.testDir, "max-mount5") - dat.files["5.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)} - - dat, err = ta.uploadAndMount(dat, t) - if err != nil { - t.Fatalf("Error during upload of files to swarm / mount of swarm dir: %v", err) - } - - //now try an additional mount, should fail due to max mounts reached - testUploadDir6 := filepath.Join(dat.testDir, "max-upload6") - err = os.MkdirAll(testUploadDir6, 0777) - if err != nil { - t.Fatalf("Couldn't create upload dir 6: %v", err) - } - dat.files["6.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)} - testMountDir6 := filepath.Join(dat.testDir, "max-mount6") - err = os.MkdirAll(testMountDir6, 0777) - if err != nil { - t.Fatalf("Couldn't create mount dir 5: %v", err) - } - bzzHash6 := createTestFilesAndUploadToSwarm(t, ta.api, dat.files, testUploadDir6, toEncrypt) - log.Debug("Created test files and uploaded to swarm with uploadDir6") - _, err = dat.swarmfs.Mount(bzzHash6, testMountDir6) - if err == nil { - t.Fatalf("Expected this mount to fail due to exceeding max number of allowed mounts, but succeeded. %v", bzzHash6) - } - log.Debug("Maximum mount reached, additional mount failed. Correct.") -} - -func (ta *testAPI) remountEncrypted(t *testing.T) { - log.Debug("Starting remountEncrypted test") - ta.remount(t, true) - log.Debug("Test remountEncrypted terminated") -} -func (ta *testAPI) remountNonEncrypted(t *testing.T) { - log.Debug("Starting remountNonEncrypted test") - ta.remount(t, false) - log.Debug("Test remountNonEncrypted terminated") -} - -//test remounting same hash second time and different hash in already mounted point -func (ta *testAPI) remount(t *testing.T, toEncrypt bool) { - dat, err := ta.initSubtest("remount") - if err != nil { - t.Fatalf("Couldn't initialize subtest dirs: %v", err) - } - defer os.RemoveAll(dat.testDir) - - dat.toEncrypt = toEncrypt - dat.testUploadDir = filepath.Join(dat.testDir, "remount-upload1") - dat.testMountDir = filepath.Join(dat.testDir, "remount-mount1") - dat.files = make(map[string]fileInfo) - - dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)} - - dat, err = ta.uploadAndMount(dat, t) - if err != nil { - t.Fatalf("Error during upload of files to swarm / mount of swarm dir: %v", err) - } - defer dat.swarmfs.Stop() - - // try mounting the same hash second time - testMountDir2, err2 := addDir(dat.testDir, "remount-mount2") - if err2 != nil { - t.Fatalf("Error creating second mount dir: %v", err2) - } - _, err2 = dat.swarmfs.Mount(dat.bzzHash, testMountDir2) - if err2 != nil { - t.Fatalf("Error mounting hash second time on different dir %v", dat.bzzHash) - } - - // mount a different hash in already mounted point - dat.files["2.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)} - testUploadDir2, err3 := addDir(dat.testDir, "remount-upload2") - if err3 != nil { - t.Fatalf("Error creating second upload dir: %v", err3) - } - bzzHash2 := createTestFilesAndUploadToSwarm(t, ta.api, dat.files, testUploadDir2, toEncrypt) - _, err = swarmfs.Mount(bzzHash2, dat.testMountDir) - if err == nil { - t.Fatalf("Error mounting hash %v", bzzHash2) - } - log.Debug("Mount on existing mount point failed. Correct.") - - // mount nonexistent hash - failDir, err3 := addDir(dat.testDir, "remount-fail") - if err3 != nil { - t.Fatalf("Error creating remount dir: %v", bzzHash2) - } - failHash := "0xfea11223344" - _, err = swarmfs.Mount(failHash, failDir) - if err == nil { - t.Fatalf("Expected this mount to fail due to non existing hash. But succeeded %v", failHash) - } - log.Debug("Nonexistent hash hasn't been mounted. Correct.") -} - -func (ta *testAPI) unmountEncrypted(t *testing.T) { - log.Debug("Starting unmountEncrypted test") - ta.unmount(t, true) - log.Debug("Test unmountEncrypted terminated") -} - -func (ta *testAPI) unmountNonEncrypted(t *testing.T) { - log.Debug("Starting unmountNonEncrypted test") - ta.unmount(t, false) - log.Debug("Test unmountNonEncrypted terminated") -} - -//mount then unmount and check that it has been unmounted -func (ta *testAPI) unmount(t *testing.T, toEncrypt bool) { - dat, err := ta.initSubtest("unmount") - if err != nil { - t.Fatalf("Couldn't initialize subtest dirs: %v", err) - } - defer os.RemoveAll(dat.testDir) - - dat.toEncrypt = toEncrypt - dat.testUploadDir = filepath.Join(dat.testDir, "ex-upload1") - dat.testMountDir = filepath.Join(dat.testDir, "ex-mount1") - dat.files = make(map[string]fileInfo) - dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)} - - dat, err = ta.uploadAndMount(dat, t) - if err != nil { - t.Fatalf("Error during upload of files to swarm / mount of swarm dir: %v", err) - } - defer dat.swarmfs.Stop() - - _, err = dat.swarmfs.Unmount(dat.testMountDir) - if err != nil { - t.Fatalf("could not unmount %v", dat.bzzHash) - } - log.Debug("Unmounted Dir") - - mi := swarmfs.Listmounts() - log.Debug("Going to list mounts") - for _, minfo := range mi { - log.Debug("Mount point in list: ", "point", minfo.MountPoint) - if minfo.MountPoint == dat.testMountDir { - t.Fatalf("mount state not cleaned up in unmount case %v", dat.testMountDir) - } - } - log.Debug("subtest terminated") -} - -func (ta *testAPI) unmountWhenResourceBusyEncrypted(t *testing.T) { - log.Debug("Starting unmountWhenResourceBusyEncrypted test") - ta.unmountWhenResourceBusy(t, true) - log.Debug("Test unmountWhenResourceBusyEncrypted terminated") -} -func (ta *testAPI) unmountWhenResourceBusyNonEncrypted(t *testing.T) { - log.Debug("Starting unmountWhenResourceBusyNonEncrypted test") - ta.unmountWhenResourceBusy(t, false) - log.Debug("Test unmountWhenResourceBusyNonEncrypted terminated") -} - -//unmount while a resource is busy; should fail -func (ta *testAPI) unmountWhenResourceBusy(t *testing.T, toEncrypt bool) { - dat, err := ta.initSubtest("unmountWhenResourceBusy") - if err != nil { - t.Fatalf("Couldn't initialize subtest dirs: %v", err) - } - defer os.RemoveAll(dat.testDir) - - dat.toEncrypt = toEncrypt - dat.testUploadDir = filepath.Join(dat.testDir, "ex-upload1") - dat.testMountDir = filepath.Join(dat.testDir, "ex-mount1") - dat.files = make(map[string]fileInfo) - dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)} - - dat, err = ta.uploadAndMount(dat, t) - if err != nil { - t.Fatalf("Error during upload of files to swarm / mount of swarm dir: %v", err) - } - defer dat.swarmfs.Stop() - - //create a file in the mounted directory, then try to unmount - should fail - actualPath := filepath.Join(dat.testMountDir, "2.txt") - //d, err := os.OpenFile(actualPath, os.O_RDWR, os.FileMode(0700)) - d, err := os.Create(actualPath) - if err != nil { - t.Fatalf("Couldn't create new file: %v", err) - } - //we need to manually close the file before mount for this test - //but let's defer too in case of errors - defer d.Close() - _, err = d.Write(testutil.RandomBytes(1, 10)) - if err != nil { - t.Fatalf("Couldn't write to file: %v", err) - } - log.Debug("Bytes written") - - _, err = dat.swarmfs.Unmount(dat.testMountDir) - if err == nil { - t.Fatalf("Expected mount to fail due to resource busy, but it succeeded...") - } - //free resources - err = d.Close() - if err != nil { - t.Fatalf("Couldn't close file! %v", dat.bzzHash) - } - log.Debug("File closed") - - //now unmount after explicitly closed file - _, err = dat.swarmfs.Unmount(dat.testMountDir) - if err != nil { - t.Fatalf("Expected mount to succeed after freeing resource, but it failed: %v", err) - } - //check if the dir is still mounted - mi := dat.swarmfs.Listmounts() - log.Debug("Going to list mounts") - for _, minfo := range mi { - log.Debug("Mount point in list: ", "point", minfo.MountPoint) - if minfo.MountPoint == dat.testMountDir { - t.Fatalf("mount state not cleaned up in unmount case %v", dat.testMountDir) - } - } - log.Debug("subtest terminated") -} - -func (ta *testAPI) seekInMultiChunkFileEncrypted(t *testing.T) { - log.Debug("Starting seekInMultiChunkFileEncrypted test") - ta.seekInMultiChunkFile(t, true) - log.Debug("Test seekInMultiChunkFileEncrypted terminated") -} - -func (ta *testAPI) seekInMultiChunkFileNonEncrypted(t *testing.T) { - log.Debug("Starting seekInMultiChunkFileNonEncrypted test") - ta.seekInMultiChunkFile(t, false) - log.Debug("Test seekInMultiChunkFileNonEncrypted terminated") -} - -//open a file in a mounted dir and go to a certain position -func (ta *testAPI) seekInMultiChunkFile(t *testing.T, toEncrypt bool) { - dat, err := ta.initSubtest("seekInMultiChunkFile") - if err != nil { - t.Fatalf("Couldn't initialize subtest dirs: %v", err) - } - defer os.RemoveAll(dat.testDir) - - dat.toEncrypt = toEncrypt - dat.testUploadDir = filepath.Join(dat.testDir, "seek-upload1") - dat.testMountDir = filepath.Join(dat.testDir, "seek-mount") - dat.files = make(map[string]fileInfo) - dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10240)} - - dat, err = ta.uploadAndMount(dat, t) - if err != nil { - t.Fatalf("Error during upload of files to swarm / mount of swarm dir: %v", err) - } - defer dat.swarmfs.Stop() - - // Open the file in the mounted dir and seek the second chunk - actualPath := filepath.Join(dat.testMountDir, "1.txt") - d, err := os.OpenFile(actualPath, os.O_RDONLY, os.FileMode(0700)) - if err != nil { - t.Fatalf("Couldn't open file: %v", err) - } - log.Debug("Opened file") - defer func() { - err := d.Close() - if err != nil { - t.Fatalf("Error closing file! %v", err) - } - }() - - _, err = d.Seek(5000, 0) - if err != nil { - t.Fatalf("Error seeking in file: %v", err) - } - - contents := make([]byte, 1024) - _, err = d.Read(contents) - if err != nil { - t.Fatalf("Error reading file: %v", err) - } - log.Debug("Read contents") - finfo := dat.files["1.txt"] - - if !bytes.Equal(finfo.contents[:6024][5000:], contents) { - t.Fatalf("File seek contents mismatch") - } - log.Debug("subtest terminated") -} - -func (ta *testAPI) createNewFileEncrypted(t *testing.T) { - log.Debug("Starting createNewFileEncrypted test") - ta.createNewFile(t, true) - log.Debug("Test createNewFileEncrypted terminated") -} - -func (ta *testAPI) createNewFileNonEncrypted(t *testing.T) { - log.Debug("Starting createNewFileNonEncrypted test") - ta.createNewFile(t, false) - log.Debug("Test createNewFileNonEncrypted terminated") -} - -//create a new file in a mounted swarm directory, -//unmount the fuse dir and then remount to see if new file is still there -func (ta *testAPI) createNewFile(t *testing.T, toEncrypt bool) { - dat, err := ta.initSubtest("createNewFile") - if err != nil { - t.Fatalf("Couldn't initialize subtest dirs: %v", err) - } - defer os.RemoveAll(dat.testDir) - - dat.toEncrypt = toEncrypt - dat.testUploadDir = filepath.Join(dat.testDir, "create-upload1") - dat.testMountDir = filepath.Join(dat.testDir, "create-mount") - dat.files = make(map[string]fileInfo) - dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)} - dat.files["five.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(2, 10)} - dat.files["six.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)} - - dat, err = ta.uploadAndMount(dat, t) - if err != nil { - t.Fatalf("Error during upload of files to swarm / mount of swarm dir: %v", err) - } - defer dat.swarmfs.Stop() - - // Create a new file in the root dir and check - actualPath := filepath.Join(dat.testMountDir, "2.txt") - d, err1 := os.OpenFile(actualPath, os.O_RDWR|os.O_CREATE, os.FileMode(0665)) - if err1 != nil { - t.Fatalf("Could not open file %s : %v", actualPath, err1) - } - defer d.Close() - log.Debug("Opened file") - contents := testutil.RandomBytes(1, 11) - log.Debug("content read") - _, err = d.Write(contents) - if err != nil { - t.Fatalf("Couldn't write contents: %v", err) - } - log.Debug("content written") - err = d.Close() - if err != nil { - t.Fatalf("Couldn't close file: %v", err) - } - log.Debug("file closed") - - mi, err2 := dat.swarmfs.Unmount(dat.testMountDir) - if err2 != nil { - t.Fatalf("Could not unmount %v", err2) - } - log.Debug("Directory unmounted") - - testMountDir2, err3 := addDir(dat.testDir, "create-mount2") - if err3 != nil { - t.Fatalf("Error creating mount dir2: %v", err3) - } - // mount again and see if things are okay - dat.files["2.txt"] = fileInfo{0700, 333, 444, contents} - _ = mountDir(t, ta.api, dat.files, mi.LatestManifest, testMountDir2) - log.Debug("Directory mounted again") - - checkFile(t, testMountDir2, "2.txt", contents) - _, err2 = dat.swarmfs.Unmount(testMountDir2) - if err2 != nil { - t.Fatalf("Could not unmount %v", err2) - } - log.Debug("subtest terminated") -} - -func (ta *testAPI) createNewFileInsideDirectoryEncrypted(t *testing.T) { - log.Debug("Starting createNewFileInsideDirectoryEncrypted test") - ta.createNewFileInsideDirectory(t, true) - log.Debug("Test createNewFileInsideDirectoryEncrypted terminated") -} - -func (ta *testAPI) createNewFileInsideDirectoryNonEncrypted(t *testing.T) { - log.Debug("Starting createNewFileInsideDirectoryNonEncrypted test") - ta.createNewFileInsideDirectory(t, false) - log.Debug("Test createNewFileInsideDirectoryNonEncrypted terminated") -} - -//create a new file inside a directory inside the mount -func (ta *testAPI) createNewFileInsideDirectory(t *testing.T, toEncrypt bool) { - dat, err := ta.initSubtest("createNewFileInsideDirectory") - if err != nil { - t.Fatalf("Couldn't initialize subtest dirs: %v", err) - } - defer os.RemoveAll(dat.testDir) - - dat.toEncrypt = toEncrypt - dat.testUploadDir = filepath.Join(dat.testDir, "createinsidedir-upload") - dat.testMountDir = filepath.Join(dat.testDir, "createinsidedir-mount") - dat.files = make(map[string]fileInfo) - dat.files["one/1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)} - - dat, err = ta.uploadAndMount(dat, t) - if err != nil { - t.Fatalf("Error during upload of files to swarm / mount of swarm dir: %v", err) - } - defer dat.swarmfs.Stop() - - // Create a new file inside a existing dir and check - dirToCreate := filepath.Join(dat.testMountDir, "one") - actualPath := filepath.Join(dirToCreate, "2.txt") - d, err1 := os.OpenFile(actualPath, os.O_RDWR|os.O_CREATE, os.FileMode(0665)) - if err1 != nil { - t.Fatalf("Could not create file %s : %v", actualPath, err1) - } - defer d.Close() - log.Debug("File opened") - contents := testutil.RandomBytes(1, 11) - log.Debug("Content read") - _, err = d.Write(contents) - if err != nil { - t.Fatalf("Error writing random bytes into file %v", err) - } - log.Debug("Content written") - err = d.Close() - if err != nil { - t.Fatalf("Error closing file %v", err) - } - log.Debug("File closed") - - mi, err2 := dat.swarmfs.Unmount(dat.testMountDir) - if err2 != nil { - t.Fatalf("Could not unmount %v", err2) - } - log.Debug("Directory unmounted") - - testMountDir2, err3 := addDir(dat.testDir, "createinsidedir-mount2") - if err3 != nil { - t.Fatalf("Error creating mount dir2: %v", err3) - } - // mount again and see if things are okay - dat.files["one/2.txt"] = fileInfo{0700, 333, 444, contents} - _ = mountDir(t, ta.api, dat.files, mi.LatestManifest, testMountDir2) - log.Debug("Directory mounted again") - - checkFile(t, testMountDir2, "one/2.txt", contents) - _, err = dat.swarmfs.Unmount(testMountDir2) - if err != nil { - t.Fatalf("could not unmount %v", dat.bzzHash) - } - log.Debug("subtest terminated") -} - -func (ta *testAPI) createNewFileInsideNewDirectoryEncrypted(t *testing.T) { - log.Debug("Starting createNewFileInsideNewDirectoryEncrypted test") - ta.createNewFileInsideNewDirectory(t, true) - log.Debug("Test createNewFileInsideNewDirectoryEncrypted terminated") -} - -func (ta *testAPI) createNewFileInsideNewDirectoryNonEncrypted(t *testing.T) { - log.Debug("Starting createNewFileInsideNewDirectoryNonEncrypted test") - ta.createNewFileInsideNewDirectory(t, false) - log.Debug("Test createNewFileInsideNewDirectoryNonEncrypted terminated") -} - -//create a new directory in mount and a new file -func (ta *testAPI) createNewFileInsideNewDirectory(t *testing.T, toEncrypt bool) { - dat, err := ta.initSubtest("createNewFileInsideNewDirectory") - if err != nil { - t.Fatalf("Couldn't initialize subtest dirs: %v", err) - } - defer os.RemoveAll(dat.testDir) - - dat.toEncrypt = toEncrypt - dat.testUploadDir = filepath.Join(dat.testDir, "createinsidenewdir-upload") - dat.testMountDir = filepath.Join(dat.testDir, "createinsidenewdir-mount") - dat.files = make(map[string]fileInfo) - dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)} - - dat, err = ta.uploadAndMount(dat, t) - if err != nil { - t.Fatalf("Error during upload of files to swarm / mount of swarm dir: %v", err) - } - defer dat.swarmfs.Stop() - - // Create a new file inside a existing dir and check - dirToCreate, err2 := addDir(dat.testMountDir, "one") - if err2 != nil { - t.Fatalf("Error creating mount dir2: %v", err2) - } - actualPath := filepath.Join(dirToCreate, "2.txt") - d, err1 := os.OpenFile(actualPath, os.O_RDWR|os.O_CREATE, os.FileMode(0665)) - if err1 != nil { - t.Fatalf("Could not create file %s : %v", actualPath, err1) - } - defer d.Close() - log.Debug("File opened") - contents := testutil.RandomBytes(1, 11) - log.Debug("content read") - _, err = d.Write(contents) - if err != nil { - t.Fatalf("Error writing to file: %v", err) - } - log.Debug("content written") - err = d.Close() - if err != nil { - t.Fatalf("Error closing file: %v", err) - } - log.Debug("File closed") - - mi, err2 := dat.swarmfs.Unmount(dat.testMountDir) - if err2 != nil { - t.Fatalf("Could not unmount %v", err2) - } - log.Debug("Directory unmounted") - - // mount again and see if things are okay - dat.files["one/2.txt"] = fileInfo{0700, 333, 444, contents} - _ = mountDir(t, ta.api, dat.files, mi.LatestManifest, dat.testMountDir) - log.Debug("Directory mounted again") - - checkFile(t, dat.testMountDir, "one/2.txt", contents) - _, err2 = dat.swarmfs.Unmount(dat.testMountDir) - if err2 != nil { - t.Fatalf("Could not unmount %v", err2) - } - log.Debug("subtest terminated") -} - -func (ta *testAPI) removeExistingFileEncrypted(t *testing.T) { - log.Debug("Starting removeExistingFileEncrypted test") - ta.removeExistingFile(t, true) - log.Debug("Test removeExistingFileEncrypted terminated") -} - -func (ta *testAPI) removeExistingFileNonEncrypted(t *testing.T) { - log.Debug("Starting removeExistingFileNonEncrypted test") - ta.removeExistingFile(t, false) - log.Debug("Test removeExistingFileNonEncrypted terminated") -} - -//remove existing file in mount -func (ta *testAPI) removeExistingFile(t *testing.T, toEncrypt bool) { - dat, err := ta.initSubtest("removeExistingFile") - if err != nil { - t.Fatalf("Couldn't initialize subtest dirs: %v", err) - } - defer os.RemoveAll(dat.testDir) - - dat.toEncrypt = toEncrypt - dat.testUploadDir = filepath.Join(dat.testDir, "remove-upload") - dat.testMountDir = filepath.Join(dat.testDir, "remove-mount") - dat.files = make(map[string]fileInfo) - dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)} - dat.files["five.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(2, 10)} - dat.files["six.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)} - - dat, err = ta.uploadAndMount(dat, t) - if err != nil { - t.Fatalf("Error during upload of files to swarm / mount of swarm dir: %v", err) - } - defer dat.swarmfs.Stop() - - // Remove a file in the root dir and check - actualPath := filepath.Join(dat.testMountDir, "five.txt") - err = os.Remove(actualPath) - if err != nil { - t.Fatalf("Error removing file! %v", err) - } - mi, err2 := dat.swarmfs.Unmount(dat.testMountDir) - if err2 != nil { - t.Fatalf("Could not unmount %v", err2) - } - log.Debug("Directory unmounted") - - // mount again and see if things are okay - delete(dat.files, "five.txt") - _ = mountDir(t, ta.api, dat.files, mi.LatestManifest, dat.testMountDir) - _, err = os.Stat(actualPath) - if err == nil { - t.Fatal("Expected file to not be present in re-mount after removal, but it is there") - } - _, err2 = dat.swarmfs.Unmount(dat.testMountDir) - if err2 != nil { - t.Fatalf("Could not unmount %v", err2) - } - log.Debug("subtest terminated") -} - -func (ta *testAPI) removeExistingFileInsideDirEncrypted(t *testing.T) { - log.Debug("Starting removeExistingFileInsideDirEncrypted test") - ta.removeExistingFileInsideDir(t, true) - log.Debug("Test removeExistingFileInsideDirEncrypted terminated") -} - -func (ta *testAPI) removeExistingFileInsideDirNonEncrypted(t *testing.T) { - log.Debug("Starting removeExistingFileInsideDirNonEncrypted test") - ta.removeExistingFileInsideDir(t, false) - log.Debug("Test removeExistingFileInsideDirNonEncrypted terminated") -} - -//remove a file inside a directory inside a mount -func (ta *testAPI) removeExistingFileInsideDir(t *testing.T, toEncrypt bool) { - dat, err := ta.initSubtest("removeExistingFileInsideDir") - if err != nil { - t.Fatalf("Couldn't initialize subtest dirs: %v", err) - } - defer os.RemoveAll(dat.testDir) - - dat.toEncrypt = toEncrypt - dat.testUploadDir = filepath.Join(dat.testDir, "remove-upload") - dat.testMountDir = filepath.Join(dat.testDir, "remove-mount") - dat.files = make(map[string]fileInfo) - dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)} - dat.files["one/five.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(2, 10)} - dat.files["one/six.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)} - - dat, err = ta.uploadAndMount(dat, t) - if err != nil { - t.Fatalf("Error during upload of files to swarm / mount of swarm dir: %v", err) - } - defer dat.swarmfs.Stop() - - // Remove a file in the root dir and check - actualPath := filepath.Join(dat.testMountDir, "one") - actualPath = filepath.Join(actualPath, "five.txt") - err = os.Remove(actualPath) - if err != nil { - t.Fatalf("Error removing file! %v", err) - } - mi, err2 := dat.swarmfs.Unmount(dat.testMountDir) - if err2 != nil { - t.Fatalf("Could not unmount %v", err2) - } - log.Debug("Directory unmounted") - - // mount again and see if things are okay - delete(dat.files, "one/five.txt") - _ = mountDir(t, ta.api, dat.files, mi.LatestManifest, dat.testMountDir) - _, err = os.Stat(actualPath) - if err == nil { - t.Fatal("Expected file to not be present in re-mount after removal, but it is there") - } - - okPath := filepath.Join(dat.testMountDir, "one") - okPath = filepath.Join(okPath, "six.txt") - _, err = os.Stat(okPath) - if err != nil { - t.Fatal("Expected file to be present in re-mount after removal, but it is not there") - } - _, err2 = dat.swarmfs.Unmount(dat.testMountDir) - if err2 != nil { - t.Fatalf("Could not unmount %v", err2) - } - log.Debug("subtest terminated") -} - -func (ta *testAPI) removeNewlyAddedFileEncrypted(t *testing.T) { - log.Debug("Starting removeNewlyAddedFileEncrypted test") - ta.removeNewlyAddedFile(t, true) - log.Debug("Test removeNewlyAddedFileEncrypted terminated") -} - -func (ta *testAPI) removeNewlyAddedFileNonEncrypted(t *testing.T) { - log.Debug("Starting removeNewlyAddedFileNonEncrypted test") - ta.removeNewlyAddedFile(t, false) - log.Debug("Test removeNewlyAddedFileNonEncrypted terminated") -} - -//add a file in mount and then remove it; on remount file should not be there -func (ta *testAPI) removeNewlyAddedFile(t *testing.T, toEncrypt bool) { - dat, err := ta.initSubtest("removeNewlyAddedFile") - if err != nil { - t.Fatalf("Couldn't initialize subtest dirs: %v", err) - } - defer os.RemoveAll(dat.testDir) - - dat.toEncrypt = toEncrypt - dat.testUploadDir = filepath.Join(dat.testDir, "removenew-upload") - dat.testMountDir = filepath.Join(dat.testDir, "removenew-mount") - dat.files = make(map[string]fileInfo) - dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)} - dat.files["five.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(2, 10)} - dat.files["six.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)} - - dat, err = ta.uploadAndMount(dat, t) - if err != nil { - t.Fatalf("Error during upload of files to swarm / mount of swarm dir: %v", err) - } - defer dat.swarmfs.Stop() - - // Add a a new file and remove it - dirToCreate := filepath.Join(dat.testMountDir, "one") - err = os.MkdirAll(dirToCreate, os.FileMode(0665)) - if err != nil { - t.Fatalf("Error creating dir in mounted dir: %v", err) - } - actualPath := filepath.Join(dirToCreate, "2.txt") - d, err1 := os.OpenFile(actualPath, os.O_RDWR|os.O_CREATE, os.FileMode(0665)) - if err1 != nil { - t.Fatalf("Could not create file %s : %v", actualPath, err1) - } - defer d.Close() - log.Debug("file opened") - contents := testutil.RandomBytes(1, 11) - log.Debug("content read") - _, err = d.Write(contents) - if err != nil { - t.Fatalf("Error writing random bytes to file: %v", err) - } - log.Debug("content written") - err = d.Close() - if err != nil { - t.Fatalf("Error closing file: %v", err) - } - log.Debug("file closed") - - checkFile(t, dat.testMountDir, "one/2.txt", contents) - log.Debug("file checked") - - err = os.Remove(actualPath) - if err != nil { - t.Fatalf("Error removing file: %v", err) - } - log.Debug("file removed") - - mi, err2 := dat.swarmfs.Unmount(dat.testMountDir) - if err2 != nil { - t.Fatalf("Could not unmount %v", err2) - } - log.Debug("Directory unmounted") - - testMountDir2, err3 := addDir(dat.testDir, "removenew-mount2") - if err3 != nil { - t.Fatalf("Error creating mount dir2: %v", err3) - } - // mount again and see if things are okay - _ = mountDir(t, ta.api, dat.files, mi.LatestManifest, testMountDir2) - log.Debug("Directory mounted again") - - if dat.bzzHash != mi.LatestManifest { - t.Fatalf("same contents different hash orig(%v): new(%v)", dat.bzzHash, mi.LatestManifest) - } - _, err2 = dat.swarmfs.Unmount(testMountDir2) - if err2 != nil { - t.Fatalf("Could not unmount %v", err2) - } - log.Debug("subtest terminated") -} - -func (ta *testAPI) addNewFileAndModifyContentsEncrypted(t *testing.T) { - log.Debug("Starting addNewFileAndModifyContentsEncrypted test") - ta.addNewFileAndModifyContents(t, true) - log.Debug("Test addNewFileAndModifyContentsEncrypted terminated") -} - -func (ta *testAPI) addNewFileAndModifyContentsNonEncrypted(t *testing.T) { - log.Debug("Starting addNewFileAndModifyContentsNonEncrypted test") - ta.addNewFileAndModifyContents(t, false) - log.Debug("Test addNewFileAndModifyContentsNonEncrypted terminated") -} - -//add a new file and modify content; remount and check the modified file is intact -func (ta *testAPI) addNewFileAndModifyContents(t *testing.T, toEncrypt bool) { - dat, err := ta.initSubtest("addNewFileAndModifyContents") - if err != nil { - t.Fatalf("Couldn't initialize subtest dirs: %v", err) - } - defer os.RemoveAll(dat.testDir) - - dat.toEncrypt = toEncrypt - dat.testUploadDir = filepath.Join(dat.testDir, "modifyfile-upload") - dat.testMountDir = filepath.Join(dat.testDir, "modifyfile-mount") - dat.files = make(map[string]fileInfo) - dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)} - dat.files["five.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(2, 10)} - dat.files["six.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)} - - dat, err = ta.uploadAndMount(dat, t) - if err != nil { - t.Fatalf("Error during upload of files to swarm / mount of swarm dir: %v", err) - } - defer dat.swarmfs.Stop() - - // Create a new file in the root dir - actualPath := filepath.Join(dat.testMountDir, "2.txt") - d, err1 := os.OpenFile(actualPath, os.O_RDWR|os.O_CREATE, os.FileMode(0665)) - if err1 != nil { - t.Fatalf("Could not create file %s : %v", actualPath, err1) - } - defer d.Close() - //write some random data into the file - log.Debug("file opened") - line1 := []byte("Line 1") - _, err = rand.Read(line1) - if err != nil { - t.Fatalf("Error writing random bytes to byte array: %v", err) - } - log.Debug("line read") - _, err = d.Write(line1) - if err != nil { - t.Fatalf("Error writing random bytes to file: %v", err) - } - log.Debug("line written") - err = d.Close() - if err != nil { - t.Fatalf("Error closing file: %v", err) - } - log.Debug("file closed") - - //unmount the hash on the mounted dir - mi1, err2 := dat.swarmfs.Unmount(dat.testMountDir) - if err2 != nil { - t.Fatalf("Could not unmount %v", err2) - } - log.Debug("Directory unmounted") - - //mount on a different dir to see if modified file is correct - testMountDir2, err3 := addDir(dat.testDir, "modifyfile-mount2") - if err3 != nil { - t.Fatalf("Error creating mount dir2: %v", err3) - } - dat.files["2.txt"] = fileInfo{0700, 333, 444, line1} - _ = mountDir(t, ta.api, dat.files, mi1.LatestManifest, testMountDir2) - log.Debug("Directory mounted again") - - checkFile(t, testMountDir2, "2.txt", line1) - log.Debug("file checked") - - //unmount second dir - mi2, err4 := dat.swarmfs.Unmount(testMountDir2) - if err4 != nil { - t.Fatalf("Could not unmount %v", err4) - } - log.Debug("Directory unmounted again") - - //mount again on original dir and modify the file - //let's clean up the mounted dir first: remove... - err = os.RemoveAll(dat.testMountDir) - if err != nil { - t.Fatalf("Error cleaning up mount dir: %v", err) - } - //...and re-create - err = os.MkdirAll(dat.testMountDir, 0777) - if err != nil { - t.Fatalf("Error re-creating mount dir: %v", err) - } - //now remount - _ = mountDir(t, ta.api, dat.files, mi2.LatestManifest, dat.testMountDir) - log.Debug("Directory mounted yet again") - - //open the file.... - fd, err5 := os.OpenFile(actualPath, os.O_RDWR|os.O_APPEND, os.FileMode(0665)) - if err5 != nil { - t.Fatalf("Could not create file %s : %v", actualPath, err5) - } - defer fd.Close() - log.Debug("file opened") - //...and modify something - line2 := []byte("Line 2") - _, err = rand.Read(line2) - if err != nil { - t.Fatalf("Error modifying random bytes to byte array: %v", err) - } - log.Debug("line read") - _, err = fd.Seek(int64(len(line1)), 0) - if err != nil { - t.Fatalf("Error seeking position for modification: %v", err) - } - _, err = fd.Write(line2) - if err != nil { - t.Fatalf("Error modifying file: %v", err) - } - log.Debug("line written") - err = fd.Close() - if err != nil { - t.Fatalf("Error closing modified file; %v", err) - } - log.Debug("file closed") - - //unmount the modified directory - mi3, err6 := dat.swarmfs.Unmount(dat.testMountDir) - if err6 != nil { - t.Fatalf("Could not unmount %v", err6) - } - log.Debug("Directory unmounted yet again") - - //now remount on a different dir and check that the modified file is ok - testMountDir4, err7 := addDir(dat.testDir, "modifyfile-mount4") - if err7 != nil { - t.Fatalf("Could not unmount %v", err7) - } - b := [][]byte{line1, line2} - line1and2 := bytes.Join(b, []byte("")) - dat.files["2.txt"] = fileInfo{0700, 333, 444, line1and2} - _ = mountDir(t, ta.api, dat.files, mi3.LatestManifest, testMountDir4) - log.Debug("Directory mounted final time") - - checkFile(t, testMountDir4, "2.txt", line1and2) - _, err = dat.swarmfs.Unmount(testMountDir4) - if err != nil { - t.Fatalf("Could not unmount %v", err) - } - log.Debug("subtest terminated") -} - -func (ta *testAPI) removeEmptyDirEncrypted(t *testing.T) { - log.Debug("Starting removeEmptyDirEncrypted test") - ta.removeEmptyDir(t, true) - log.Debug("Test removeEmptyDirEncrypted terminated") -} - -func (ta *testAPI) removeEmptyDirNonEncrypted(t *testing.T) { - log.Debug("Starting removeEmptyDirNonEncrypted test") - ta.removeEmptyDir(t, false) - log.Debug("Test removeEmptyDirNonEncrypted terminated") -} - -//remove an empty dir inside mount -func (ta *testAPI) removeEmptyDir(t *testing.T, toEncrypt bool) { - dat, err := ta.initSubtest("removeEmptyDir") - if err != nil { - t.Fatalf("Couldn't initialize subtest dirs: %v", err) - } - defer os.RemoveAll(dat.testDir) - - dat.toEncrypt = toEncrypt - dat.testUploadDir = filepath.Join(dat.testDir, "rmdir-upload") - dat.testMountDir = filepath.Join(dat.testDir, "rmdir-mount") - dat.files = make(map[string]fileInfo) - dat.files["1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)} - dat.files["five.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(2, 10)} - dat.files["six.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)} - - dat, err = ta.uploadAndMount(dat, t) - if err != nil { - t.Fatalf("Error during upload of files to swarm / mount of swarm dir: %v", err) - } - defer dat.swarmfs.Stop() - - _, err2 := addDir(dat.testMountDir, "newdir") - if err2 != nil { - t.Fatalf("Could not unmount %v", err2) - } - mi, err := dat.swarmfs.Unmount(dat.testMountDir) - if err != nil { - t.Fatalf("Could not unmount %v", err) - } - log.Debug("Directory unmounted") - //by just adding an empty dir, the hash doesn't change; test this - if dat.bzzHash != mi.LatestManifest { - t.Fatalf("same contents different hash orig(%v): new(%v)", dat.bzzHash, mi.LatestManifest) - } - log.Debug("subtest terminated") -} - -func (ta *testAPI) removeDirWhichHasFilesEncrypted(t *testing.T) { - log.Debug("Starting removeDirWhichHasFilesEncrypted test") - ta.removeDirWhichHasFiles(t, true) - log.Debug("Test removeDirWhichHasFilesEncrypted terminated") -} -func (ta *testAPI) removeDirWhichHasFilesNonEncrypted(t *testing.T) { - log.Debug("Starting removeDirWhichHasFilesNonEncrypted test") - ta.removeDirWhichHasFiles(t, false) - log.Debug("Test removeDirWhichHasFilesNonEncrypted terminated") -} - -//remove a directory with a file; check on remount file isn't there -func (ta *testAPI) removeDirWhichHasFiles(t *testing.T, toEncrypt bool) { - dat, err := ta.initSubtest("removeDirWhichHasFiles") - if err != nil { - t.Fatalf("Couldn't initialize subtest dirs: %v", err) - } - defer os.RemoveAll(dat.testDir) - - dat.toEncrypt = toEncrypt - dat.testUploadDir = filepath.Join(dat.testDir, "rmdir-upload") - dat.testMountDir = filepath.Join(dat.testDir, "rmdir-mount") - dat.files = make(map[string]fileInfo) - dat.files["one/1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)} - dat.files["two/five.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(2, 10)} - dat.files["two/six.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)} - - dat, err = ta.uploadAndMount(dat, t) - if err != nil { - t.Fatalf("Error during upload of files to swarm / mount of swarm dir: %v", err) - } - defer dat.swarmfs.Stop() - - //delete a directory inside the mounted dir with all its files - dirPath := filepath.Join(dat.testMountDir, "two") - err = os.RemoveAll(dirPath) - if err != nil { - t.Fatalf("Error removing directory in mounted dir: %v", err) - } - - mi, err2 := dat.swarmfs.Unmount(dat.testMountDir) - if err2 != nil { - t.Fatalf("Could not unmount %v ", err2) - } - log.Debug("Directory unmounted") - - //we deleted files in the OS, so let's delete them also in the files map - delete(dat.files, "two/five.txt") - delete(dat.files, "two/six.txt") - - // mount again and see if deleted files have been deleted indeed - testMountDir2, err3 := addDir(dat.testDir, "remount-mount2") - if err3 != nil { - t.Fatalf("Could not unmount %v", err3) - } - _ = mountDir(t, ta.api, dat.files, mi.LatestManifest, testMountDir2) - log.Debug("Directory mounted") - actualPath := filepath.Join(dirPath, "five.txt") - _, err = os.Stat(actualPath) - if err == nil { - t.Fatal("Expected file to not be present in re-mount after removal, but it is there") - } - _, err = os.Stat(dirPath) - if err == nil { - t.Fatal("Expected file to not be present in re-mount after removal, but it is there") - } - _, err = dat.swarmfs.Unmount(testMountDir2) - if err != nil { - t.Fatalf("Could not unmount %v", err) - } - log.Debug("subtest terminated") -} - -func (ta *testAPI) removeDirWhichHasSubDirsEncrypted(t *testing.T) { - log.Debug("Starting removeDirWhichHasSubDirsEncrypted test") - ta.removeDirWhichHasSubDirs(t, true) - log.Debug("Test removeDirWhichHasSubDirsEncrypted terminated") -} - -func (ta *testAPI) removeDirWhichHasSubDirsNonEncrypted(t *testing.T) { - log.Debug("Starting removeDirWhichHasSubDirsNonEncrypted test") - ta.removeDirWhichHasSubDirs(t, false) - log.Debug("Test removeDirWhichHasSubDirsNonEncrypted terminated") -} - -//remove a directory with subdirectories inside mount; on remount check they are not there -func (ta *testAPI) removeDirWhichHasSubDirs(t *testing.T, toEncrypt bool) { - dat, err := ta.initSubtest("removeDirWhichHasSubDirs") - if err != nil { - t.Fatalf("Couldn't initialize subtest dirs: %v", err) - } - defer os.RemoveAll(dat.testDir) - - dat.toEncrypt = toEncrypt - dat.testUploadDir = filepath.Join(dat.testDir, "rmsubdir-upload") - dat.testMountDir = filepath.Join(dat.testDir, "rmsubdir-mount") - dat.files = make(map[string]fileInfo) - dat.files["one/1.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(1, 10)} - dat.files["two/three/2.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(2, 10)} - dat.files["two/three/3.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(3, 10)} - dat.files["two/four/5.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(4, 10)} - dat.files["two/four/6.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(5, 10)} - dat.files["two/four/six/7.txt"] = fileInfo{0700, 333, 444, testutil.RandomBytes(6, 10)} - - dat, err = ta.uploadAndMount(dat, t) - if err != nil { - t.Fatalf("Error during upload of files to swarm / mount of swarm dir: %v", err) - } - defer dat.swarmfs.Stop() - - dirPath := filepath.Join(dat.testMountDir, "two") - err = os.RemoveAll(dirPath) - if err != nil { - t.Fatalf("Error removing directory in mounted dir: %v", err) - } - - //delete a directory inside the mounted dir with all its files - mi, err2 := dat.swarmfs.Unmount(dat.testMountDir) - if err2 != nil { - t.Fatalf("Could not unmount %v ", err2) - } - log.Debug("Directory unmounted") - - //we deleted files in the OS, so let's delete them also in the files map - delete(dat.files, "two/three/2.txt") - delete(dat.files, "two/three/3.txt") - delete(dat.files, "two/four/5.txt") - delete(dat.files, "two/four/6.txt") - delete(dat.files, "two/four/six/7.txt") - - // mount again and see if things are okay - testMountDir2, err3 := addDir(dat.testDir, "remount-mount2") - if err3 != nil { - t.Fatalf("Could not unmount %v", err3) - } - _ = mountDir(t, ta.api, dat.files, mi.LatestManifest, testMountDir2) - log.Debug("Directory mounted again") - actualPath := filepath.Join(dirPath, "three") - actualPath = filepath.Join(actualPath, "2.txt") - _, err = os.Stat(actualPath) - if err == nil { - t.Fatal("Expected file to not be present in re-mount after removal, but it is there") - } - actualPath = filepath.Join(dirPath, "four") - _, err = os.Stat(actualPath) - if err == nil { - t.Fatal("Expected file to not be present in re-mount after removal, but it is there") - } - _, err = os.Stat(dirPath) - if err == nil { - t.Fatal("Expected file to not be present in re-mount after removal, but it is there") - } - _, err = dat.swarmfs.Unmount(testMountDir2) - if err != nil { - t.Fatalf("Could not unmount %v", err) - } - log.Debug("subtest terminated") -} - -func (ta *testAPI) appendFileContentsToEndEncrypted(t *testing.T) { - log.Debug("Starting appendFileContentsToEndEncrypted test") - ta.appendFileContentsToEnd(t, true) - log.Debug("Test appendFileContentsToEndEncrypted terminated") -} - -func (ta *testAPI) appendFileContentsToEndNonEncrypted(t *testing.T) { - log.Debug("Starting appendFileContentsToEndNonEncrypted test") - ta.appendFileContentsToEnd(t, false) - log.Debug("Test appendFileContentsToEndNonEncrypted terminated") -} - -//append contents to the end of a file; remount and check it's intact -func (ta *testAPI) appendFileContentsToEnd(t *testing.T, toEncrypt bool) { - dat, err := ta.initSubtest("appendFileContentsToEnd") - if err != nil { - t.Fatalf("Couldn't initialize subtest dirs: %v", err) - } - defer os.RemoveAll(dat.testDir) - - dat.toEncrypt = toEncrypt - dat.testUploadDir = filepath.Join(dat.testDir, "appendlargefile-upload") - dat.testMountDir = filepath.Join(dat.testDir, "appendlargefile-mount") - dat.files = make(map[string]fileInfo) - - line1 := testutil.RandomBytes(1, 10) - - dat.files["1.txt"] = fileInfo{0700, 333, 444, line1} - - dat, err = ta.uploadAndMount(dat, t) - if err != nil { - t.Fatalf("Error during upload of files to swarm / mount of swarm dir: %v", err) - } - defer dat.swarmfs.Stop() - - actualPath := filepath.Join(dat.testMountDir, "1.txt") - fd, err4 := os.OpenFile(actualPath, os.O_RDWR|os.O_APPEND, os.FileMode(0665)) - if err4 != nil { - t.Fatalf("Could not create file %s : %v", actualPath, err4) - } - defer fd.Close() - log.Debug("file opened") - line2 := testutil.RandomBytes(1, 5) - log.Debug("line read") - _, err = fd.Seek(int64(len(line1)), 0) - if err != nil { - t.Fatalf("Error searching for position to append: %v", err) - } - _, err = fd.Write(line2) - if err != nil { - t.Fatalf("Error appending: %v", err) - } - log.Debug("line written") - err = fd.Close() - if err != nil { - t.Fatalf("Error closing file: %v", err) - } - log.Debug("file closed") - - mi1, err5 := dat.swarmfs.Unmount(dat.testMountDir) - if err5 != nil { - t.Fatalf("Could not unmount %v ", err5) - } - log.Debug("Directory unmounted") - - // mount again and see if appended file is correct - b := [][]byte{line1, line2} - line1and2 := bytes.Join(b, []byte("")) - dat.files["1.txt"] = fileInfo{0700, 333, 444, line1and2} - testMountDir2, err6 := addDir(dat.testDir, "remount-mount2") - if err6 != nil { - t.Fatalf("Could not unmount %v", err6) - } - _ = mountDir(t, ta.api, dat.files, mi1.LatestManifest, testMountDir2) - log.Debug("Directory mounted") - - checkFile(t, testMountDir2, "1.txt", line1and2) - - _, err = dat.swarmfs.Unmount(testMountDir2) - if err != nil { - t.Fatalf("Could not unmount %v", err) - } - log.Debug("subtest terminated") -} - -//run all the tests -func TestFUSE(t *testing.T) { - t.Skip("disable fuse tests until they are stable") - //create a data directory for swarm - datadir, err := ioutil.TempDir("", "fuse") - if err != nil { - t.Fatalf("unable to create temp dir: %v", err) - } - defer os.RemoveAll(datadir) - - fileStore, err := storage.NewLocalFileStore(datadir, make([]byte, 32)) - if err != nil { - t.Fatal(err) - } - ta := &testAPI{api: api.NewAPI(fileStore, nil, nil, nil)} - - //run a short suite of tests - //approx time: 28s - t.Run("mountListAndUnmountEncrypted", ta.mountListAndUnmountEncrypted) - t.Run("remountEncrypted", ta.remountEncrypted) - t.Run("unmountWhenResourceBusyNonEncrypted", ta.unmountWhenResourceBusyNonEncrypted) - t.Run("removeExistingFileEncrypted", ta.removeExistingFileEncrypted) - t.Run("addNewFileAndModifyContentsNonEncrypted", ta.addNewFileAndModifyContentsNonEncrypted) - t.Run("removeDirWhichHasFilesNonEncrypted", ta.removeDirWhichHasFilesNonEncrypted) - t.Run("appendFileContentsToEndEncrypted", ta.appendFileContentsToEndEncrypted) - - //provide longrunning flag to execute all tests - //approx time with longrunning: 140s - if *longrunning { - t.Run("mountListAndUnmountNonEncrypted", ta.mountListAndUnmountNonEncrypted) - t.Run("maxMountsEncrypted", ta.maxMountsEncrypted) - t.Run("maxMountsNonEncrypted", ta.maxMountsNonEncrypted) - t.Run("remountNonEncrypted", ta.remountNonEncrypted) - t.Run("unmountEncrypted", ta.unmountEncrypted) - t.Run("unmountNonEncrypted", ta.unmountNonEncrypted) - t.Run("unmountWhenResourceBusyEncrypted", ta.unmountWhenResourceBusyEncrypted) - t.Run("unmountWhenResourceBusyNonEncrypted", ta.unmountWhenResourceBusyNonEncrypted) - t.Run("seekInMultiChunkFileEncrypted", ta.seekInMultiChunkFileEncrypted) - t.Run("seekInMultiChunkFileNonEncrypted", ta.seekInMultiChunkFileNonEncrypted) - t.Run("createNewFileEncrypted", ta.createNewFileEncrypted) - t.Run("createNewFileNonEncrypted", ta.createNewFileNonEncrypted) - t.Run("createNewFileInsideDirectoryEncrypted", ta.createNewFileInsideDirectoryEncrypted) - t.Run("createNewFileInsideDirectoryNonEncrypted", ta.createNewFileInsideDirectoryNonEncrypted) - t.Run("createNewFileInsideNewDirectoryEncrypted", ta.createNewFileInsideNewDirectoryEncrypted) - t.Run("createNewFileInsideNewDirectoryNonEncrypted", ta.createNewFileInsideNewDirectoryNonEncrypted) - t.Run("removeExistingFileNonEncrypted", ta.removeExistingFileNonEncrypted) - t.Run("removeExistingFileInsideDirEncrypted", ta.removeExistingFileInsideDirEncrypted) - t.Run("removeExistingFileInsideDirNonEncrypted", ta.removeExistingFileInsideDirNonEncrypted) - t.Run("removeNewlyAddedFileEncrypted", ta.removeNewlyAddedFileEncrypted) - t.Run("removeNewlyAddedFileNonEncrypted", ta.removeNewlyAddedFileNonEncrypted) - t.Run("addNewFileAndModifyContentsEncrypted", ta.addNewFileAndModifyContentsEncrypted) - t.Run("removeEmptyDirEncrypted", ta.removeEmptyDirEncrypted) - t.Run("removeEmptyDirNonEncrypted", ta.removeEmptyDirNonEncrypted) - t.Run("removeDirWhichHasFilesEncrypted", ta.removeDirWhichHasFilesEncrypted) - t.Run("removeDirWhichHasSubDirsEncrypted", ta.removeDirWhichHasSubDirsEncrypted) - t.Run("removeDirWhichHasSubDirsNonEncrypted", ta.removeDirWhichHasSubDirsNonEncrypted) - t.Run("appendFileContentsToEndNonEncrypted", ta.appendFileContentsToEndNonEncrypted) - } -} - -func Upload(uploadDir, index string, a *api.API, toEncrypt bool) (hash string, err error) { - fs := api.NewFileSystem(a) - hash, err = fs.Upload(uploadDir, index, toEncrypt) - return hash, err -} diff --git a/swarm/fuse/swarmfs_unix.go b/swarm/fuse/swarmfs_unix.go deleted file mode 100644 index fda41a261ce1..000000000000 --- a/swarm/fuse/swarmfs_unix.go +++ /dev/null @@ -1,285 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build linux darwin freebsd - -package fuse - -import ( - "context" - "errors" - "fmt" - "os" - "path/filepath" - "strings" - "sync" - "time" - - "bazil.org/fuse" - "bazil.org/fuse/fs" - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/swarm/api" - "github.com/ubiq/go-ubiq/swarm/log" -) - -var ( - errEmptyMountPoint = errors.New("need non-empty mount point") - errNoRelativeMountPoint = errors.New("invalid path for mount point (need absolute path)") - errMaxMountCount = errors.New("max FUSE mount count reached") - errMountTimeout = errors.New("mount timeout") - errAlreadyMounted = errors.New("mount point is already serving") -) - -func isFUSEUnsupportedError(err error) bool { - if perr, ok := err.(*os.PathError); ok { - return perr.Op == "open" && perr.Path == "/dev/fuse" - } - return err == fuse.ErrOSXFUSENotFound -} - -// MountInfo contains information about every active mount -type MountInfo struct { - MountPoint string - StartManifest string - LatestManifest string - rootDir *SwarmDir - fuseConnection *fuse.Conn - swarmApi *api.API - lock *sync.RWMutex - serveClose chan struct{} -} - -func NewMountInfo(mhash, mpoint string, sapi *api.API) *MountInfo { - log.Debug("swarmfs NewMountInfo", "hash", mhash, "mount point", mpoint) - newMountInfo := &MountInfo{ - MountPoint: mpoint, - StartManifest: mhash, - LatestManifest: mhash, - rootDir: nil, - fuseConnection: nil, - swarmApi: sapi, - lock: &sync.RWMutex{}, - serveClose: make(chan struct{}), - } - return newMountInfo -} - -func (swarmfs *SwarmFS) Mount(mhash, mountpoint string) (*MountInfo, error) { - log.Info("swarmfs", "mounting hash", mhash, "mount point", mountpoint) - if mountpoint == "" { - return nil, errEmptyMountPoint - } - if !strings.HasPrefix(mountpoint, "/") { - return nil, errNoRelativeMountPoint - } - cleanedMountPoint, err := filepath.Abs(filepath.Clean(mountpoint)) - if err != nil { - return nil, err - } - log.Trace("swarmfs mount", "cleanedMountPoint", cleanedMountPoint) - - swarmfs.swarmFsLock.Lock() - defer swarmfs.swarmFsLock.Unlock() - - noOfActiveMounts := len(swarmfs.activeMounts) - log.Debug("swarmfs mount", "# active mounts", noOfActiveMounts) - if noOfActiveMounts >= maxFuseMounts { - return nil, errMaxMountCount - } - - if _, ok := swarmfs.activeMounts[cleanedMountPoint]; ok { - return nil, errAlreadyMounted - } - - log.Trace("swarmfs mount: getting manifest tree") - _, manifestEntryMap, err := swarmfs.swarmApi.BuildDirectoryTree(context.TODO(), mhash, true) - if err != nil { - return nil, err - } - - log.Trace("swarmfs mount: building mount info") - mi := NewMountInfo(mhash, cleanedMountPoint, swarmfs.swarmApi) - - dirTree := map[string]*SwarmDir{} - rootDir := NewSwarmDir("/", mi) - log.Trace("swarmfs mount", "rootDir", rootDir) - mi.rootDir = rootDir - - log.Trace("swarmfs mount: traversing manifest map") - for suffix, entry := range manifestEntryMap { - if suffix == "" { //empty suffix means that the file has no name - i.e. this is the default entry in a manifest. Since we cannot have files without a name, let us ignore this entry - log.Warn("Manifest has an empty-path (default) entry which will be ignored in FUSE mount.") - continue - } - addr := common.Hex2Bytes(entry.Hash) - fullpath := "/" + suffix - basepath := filepath.Dir(fullpath) - parentDir := rootDir - dirUntilNow := "" - paths := strings.Split(basepath, "/") - for i := range paths { - if paths[i] != "" { - thisDir := paths[i] - dirUntilNow = dirUntilNow + "/" + thisDir - - if _, ok := dirTree[dirUntilNow]; !ok { - dirTree[dirUntilNow] = NewSwarmDir(dirUntilNow, mi) - parentDir.directories = append(parentDir.directories, dirTree[dirUntilNow]) - parentDir = dirTree[dirUntilNow] - - } else { - parentDir = dirTree[dirUntilNow] - } - } - } - thisFile := NewSwarmFile(basepath, filepath.Base(fullpath), mi) - thisFile.addr = addr - - parentDir.files = append(parentDir.files, thisFile) - } - - fconn, err := fuse.Mount(cleanedMountPoint, fuse.FSName("swarmfs"), fuse.VolumeName(mhash)) - if isFUSEUnsupportedError(err) { - log.Error("swarmfs error - FUSE not installed", "mountpoint", cleanedMountPoint, "err", err) - return nil, err - } else if err != nil { - fuse.Unmount(cleanedMountPoint) - log.Error("swarmfs error mounting swarm manifest", "mountpoint", cleanedMountPoint, "err", err) - return nil, err - } - mi.fuseConnection = fconn - - serverr := make(chan error, 1) - go func() { - log.Info("swarmfs", "serving hash", mhash, "at", cleanedMountPoint) - filesys := &SwarmRoot{root: rootDir} - //start serving the actual file system; see note below - if err := fs.Serve(fconn, filesys); err != nil { - log.Warn("swarmfs could not serve the requested hash", "error", err) - serverr <- err - } - mi.serveClose <- struct{}{} - }() - - /* - IMPORTANT NOTE: the fs.Serve function is blocking; - Serve builds up the actual fuse file system by calling the - Attr functions on each SwarmFile, creating the file inodes; - specifically calling the swarm's LazySectionReader.Size() to set the file size. - - This can take some time, and it appears that if we access the fuse file system - too early, we can bring the tests to deadlock. The assumption so far is that - at this point, the fuse driver didn't finish to initialize the file system. - - Accessing files too early not only deadlocks the tests, but locks the access - of the fuse file completely, resulting in blocked resources at OS system level. - Even a simple `ls /tmp/testDir/testMountDir` could deadlock in a shell. - - Workaround so far is to wait some time to give the OS enough time to initialize - the fuse file system. During tests, this seemed to address the issue. - - HOWEVER IT SHOULD BE NOTED THAT THIS MAY ONLY BE AN EFFECT, - AND THE DEADLOCK CAUSED BY SOMETHING ELSE BLOCKING ACCESS DUE TO SOME RACE CONDITION - (caused in the bazil.org library and/or the SwarmRoot, SwarmDir and SwarmFile implementations) - */ - time.Sleep(2 * time.Second) - - timer := time.NewTimer(mountTimeout) - defer timer.Stop() - // Check if the mount process has an error to report. - select { - case <-timer.C: - log.Warn("swarmfs timed out mounting over FUSE", "mountpoint", cleanedMountPoint, "err", err) - err := fuse.Unmount(cleanedMountPoint) - if err != nil { - return nil, err - } - return nil, errMountTimeout - case err := <-serverr: - log.Warn("swarmfs error serving over FUSE", "mountpoint", cleanedMountPoint, "err", err) - err = fuse.Unmount(cleanedMountPoint) - return nil, err - - case <-fconn.Ready: - //this signals that the actual mount point from the fuse.Mount call is ready; - //it does not signal though that the file system from fs.Serve is actually fully built up - if err := fconn.MountError; err != nil { - log.Error("Mounting error from fuse driver: ", "err", err) - return nil, err - } - log.Info("swarmfs now served over FUSE", "manifest", mhash, "mountpoint", cleanedMountPoint) - } - - timer.Stop() - swarmfs.activeMounts[cleanedMountPoint] = mi - return mi, nil -} - -func (swarmfs *SwarmFS) Unmount(mountpoint string) (*MountInfo, error) { - swarmfs.swarmFsLock.Lock() - defer swarmfs.swarmFsLock.Unlock() - - cleanedMountPoint, err := filepath.Abs(filepath.Clean(mountpoint)) - if err != nil { - return nil, err - } - - mountInfo := swarmfs.activeMounts[cleanedMountPoint] - - if mountInfo == nil || mountInfo.MountPoint != cleanedMountPoint { - return nil, fmt.Errorf("swarmfs %s is not mounted", cleanedMountPoint) - } - err = fuse.Unmount(cleanedMountPoint) - if err != nil { - err1 := externalUnmount(cleanedMountPoint) - if err1 != nil { - errStr := fmt.Sprintf("swarmfs unmount error: %v", err) - log.Warn(errStr) - return nil, err1 - } - } - - err = mountInfo.fuseConnection.Close() - if err != nil { - return nil, err - } - delete(swarmfs.activeMounts, cleanedMountPoint) - - <-mountInfo.serveClose - - succString := fmt.Sprintf("swarmfs unmounting %v succeeded", cleanedMountPoint) - log.Info(succString) - - return mountInfo, nil -} - -func (swarmfs *SwarmFS) Listmounts() []*MountInfo { - swarmfs.swarmFsLock.RLock() - defer swarmfs.swarmFsLock.RUnlock() - rows := make([]*MountInfo, 0, len(swarmfs.activeMounts)) - for _, mi := range swarmfs.activeMounts { - rows = append(rows, mi) - } - return rows -} - -func (swarmfs *SwarmFS) Stop() bool { - for mp := range swarmfs.activeMounts { - mountInfo := swarmfs.activeMounts[mp] - swarmfs.Unmount(mountInfo.MountPoint) - } - return true -} diff --git a/swarm/fuse/swarmfs_util.go b/swarm/fuse/swarmfs_util.go deleted file mode 100644 index 3832024030e1..000000000000 --- a/swarm/fuse/swarmfs_util.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build linux darwin freebsd - -package fuse - -import ( - "context" - "fmt" - "os/exec" - "runtime" - - "github.com/ubiq/go-ubiq/swarm/log" -) - -func externalUnmount(mountPoint string) error { - ctx, cancel := context.WithTimeout(context.Background(), unmountTimeout) - defer cancel() - - // Try generic umount. - if err := exec.CommandContext(ctx, "umount", mountPoint).Run(); err == nil { - return nil - } - // Try FUSE-specific commands if umount didn't work. - switch runtime.GOOS { - case "darwin": - return exec.CommandContext(ctx, "diskutil", "umount", mountPoint).Run() - case "linux": - return exec.CommandContext(ctx, "fusermount", "-u", mountPoint).Run() - default: - return fmt.Errorf("swarmfs unmount: unimplemented") - } -} - -func addFileToSwarm(sf *SwarmFile, content []byte, size int) error { - fkey, mhash, err := sf.mountInfo.swarmApi.AddFile(context.TODO(), sf.mountInfo.LatestManifest, sf.path, sf.name, content, true) - if err != nil { - return err - } - - sf.lock.Lock() - defer sf.lock.Unlock() - sf.addr = fkey - sf.fileSize = int64(size) - - sf.mountInfo.lock.Lock() - defer sf.mountInfo.lock.Unlock() - sf.mountInfo.LatestManifest = mhash - - log.Info("swarmfs added new file:", "fname", sf.name, "new Manifest hash", mhash) - return nil -} - -func removeFileFromSwarm(sf *SwarmFile) error { - mkey, err := sf.mountInfo.swarmApi.RemoveFile(context.TODO(), sf.mountInfo.LatestManifest, sf.path, sf.name, true) - if err != nil { - return err - } - - sf.mountInfo.lock.Lock() - defer sf.mountInfo.lock.Unlock() - sf.mountInfo.LatestManifest = mkey - - log.Info("swarmfs removed file:", "fname", sf.name, "new Manifest hash", mkey) - return nil -} - -func removeDirectoryFromSwarm(sd *SwarmDir) error { - if len(sd.directories) == 0 && len(sd.files) == 0 { - return nil - } - - for _, d := range sd.directories { - err := removeDirectoryFromSwarm(d) - if err != nil { - return err - } - } - - for _, f := range sd.files { - err := removeFileFromSwarm(f) - if err != nil { - return err - } - } - - return nil -} - -func appendToExistingFileInSwarm(sf *SwarmFile, content []byte, offset int64, length int64) error { - fkey, mhash, err := sf.mountInfo.swarmApi.AppendFile(context.TODO(), sf.mountInfo.LatestManifest, sf.path, sf.name, sf.fileSize, content, sf.addr, offset, length, true) - if err != nil { - return err - } - - sf.lock.Lock() - defer sf.lock.Unlock() - sf.addr = fkey - sf.fileSize = sf.fileSize + int64(len(content)) - - sf.mountInfo.lock.Lock() - defer sf.mountInfo.lock.Unlock() - sf.mountInfo.LatestManifest = mhash - - log.Info("swarmfs appended file:", "fname", sf.name, "new Manifest hash", mhash) - return nil -} diff --git a/swarm/log/log.go b/swarm/log/log.go deleted file mode 100644 index 88f99385eaa9..000000000000 --- a/swarm/log/log.go +++ /dev/null @@ -1,48 +0,0 @@ -package log - -import ( - l "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/metrics" -) - -const ( - // CallDepth is set to 1 in order to influence to reported line number of - // the log message with 1 skipped stack frame of calling l.Output() - CallDepth = 1 -) - -// Warn is a convenient alias for log.Warn with stats -func Warn(msg string, ctx ...interface{}) { - metrics.GetOrRegisterCounter("warn", nil).Inc(1) - l.Output(msg, l.LvlWarn, CallDepth, ctx...) -} - -// Error is a convenient alias for log.Error with stats -func Error(msg string, ctx ...interface{}) { - metrics.GetOrRegisterCounter("error", nil).Inc(1) - l.Output(msg, l.LvlError, CallDepth, ctx...) -} - -// Crit is a convenient alias for log.Crit with stats -func Crit(msg string, ctx ...interface{}) { - metrics.GetOrRegisterCounter("crit", nil).Inc(1) - l.Output(msg, l.LvlCrit, CallDepth, ctx...) -} - -// Info is a convenient alias for log.Info with stats -func Info(msg string, ctx ...interface{}) { - metrics.GetOrRegisterCounter("info", nil).Inc(1) - l.Output(msg, l.LvlInfo, CallDepth, ctx...) -} - -// Debug is a convenient alias for log.Debug with stats -func Debug(msg string, ctx ...interface{}) { - metrics.GetOrRegisterCounter("debug", nil).Inc(1) - l.Output(msg, l.LvlDebug, CallDepth, ctx...) -} - -// Trace is a convenient alias for log.Trace with stats -func Trace(msg string, ctx ...interface{}) { - metrics.GetOrRegisterCounter("trace", nil).Inc(1) - l.Output(msg, l.LvlTrace, CallDepth, ctx...) -} diff --git a/swarm/metrics/flags.go b/swarm/metrics/flags.go deleted file mode 100644 index 92727eba0bc3..000000000000 --- a/swarm/metrics/flags.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package metrics - -import ( - "time" - - "github.com/ubiq/go-ubiq/cmd/utils" - gubiqmetrics "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/metrics/influxdb" - "github.com/ubiq/go-ubiq/swarm/log" - cli "gopkg.in/urfave/cli.v1" -) - -var ( - MetricsEnableInfluxDBExportFlag = cli.BoolFlag{ - Name: "metrics.influxdb.export", - Usage: "Enable metrics export/push to an external InfluxDB database", - } - MetricsEnableInfluxDBAccountingExportFlag = cli.BoolFlag{ - Name: "metrics.influxdb.accounting", - Usage: "Enable accounting metrics export/push to an external InfluxDB database", - } - MetricsInfluxDBEndpointFlag = cli.StringFlag{ - Name: "metrics.influxdb.endpoint", - Usage: "Metrics InfluxDB endpoint", - Value: "http://127.0.0.1:8086", - } - MetricsInfluxDBDatabaseFlag = cli.StringFlag{ - Name: "metrics.influxdb.database", - Usage: "Metrics InfluxDB database", - Value: "metrics", - } - MetricsInfluxDBUsernameFlag = cli.StringFlag{ - Name: "metrics.influxdb.username", - Usage: "Metrics InfluxDB username", - Value: "", - } - MetricsInfluxDBPasswordFlag = cli.StringFlag{ - Name: "metrics.influxdb.password", - Usage: "Metrics InfluxDB password", - Value: "", - } - // Tags are part of every measurement sent to InfluxDB. Queries on tags are faster in InfluxDB. - // For example `host` tag could be used so that we can group all nodes and average a measurement - // across all of them, but also so that we can select a specific node and inspect its measurements. - // https://docs.influxdata.com/influxdb/v1.4/concepts/key_concepts/#tag-key - MetricsInfluxDBTagsFlag = cli.StringFlag{ - Name: "metrics.influxdb.tags", - Usage: "Comma-separated InfluxDB tags (key/values) attached to all measurements", - Value: "host=localhost", - } -) - -// Flags holds all command-line flags required for metrics collection. -var Flags = []cli.Flag{ - utils.MetricsEnabledFlag, - MetricsEnableInfluxDBExportFlag, - MetricsEnableInfluxDBAccountingExportFlag, - MetricsInfluxDBEndpointFlag, - MetricsInfluxDBDatabaseFlag, - MetricsInfluxDBUsernameFlag, - MetricsInfluxDBPasswordFlag, - MetricsInfluxDBTagsFlag, -} - -func Setup(ctx *cli.Context) { - if gubiqmetrics.Enabled { - log.Info("Enabling swarm metrics collection") - var ( - endpoint = ctx.GlobalString(MetricsInfluxDBEndpointFlag.Name) - database = ctx.GlobalString(MetricsInfluxDBDatabaseFlag.Name) - username = ctx.GlobalString(MetricsInfluxDBUsernameFlag.Name) - password = ctx.GlobalString(MetricsInfluxDBPasswordFlag.Name) - enableExport = ctx.GlobalBool(MetricsEnableInfluxDBExportFlag.Name) - enableAccountingExport = ctx.GlobalBool(MetricsEnableInfluxDBAccountingExportFlag.Name) - ) - - // Start system runtime metrics collection - go gubiqmetrics.CollectProcessMetrics(2 * time.Second) - - tagsMap := utils.SplitTagsFlag(ctx.GlobalString(MetricsInfluxDBTagsFlag.Name)) - - if enableExport { - log.Info("Enabling swarm metrics export to InfluxDB") - go influxdb.InfluxDBWithTags(gubiqmetrics.DefaultRegistry, 10*time.Second, endpoint, database, username, password, "swarm.", tagsMap) - } - - if enableAccountingExport { - log.Info("Exporting swarm accounting metrics to InfluxDB") - go influxdb.InfluxDBWithTags(gubiqmetrics.AccountingRegistry, 10*time.Second, endpoint, database, username, password, "accounting.", tagsMap) - } - } -} diff --git a/swarm/network/README.md b/swarm/network/README.md deleted file mode 100644 index 684ad0c8c017..000000000000 --- a/swarm/network/README.md +++ /dev/null @@ -1,152 +0,0 @@ -## Streaming - -Streaming is a new protocol of the swarm bzz bundle of protocols. -This protocol provides the basic logic for chunk-based data flow. -It implements simple retrieve requests and delivery using priority queue. -A data exchange stream is a directional flow of chunks between peers. -The source of datachunks is the upstream, the receiver is called the -downstream peer. Each streaming protocol defines an outgoing streamer -and an incoming streamer, the former installing on the upstream, -the latter on the downstream peer. - -Subscribe on StreamerPeer launches an incoming streamer that sends -a subscribe msg upstream. The streamer on the upstream peer -handles the subscribe msg by installing the relevant outgoing streamer -. The modules now engage in a process of upstream sending a sequence of hashes of -chunks downstream (OfferedHashesMsg). The downstream peer evaluates which hashes are needed -and get it delivered by sending back a msg (WantedHashesMsg). - -Historical syncing is supported - currently not the right abstraction -- -state kept across sessions by saving a series of intervals after their last -batch actually arrived. - -Live streaming is also supported, by starting session from the first item -after the subscription. - -Provable data exchange. In case a stream represents a swarm document's data layer -or higher level chunks, streaming up to a certain index is always provable. It saves on -sending intermediate chunks. - -Using the streamer logic, various stream types are easy to implement: - -* light node requests: - * url lookup with offset - * document download - * document upload -* syncing - * live session syncing - * historical syncing -* simple retrieve requests and deliveries -* swarm feeds streams -* receipting for finger pointing - -## Syncing - -Syncing is the process that makes sure storer nodes end up storing all and only the chunks that are requested from them. - -### Requirements - -- eventual consistency: so each chunk historical should be syncable -- since the same chunk can and will arrive from many peers, (network traffic should be -optimised, only one transfer of data per chunk) -- explicit request deliveries should be prioritised higher than recent chunks received -during the ongoing session which in turn should be higher than historical chunks. -- insured chunks should get receipted for finger pointing litigation, the receipts storage -should be organised efficiently, upstream peer should also be able to find these -receipts for a deleted chunk easily to refute their challenge. -- syncing should be resilient to cut connections, metadata should be persisted that -keep track of syncing state across sessions, historical syncing state should survive restart -- extra data structures to support syncing should be kept at minimum -- syncing is not organized separately for chunk types (Swarm feed updates v regular content chunk) -- various types of streams should have common logic abstracted - -Syncing is now entirely mediated by the localstore, ie., no processes or memory leaks due to network contention. -When a new chunk is stored, its chunk hash is index by proximity bin - -peers syncronise by getting the chunks closer to the downstream peer than to the upstream one. -Consequently peers just sync all stored items for the kad bin the receiving peer falls into. -The special case of nearest neighbour sets is handled by the downstream peer -indicating they want to sync all kademlia bins with proximity equal to or higher -than their depth. - -This sync state represents the initial state of a sync connection session. -Retrieval is dictated by downstream peers simply using a special streamer protocol. - -Syncing chunks created during the session by the upstream peer is called live session syncing -while syncing of earlier chunks is historical syncing. - -Once the relevant chunk is retrieved, downstream peer looks up all hash segments in its localstore -and sends to the upstream peer a message with a a bitvector to indicate -missing chunks (e.g., for chunk `k`, hash with chunk internal index which case ) -new items. In turn upstream peer sends the relevant chunk data alongside their index. - -On sending chunks there is a priority queue system. If during looking up hashes in its localstore, -downstream peer hits on an open request then a retrieve request is sent immediately to the upstream peer indicating -that no extra round of checks is needed. If another peers syncer hits the same open request, it is slightly unsafe to not ask -that peer too: if the first one disconnects before delivering or fails to deliver and therefore gets -disconnected, we should still be able to continue with the other. The minimum redundant traffic coming from such simultaneous -eventualities should be sufficiently rare not to warrant more complex treatment. - -Session syncing involves downstream peer to request a new state on a bin from upstream. -using the new state, the range (of chunks) between the previous state and the new one are retrieved -and chunks are requested identical to the historical case. After receiving all the missing chunks -from the new hashes, downstream peer will request a new range. If this happens before upstream peer updates a new state, -we say that session syncing is live or the two peers are in sync. In general the time interval passed since downstream peer request up to the current session cursor is a good indication of a permanent (probably increasing) lag. - -If there is no historical backlog, and downstream peer has an acceptable 'last synced' tag, then it is said to be fully synced with the upstream peer. -If a peer is fully synced with all its storer peers, it can advertise itself as globally fully synced. - -The downstream peer persists the record of the last synced offset. When the two peers disconnect and -reconnect syncing can start from there. -This situation however can also happen while historical syncing is not yet complete. -Effectively this means that the peer needs to persist a record of an arbitrary array of offset ranges covered. - -### Delivery requests - -once the appropriate ranges of the hashstream are retrieved and buffered, downstream peer just scans the hashes, looks them up in localstore, if not found, create a request entry. -The range is referenced by the chunk index. Alongside the name (indicating the stream, e.g., content chunks for bin 6) and the range -downstream peer sends a 128 long bitvector indicating which chunks are needed. -Newly created requests are satisfied bound together in a waitgroup which when done, will promptt sending the next one. -to be able to do check and storage concurrently, we keep a buffer of one, we start with two batches of hashes. -If there is nothing to give, upstream peers SetNextBatch is blocking. Subscription ends with an unsubscribe. which removes the syncer from the map. - -Canceling requests (for instance the late chunks of an erasure batch) should be a chan closed -on the request - -Simple request is also a subscribe -different streaming protocols are different p2p protocols with same message types. -the constructor is the Run function itself. which takes a streamerpeer as argument - - -### provable streams - -The swarm hash over the hash stream has many advantages. It implements a provable data transfer -and provide efficient storage for receipts in the form of inclusion proofs useable for finger pointing litigation. -When challenged on a missing chunk, upstream peer will provide an inclusion proof of a chunk hash against the state of the -sync stream. In order to be able to generate such an inclusion proof, upstream peer needs to store the hash index (counting consecutive hash-size segments) alongside the chunk data and preserve it even when the chunk data is deleted until the chunk is no longer insured. -if there is no valid insurance on the files the entry may be deleted. -As long as the chunk is preserved, no takeover proof will be needed since the node can respond to any challenge. -However, once the node needs to delete an insured chunk for capacity reasons, a receipt should be available to -refute the challenge by finger pointing to a downstream peer. -As part of the deletion protocol then, hashes of insured chunks to be removed are pushed to an infinite stream for every bin. - -Downstream peer on the other hand needs to make sure that they can only be finger pointed about a chunk they did receive and store. -For this the check of a state should be exhaustive. If historical syncing finishes on one state, all hashes before are covered, no -surprises. In other words historical syncing this process is self verifying. With session syncing however, it is not enough to check going back covering the range from old offset to new. Continuity (i.e., that the new state is extension of the old) needs to be verified: after downstream peer reads the range into a buffer, it appends the buffer the last known state at the last known offset and verifies the resulting hash matches -the latest state. Past intervals of historical syncing are checked via the session root. -Upstream peer signs the states, downstream peers can use as handover proofs. -Downstream peers sign off on a state together with an initial offset. - -Once historical syncing is complete and the session does not lag, downstream peer only preserves the latest upstream state and store the signed version. - -Upstream peer needs to keep the latest takeover states: each deleted chunk's hash should be covered by takeover proof of at least one peer. If historical syncing is complete, upstream peer typically will store only the latest takeover proof from downstream peer. -Crucially, the structure is totally independent of the number of peers in the bin, so it scales extremely well. - -## implementation - -The simplest protocol just involves upstream peer to prefix the key with the kademlia proximity order (say 0-15 or 0-31) -and simply iterate on index per bin when syncing with a peer. - -priority queues are used for sending chunks so that user triggered requests should be responded to first, session syncing second, and historical with lower priority. -The request on chunks remains implemented as a dataless entry in the memory store. -The lifecycle of this object should be more carefully thought through, ie., when it fails to retrieve it should be removed. diff --git a/swarm/network/bitvector/bitvector.go b/swarm/network/bitvector/bitvector.go deleted file mode 100644 index 958328502329..000000000000 --- a/swarm/network/bitvector/bitvector.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package bitvector - -import ( - "errors" -) - -var errInvalidLength = errors.New("invalid length") - -type BitVector struct { - len int - b []byte -} - -func New(l int) (bv *BitVector, err error) { - return NewFromBytes(make([]byte, l/8+1), l) -} - -func NewFromBytes(b []byte, l int) (bv *BitVector, err error) { - if l <= 0 { - return nil, errInvalidLength - } - if len(b)*8 < l { - return nil, errInvalidLength - } - return &BitVector{ - len: l, - b: b, - }, nil -} - -func (bv *BitVector) Get(i int) bool { - bi := i / 8 - return bv.b[bi]&(0x1<. - -package bitvector - -import "testing" - -func TestBitvectorNew(t *testing.T) { - _, err := New(0) - if err != errInvalidLength { - t.Errorf("expected err %v, got %v", errInvalidLength, err) - } - - _, err = NewFromBytes(nil, 0) - if err != errInvalidLength { - t.Errorf("expected err %v, got %v", errInvalidLength, err) - } - - _, err = NewFromBytes([]byte{0}, 9) - if err != errInvalidLength { - t.Errorf("expected err %v, got %v", errInvalidLength, err) - } - - _, err = NewFromBytes(make([]byte, 8), 8) - if err != nil { - t.Error(err) - } -} - -func TestBitvectorGetSet(t *testing.T) { - for _, length := range []int{ - 1, - 2, - 4, - 8, - 9, - 15, - 16, - } { - bv, err := New(length) - if err != nil { - t.Errorf("error for length %v: %v", length, err) - } - - for i := 0; i < length; i++ { - if bv.Get(i) { - t.Errorf("expected false for element on index %v", i) - } - } - - func() { - defer func() { - if err := recover(); err == nil { - t.Errorf("expecting panic") - } - }() - bv.Get(length + 8) - }() - - for i := 0; i < length; i++ { - bv.Set(i, true) - for j := 0; j < length; j++ { - if j == i { - if !bv.Get(j) { - t.Errorf("element on index %v is not set to true", i) - } - } else { - if bv.Get(j) { - t.Errorf("element on index %v is not false", i) - } - } - } - - bv.Set(i, false) - - if bv.Get(i) { - t.Errorf("element on index %v is not set to false", i) - } - } - } -} - -func TestBitvectorNewFromBytesGet(t *testing.T) { - bv, err := NewFromBytes([]byte{8}, 8) - if err != nil { - t.Error(err) - } - if !bv.Get(3) { - t.Fatalf("element 3 is not set to true: state %08b", bv.b[0]) - } -} diff --git a/swarm/network/common.go b/swarm/network/common.go deleted file mode 100644 index 15b2e2060437..000000000000 --- a/swarm/network/common.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package network - -import ( - "fmt" - "strings" -) - -func LogAddrs(nns [][]byte) string { - var nnsa []string - for _, nn := range nns { - nnsa = append(nnsa, fmt.Sprintf("%08x", nn[:4])) - } - return strings.Join(nnsa, ", ") -} diff --git a/swarm/network/discovery.go b/swarm/network/discovery.go deleted file mode 100644 index 4e8692173595..000000000000 --- a/swarm/network/discovery.go +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package network - -import ( - "context" - "fmt" - "sync" - - "github.com/ubiq/go-ubiq/swarm/pot" -) - -// discovery bzz extension for requesting and relaying node address records - -// Peer wraps BzzPeer and embeds Kademlia overlay connectivity driver -type Peer struct { - *BzzPeer - kad *Kademlia - sentPeers bool // whether we already sent peer closer to this address - mtx sync.RWMutex // - peers map[string]bool // tracks node records sent to the peer - depth uint8 // the proximity order advertised by remote as depth of saturation -} - -// NewPeer constructs a discovery peer -func NewPeer(p *BzzPeer, kad *Kademlia) *Peer { - d := &Peer{ - kad: kad, - BzzPeer: p, - peers: make(map[string]bool), - } - // record remote as seen so we never send a peer its own record - d.seen(p.BzzAddr) - return d -} - -// HandleMsg is the message handler that delegates incoming messages -func (d *Peer) HandleMsg(ctx context.Context, msg interface{}) error { - switch msg := msg.(type) { - - case *peersMsg: - return d.handlePeersMsg(msg) - - case *subPeersMsg: - return d.handleSubPeersMsg(msg) - - default: - return fmt.Errorf("unknown message type: %T", msg) - } -} - -// NotifyDepth sends a message to all connections if depth of saturation is changed -func NotifyDepth(depth uint8, kad *Kademlia) { - f := func(val *Peer, po int) bool { - val.NotifyDepth(depth) - return true - } - kad.EachConn(nil, 255, f) -} - -// NotifyPeer informs all peers about a newly added node -func NotifyPeer(p *BzzAddr, k *Kademlia) { - f := func(val *Peer, po int) bool { - val.NotifyPeer(p, uint8(po)) - return true - } - k.EachConn(p.Address(), 255, f) -} - -// NotifyPeer notifies the remote node (recipient) about a peer if -// the peer's PO is within the recipients advertised depth -// OR the peer is closer to the recipient than self -// unless already notified during the connection session -func (d *Peer) NotifyPeer(a *BzzAddr, po uint8) { - // immediately return - if (po < d.getDepth() && pot.ProxCmp(d.kad.BaseAddr(), d, a) != 1) || d.seen(a) { - return - } - resp := &peersMsg{ - Peers: []*BzzAddr{a}, - } - go d.Send(context.TODO(), resp) -} - -// NotifyDepth sends a subPeers Msg to the receiver notifying them about -// a change in the depth of saturation -func (d *Peer) NotifyDepth(po uint8) { - go d.Send(context.TODO(), &subPeersMsg{Depth: po}) -} - -/* -peersMsg is the message to pass peer information -It is always a response to a peersRequestMsg - -The encoding of a peer address is identical the devp2p base protocol peers -messages: [IP, Port, NodeID], -Note that a node's FileStore address is not the NodeID but the hash of the NodeID. - -TODO: -To mitigate against spurious peers messages, requests should be remembered -and correctness of responses should be checked - -If the proxBin of peers in the response is incorrect the sender should be -disconnected -*/ - -// peersMsg encapsulates an array of peer addresses -// used for communicating about known peers -// relevant for bootstrapping connectivity and updating peersets -type peersMsg struct { - Peers []*BzzAddr -} - -// String pretty prints a peersMsg -func (msg peersMsg) String() string { - return fmt.Sprintf("%T: %v", msg, msg.Peers) -} - -// handlePeersMsg called by the protocol when receiving peerset (for target address) -// list of nodes ([]PeerAddr in peersMsg) is added to the overlay db using the -// Register interface method -func (d *Peer) handlePeersMsg(msg *peersMsg) error { - // register all addresses - if len(msg.Peers) == 0 { - return nil - } - - for _, a := range msg.Peers { - d.seen(a) - NotifyPeer(a, d.kad) - } - return d.kad.Register(msg.Peers...) -} - -// subPeers msg is communicating the depth of the overlay table of a peer -type subPeersMsg struct { - Depth uint8 -} - -// String returns the pretty printer -func (msg subPeersMsg) String() string { - return fmt.Sprintf("%T: request peers > PO%02d. ", msg, msg.Depth) -} - -func (d *Peer) handleSubPeersMsg(msg *subPeersMsg) error { - if !d.sentPeers { - d.setDepth(msg.Depth) - var peers []*BzzAddr - d.kad.EachConn(d.Over(), 255, func(p *Peer, po int) bool { - if pob, _ := Pof(d, d.kad.BaseAddr(), 0); pob > po { - return false - } - if !d.seen(p.BzzAddr) { - peers = append(peers, p.BzzAddr) - } - return true - }) - if len(peers) > 0 { - go d.Send(context.TODO(), &peersMsg{Peers: peers}) - } - } - d.sentPeers = true - return nil -} - -// seen takes an peer address and checks if it was sent to a peer already -// if not, marks the peer as sent -func (d *Peer) seen(p *BzzAddr) bool { - d.mtx.Lock() - defer d.mtx.Unlock() - k := string(p.Address()) - if d.peers[k] { - return true - } - d.peers[k] = true - return false -} - -func (d *Peer) getDepth() uint8 { - d.mtx.RLock() - defer d.mtx.RUnlock() - return d.depth -} - -func (d *Peer) setDepth(depth uint8) { - d.mtx.Lock() - defer d.mtx.Unlock() - d.depth = depth -} diff --git a/swarm/network/discovery_test.go b/swarm/network/discovery_test.go deleted file mode 100644 index b154b14cd4bc..000000000000 --- a/swarm/network/discovery_test.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package network - -import ( - "testing" - - p2ptest "github.com/ubiq/go-ubiq/p2p/testing" -) - -/*** - * - * - after connect, that outgoing subpeersmsg is sent - * - */ -func TestDiscovery(t *testing.T) { - params := NewHiveParams() - s, pp := newHiveTester(t, params, 1, nil) - - node := s.Nodes[0] - raddr := NewAddr(node) - pp.Register(raddr) - - // start the hive and wait for the connection - pp.Start(s.Server) - defer pp.Stop() - - // send subPeersMsg to the peer - err := s.TestExchanges(p2ptest.Exchange{ - Label: "outgoing subPeersMsg", - Expects: []p2ptest.Expect{ - { - Code: 1, - Msg: &subPeersMsg{Depth: 0}, - Peer: node.ID(), - }, - }, - }) - - if err != nil { - t.Fatal(err) - } -} diff --git a/swarm/network/fetcher.go b/swarm/network/fetcher.go deleted file mode 100644 index d8eb5b972bf9..000000000000 --- a/swarm/network/fetcher.go +++ /dev/null @@ -1,323 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package network - -import ( - "context" - "sync" - "time" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/swarm/storage" -) - -const ( - defaultSearchTimeout = 1 * time.Second - // maximum number of forwarded requests (hops), to make sure requests are not - // forwarded forever in peer loops - maxHopCount uint8 = 20 -) - -// Time to consider peer to be skipped. -// Also used in stream delivery. -var RequestTimeout = 10 * time.Second - -type RequestFunc func(context.Context, *Request) (*enode.ID, chan struct{}, error) - -// Fetcher is created when a chunk is not found locally. It starts a request handler loop once and -// keeps it alive until all active requests are completed. This can happen: -// 1. either because the chunk is delivered -// 2. or because the requester cancelled/timed out -// Fetcher self destroys itself after it is completed. -// TODO: cancel all forward requests after termination -type Fetcher struct { - protoRequestFunc RequestFunc // request function fetcher calls to issue retrieve request for a chunk - addr storage.Address // the address of the chunk to be fetched - offerC chan *enode.ID // channel of sources (peer node id strings) - requestC chan uint8 // channel for incoming requests (with the hopCount value in it) - searchTimeout time.Duration - skipCheck bool - ctx context.Context -} - -type Request struct { - Addr storage.Address // chunk address - Source *enode.ID // nodeID of peer to request from (can be nil) - SkipCheck bool // whether to offer the chunk first or deliver directly - peersToSkip *sync.Map // peers not to request chunk from (only makes sense if source is nil) - HopCount uint8 // number of forwarded requests (hops) -} - -// NewRequest returns a new instance of Request based on chunk address skip check and -// a map of peers to skip. -func NewRequest(addr storage.Address, skipCheck bool, peersToSkip *sync.Map) *Request { - return &Request{ - Addr: addr, - SkipCheck: skipCheck, - peersToSkip: peersToSkip, - } -} - -// SkipPeer returns if the peer with nodeID should not be requested to deliver a chunk. -// Peers to skip are kept per Request and for a time period of RequestTimeout. -// This function is used in stream package in Delivery.RequestFromPeers to optimize -// requests for chunks. -func (r *Request) SkipPeer(nodeID string) bool { - val, ok := r.peersToSkip.Load(nodeID) - if !ok { - return false - } - t, ok := val.(time.Time) - if ok && time.Now().After(t.Add(RequestTimeout)) { - // deadline expired - r.peersToSkip.Delete(nodeID) - return false - } - return true -} - -// FetcherFactory is initialised with a request function and can create fetchers -type FetcherFactory struct { - request RequestFunc - skipCheck bool -} - -// NewFetcherFactory takes a request function and skip check parameter and creates a FetcherFactory -func NewFetcherFactory(request RequestFunc, skipCheck bool) *FetcherFactory { - return &FetcherFactory{ - request: request, - skipCheck: skipCheck, - } -} - -// New constructs a new Fetcher, for the given chunk. All peers in peersToSkip -// are not requested to deliver the given chunk. peersToSkip should always -// contain the peers which are actively requesting this chunk, to make sure we -// don't request back the chunks from them. -// The created Fetcher is started and returned. -func (f *FetcherFactory) New(ctx context.Context, source storage.Address, peers *sync.Map) storage.NetFetcher { - fetcher := NewFetcher(ctx, source, f.request, f.skipCheck) - go fetcher.run(peers) - return fetcher -} - -// NewFetcher creates a new Fetcher for the given chunk address using the given request function. -func NewFetcher(ctx context.Context, addr storage.Address, rf RequestFunc, skipCheck bool) *Fetcher { - return &Fetcher{ - addr: addr, - protoRequestFunc: rf, - offerC: make(chan *enode.ID), - requestC: make(chan uint8), - searchTimeout: defaultSearchTimeout, - skipCheck: skipCheck, - ctx: ctx, - } -} - -// Offer is called when an upstream peer offers the chunk via syncing as part of `OfferedHashesMsg` and the node does not have the chunk locally. -func (f *Fetcher) Offer(source *enode.ID) { - // First we need to have this select to make sure that we return if context is done - select { - case <-f.ctx.Done(): - return - default: - } - - // This select alone would not guarantee that we return of context is done, it could potentially - // push to offerC instead if offerC is available (see number 2 in https://golang.org/ref/spec#Select_statements) - select { - case f.offerC <- source: - case <-f.ctx.Done(): - } -} - -// Request is called when an upstream peer request the chunk as part of `RetrieveRequestMsg`, or from a local request through FileStore, and the node does not have the chunk locally. -func (f *Fetcher) Request(hopCount uint8) { - // First we need to have this select to make sure that we return if context is done - select { - case <-f.ctx.Done(): - return - default: - } - - if hopCount >= maxHopCount { - log.Debug("fetcher request hop count limit reached", "hops", hopCount) - return - } - - // This select alone would not guarantee that we return of context is done, it could potentially - // push to offerC instead if offerC is available (see number 2 in https://golang.org/ref/spec#Select_statements) - select { - case f.requestC <- hopCount + 1: - case <-f.ctx.Done(): - } -} - -// start prepares the Fetcher -// it keeps the Fetcher alive within the lifecycle of the passed context -func (f *Fetcher) run(peers *sync.Map) { - var ( - doRequest bool // determines if retrieval is initiated in the current iteration - wait *time.Timer // timer for search timeout - waitC <-chan time.Time // timer channel - sources []*enode.ID // known sources, ie. peers that offered the chunk - requested bool // true if the chunk was actually requested - hopCount uint8 - ) - gone := make(chan *enode.ID) // channel to signal that a peer we requested from disconnected - - // loop that keeps the fetching process alive - // after every request a timer is set. If this goes off we request again from another peer - // note that the previous request is still alive and has the chance to deliver, so - // requesting again extends the search. ie., - // if a peer we requested from is gone we issue a new request, so the number of active - // requests never decreases - for { - select { - - // incoming offer - case source := <-f.offerC: - log.Trace("new source", "peer addr", source, "request addr", f.addr) - // 1) the chunk is offered by a syncing peer - // add to known sources - sources = append(sources, source) - // launch a request to the source iff the chunk was requested (not just expected because its offered by a syncing peer) - doRequest = requested - - // incoming request - case hopCount = <-f.requestC: - log.Trace("new request", "request addr", f.addr) - // 2) chunk is requested, set requested flag - // launch a request iff none been launched yet - doRequest = !requested - requested = true - - // peer we requested from is gone. fall back to another - // and remove the peer from the peers map - case id := <-gone: - log.Trace("peer gone", "peer id", id.String(), "request addr", f.addr) - peers.Delete(id.String()) - doRequest = requested - - // search timeout: too much time passed since the last request, - // extend the search to a new peer if we can find one - case <-waitC: - log.Trace("search timed out: requesting", "request addr", f.addr) - doRequest = requested - - // all Fetcher context closed, can quit - case <-f.ctx.Done(): - log.Trace("terminate fetcher", "request addr", f.addr) - // TODO: send cancellations to all peers left over in peers map (i.e., those we requested from) - return - } - - // need to issue a new request - if doRequest { - var err error - sources, err = f.doRequest(gone, peers, sources, hopCount) - if err != nil { - log.Info("unable to request", "request addr", f.addr, "err", err) - } - } - - // if wait channel is not set, set it to a timer - if requested { - if wait == nil { - wait = time.NewTimer(f.searchTimeout) - defer wait.Stop() - waitC = wait.C - } else { - // stop the timer and drain the channel if it was not drained earlier - if !wait.Stop() { - select { - case <-wait.C: - default: - } - } - // reset the timer to go off after defaultSearchTimeout - wait.Reset(f.searchTimeout) - } - } - doRequest = false - } -} - -// doRequest attempts at finding a peer to request the chunk from -// * first it tries to request explicitly from peers that are known to have offered the chunk -// * if there are no such peers (available) it tries to request it from a peer closest to the chunk address -// excluding those in the peersToSkip map -// * if no such peer is found an error is returned -// -// if a request is successful, -// * the peer's address is added to the set of peers to skip -// * the peer's address is removed from prospective sources, and -// * a go routine is started that reports on the gone channel if the peer is disconnected (or terminated their streamer) -func (f *Fetcher) doRequest(gone chan *enode.ID, peersToSkip *sync.Map, sources []*enode.ID, hopCount uint8) ([]*enode.ID, error) { - var i int - var sourceID *enode.ID - var quit chan struct{} - - req := &Request{ - Addr: f.addr, - SkipCheck: f.skipCheck, - peersToSkip: peersToSkip, - HopCount: hopCount, - } - - foundSource := false - // iterate over known sources - for i = 0; i < len(sources); i++ { - req.Source = sources[i] - var err error - sourceID, quit, err = f.protoRequestFunc(f.ctx, req) - if err == nil { - // remove the peer from known sources - // Note: we can modify the source although we are looping on it, because we break from the loop immediately - sources = append(sources[:i], sources[i+1:]...) - foundSource = true - break - } - } - - // if there are no known sources, or none available, we try request from a closest node - if !foundSource { - req.Source = nil - var err error - sourceID, quit, err = f.protoRequestFunc(f.ctx, req) - if err != nil { - // if no peers found to request from - return sources, err - } - } - // add peer to the set of peers to skip from now - peersToSkip.Store(sourceID.String(), time.Now()) - - // if the quit channel is closed, it indicates that the source peer we requested from - // disconnected or terminated its streamer - // here start a go routine that watches this channel and reports the source peer on the gone channel - // this go routine quits if the fetcher global context is done to prevent process leak - go func() { - select { - case <-quit: - gone <- sourceID - case <-f.ctx.Done(): - } - }() - return sources, nil -} diff --git a/swarm/network/fetcher_test.go b/swarm/network/fetcher_test.go deleted file mode 100644 index e5e9f4190359..000000000000 --- a/swarm/network/fetcher_test.go +++ /dev/null @@ -1,476 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package network - -import ( - "context" - "sync" - "testing" - "time" - - "github.com/ubiq/go-ubiq/p2p/enode" -) - -var requestedPeerID = enode.HexID("3431c3939e1ee2a6345e976a8234f9870152d64879f30bc272a074f6859e75e8") -var sourcePeerID = enode.HexID("99d8594b52298567d2ca3f4c441a5ba0140ee9245e26460d01102a52773c73b9") - -// mockRequester pushes every request to the requestC channel when its doRequest function is called -type mockRequester struct { - // requests []Request - requestC chan *Request // when a request is coming it is pushed to requestC - waitTimes []time.Duration // with waitTimes[i] you can define how much to wait on the ith request (optional) - count int //counts the number of requests - quitC chan struct{} -} - -func newMockRequester(waitTimes ...time.Duration) *mockRequester { - return &mockRequester{ - requestC: make(chan *Request), - waitTimes: waitTimes, - quitC: make(chan struct{}), - } -} - -func (m *mockRequester) doRequest(ctx context.Context, request *Request) (*enode.ID, chan struct{}, error) { - waitTime := time.Duration(0) - if m.count < len(m.waitTimes) { - waitTime = m.waitTimes[m.count] - m.count++ - } - time.Sleep(waitTime) - m.requestC <- request - - // if there is a Source in the request use that, if not use the global requestedPeerId - source := request.Source - if source == nil { - source = &requestedPeerID - } - return source, m.quitC, nil -} - -// TestFetcherSingleRequest creates a Fetcher using mockRequester, and run it with a sample set of peers to skip. -// mockRequester pushes a Request on a channel every time the request function is called. Using -// this channel we test if calling Fetcher.Request calls the request function, and whether it uses -// the correct peers to skip which we provided for the fetcher.run function. -func TestFetcherSingleRequest(t *testing.T) { - requester := newMockRequester() - addr := make([]byte, 32) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - fetcher := NewFetcher(ctx, addr, requester.doRequest, true) - - peers := []string{"a", "b", "c", "d"} - peersToSkip := &sync.Map{} - for _, p := range peers { - peersToSkip.Store(p, time.Now()) - } - - go fetcher.run(peersToSkip) - - fetcher.Request(0) - - select { - case request := <-requester.requestC: - // request should contain all peers from peersToSkip provided to the fetcher - for _, p := range peers { - if _, ok := request.peersToSkip.Load(p); !ok { - t.Fatalf("request.peersToSkip misses peer") - } - } - - // source peer should be also added to peersToSkip eventually - time.Sleep(100 * time.Millisecond) - if _, ok := request.peersToSkip.Load(requestedPeerID.String()); !ok { - t.Fatalf("request.peersToSkip does not contain peer returned by the request function") - } - - // hopCount in the forwarded request should be incremented - if request.HopCount != 1 { - t.Fatalf("Expected request.HopCount 1 got %v", request.HopCount) - } - - // fetch should trigger a request, if it doesn't happen in time, test should fail - case <-time.After(200 * time.Millisecond): - t.Fatalf("fetch timeout") - } -} - -// TestCancelStopsFetcher tests that a cancelled fetcher does not initiate further requests even if its fetch function is called -func TestFetcherCancelStopsFetcher(t *testing.T) { - requester := newMockRequester() - addr := make([]byte, 32) - - ctx, cancel := context.WithCancel(context.Background()) - - fetcher := NewFetcher(ctx, addr, requester.doRequest, true) - - peersToSkip := &sync.Map{} - - // we start the fetcher, and then we immediately cancel the context - go fetcher.run(peersToSkip) - cancel() - - // we call Request with an active context - fetcher.Request(0) - - // fetcher should not initiate request, we can only check by waiting a bit and making sure no request is happening - select { - case <-requester.requestC: - t.Fatalf("cancelled fetcher initiated request") - case <-time.After(200 * time.Millisecond): - } -} - -// TestFetchCancelStopsRequest tests that calling a Request function with a cancelled context does not initiate a request -func TestFetcherCancelStopsRequest(t *testing.T) { - t.Skip("since context is now per fetcher, this test is likely redundant") - - requester := newMockRequester(100 * time.Millisecond) - addr := make([]byte, 32) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - fetcher := NewFetcher(ctx, addr, requester.doRequest, true) - - peersToSkip := &sync.Map{} - - // we start the fetcher with an active context - go fetcher.run(peersToSkip) - - // we call Request with a cancelled context - fetcher.Request(0) - - // fetcher should not initiate request, we can only check by waiting a bit and making sure no request is happening - select { - case <-requester.requestC: - t.Fatalf("cancelled fetch function initiated request") - case <-time.After(200 * time.Millisecond): - } - - // if there is another Request with active context, there should be a request, because the fetcher itself is not cancelled - fetcher.Request(0) - - select { - case <-requester.requestC: - case <-time.After(200 * time.Millisecond): - t.Fatalf("expected request") - } -} - -// TestOfferUsesSource tests Fetcher Offer behavior. -// In this case there should be 1 (and only one) request initiated from the source peer, and the -// source nodeid should appear in the peersToSkip map. -func TestFetcherOfferUsesSource(t *testing.T) { - requester := newMockRequester(100 * time.Millisecond) - addr := make([]byte, 32) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - fetcher := NewFetcher(ctx, addr, requester.doRequest, true) - - peersToSkip := &sync.Map{} - - // start the fetcher - go fetcher.run(peersToSkip) - - // call the Offer function with the source peer - fetcher.Offer(&sourcePeerID) - - // fetcher should not initiate request - select { - case <-requester.requestC: - t.Fatalf("fetcher initiated request") - case <-time.After(200 * time.Millisecond): - } - - // call Request after the Offer - fetcher.Request(0) - - // there should be exactly 1 request coming from fetcher - var request *Request - select { - case request = <-requester.requestC: - if *request.Source != sourcePeerID { - t.Fatalf("Expected source id %v got %v", sourcePeerID, request.Source) - } - case <-time.After(200 * time.Millisecond): - t.Fatalf("fetcher did not initiate request") - } - - select { - case <-requester.requestC: - t.Fatalf("Fetcher number of requests expected 1 got 2") - case <-time.After(200 * time.Millisecond): - } - - // source peer should be added to peersToSkip eventually - time.Sleep(100 * time.Millisecond) - if _, ok := request.peersToSkip.Load(sourcePeerID.String()); !ok { - t.Fatalf("SourcePeerId not added to peersToSkip") - } -} - -func TestFetcherOfferAfterRequestUsesSourceFromContext(t *testing.T) { - requester := newMockRequester(100 * time.Millisecond) - addr := make([]byte, 32) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - fetcher := NewFetcher(ctx, addr, requester.doRequest, true) - - peersToSkip := &sync.Map{} - - // start the fetcher - go fetcher.run(peersToSkip) - - // call Request first - fetcher.Request(0) - - // there should be a request coming from fetcher - var request *Request - select { - case request = <-requester.requestC: - if request.Source != nil { - t.Fatalf("Incorrect source peer id, expected nil got %v", request.Source) - } - case <-time.After(200 * time.Millisecond): - t.Fatalf("fetcher did not initiate request") - } - - // after the Request call Offer - fetcher.Offer(&sourcePeerID) - - // there should be a request coming from fetcher - select { - case request = <-requester.requestC: - if *request.Source != sourcePeerID { - t.Fatalf("Incorrect source peer id, expected %v got %v", sourcePeerID, request.Source) - } - case <-time.After(200 * time.Millisecond): - t.Fatalf("fetcher did not initiate request") - } - - // source peer should be added to peersToSkip eventually - time.Sleep(100 * time.Millisecond) - if _, ok := request.peersToSkip.Load(sourcePeerID.String()); !ok { - t.Fatalf("SourcePeerId not added to peersToSkip") - } -} - -// TestFetcherRetryOnTimeout tests that fetch retries after searchTimeOut has passed -func TestFetcherRetryOnTimeout(t *testing.T) { - requester := newMockRequester() - addr := make([]byte, 32) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - fetcher := NewFetcher(ctx, addr, requester.doRequest, true) - // set searchTimeOut to low value so the test is quicker - fetcher.searchTimeout = 250 * time.Millisecond - - peersToSkip := &sync.Map{} - - // start the fetcher - go fetcher.run(peersToSkip) - - // call the fetch function with an active context - fetcher.Request(0) - - // after 100ms the first request should be initiated - time.Sleep(100 * time.Millisecond) - - select { - case <-requester.requestC: - default: - t.Fatalf("fetch did not initiate request") - } - - // after another 100ms no new request should be initiated, because search timeout is 250ms - time.Sleep(100 * time.Millisecond) - - select { - case <-requester.requestC: - t.Fatalf("unexpected request from fetcher") - default: - } - - // after another 300ms search timeout is over, there should be a new request - time.Sleep(300 * time.Millisecond) - - select { - case <-requester.requestC: - default: - t.Fatalf("fetch did not retry request") - } -} - -// TestFetcherFactory creates a FetcherFactory and checks if the factory really creates and starts -// a Fetcher when it return a fetch function. We test the fetching functionality just by checking if -// a request is initiated when the fetch function is called -func TestFetcherFactory(t *testing.T) { - requester := newMockRequester(100 * time.Millisecond) - addr := make([]byte, 32) - fetcherFactory := NewFetcherFactory(requester.doRequest, false) - - peersToSkip := &sync.Map{} - - fetcher := fetcherFactory.New(context.Background(), addr, peersToSkip) - - fetcher.Request(0) - - // check if the created fetchFunction really starts a fetcher and initiates a request - select { - case <-requester.requestC: - case <-time.After(200 * time.Millisecond): - t.Fatalf("fetch timeout") - } - -} - -func TestFetcherRequestQuitRetriesRequest(t *testing.T) { - requester := newMockRequester() - addr := make([]byte, 32) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - fetcher := NewFetcher(ctx, addr, requester.doRequest, true) - - // make sure the searchTimeout is long so it is sure the request is not - // retried because of timeout - fetcher.searchTimeout = 10 * time.Second - - peersToSkip := &sync.Map{} - - go fetcher.run(peersToSkip) - - fetcher.Request(0) - - select { - case <-requester.requestC: - case <-time.After(200 * time.Millisecond): - t.Fatalf("request is not initiated") - } - - close(requester.quitC) - - select { - case <-requester.requestC: - case <-time.After(200 * time.Millisecond): - t.Fatalf("request is not initiated after failed request") - } -} - -// TestRequestSkipPeer checks if PeerSkip function will skip provided peer -// and not skip unknown one. -func TestRequestSkipPeer(t *testing.T) { - addr := make([]byte, 32) - peers := []enode.ID{ - enode.HexID("3431c3939e1ee2a6345e976a8234f9870152d64879f30bc272a074f6859e75e8"), - enode.HexID("99d8594b52298567d2ca3f4c441a5ba0140ee9245e26460d01102a52773c73b9"), - } - - peersToSkip := new(sync.Map) - peersToSkip.Store(peers[0].String(), time.Now()) - r := NewRequest(addr, false, peersToSkip) - - if !r.SkipPeer(peers[0].String()) { - t.Errorf("peer not skipped") - } - - if r.SkipPeer(peers[1].String()) { - t.Errorf("peer skipped") - } -} - -// TestRequestSkipPeerExpired checks if a peer to skip is not skipped -// after RequestTimeout has passed. -func TestRequestSkipPeerExpired(t *testing.T) { - addr := make([]byte, 32) - peer := enode.HexID("3431c3939e1ee2a6345e976a8234f9870152d64879f30bc272a074f6859e75e8") - - // set RequestTimeout to a low value and reset it after the test - defer func(t time.Duration) { RequestTimeout = t }(RequestTimeout) - RequestTimeout = 250 * time.Millisecond - - peersToSkip := new(sync.Map) - peersToSkip.Store(peer.String(), time.Now()) - r := NewRequest(addr, false, peersToSkip) - - if !r.SkipPeer(peer.String()) { - t.Errorf("peer not skipped") - } - - time.Sleep(500 * time.Millisecond) - - if r.SkipPeer(peer.String()) { - t.Errorf("peer skipped") - } -} - -// TestRequestSkipPeerPermanent checks if a peer to skip is not skipped -// after RequestTimeout is not skipped if it is set for a permanent skipping -// by value to peersToSkip map is not time.Duration. -func TestRequestSkipPeerPermanent(t *testing.T) { - addr := make([]byte, 32) - peer := enode.HexID("3431c3939e1ee2a6345e976a8234f9870152d64879f30bc272a074f6859e75e8") - - // set RequestTimeout to a low value and reset it after the test - defer func(t time.Duration) { RequestTimeout = t }(RequestTimeout) - RequestTimeout = 250 * time.Millisecond - - peersToSkip := new(sync.Map) - peersToSkip.Store(peer.String(), true) - r := NewRequest(addr, false, peersToSkip) - - if !r.SkipPeer(peer.String()) { - t.Errorf("peer not skipped") - } - - time.Sleep(500 * time.Millisecond) - - if !r.SkipPeer(peer.String()) { - t.Errorf("peer not skipped") - } -} - -func TestFetcherMaxHopCount(t *testing.T) { - requester := newMockRequester() - addr := make([]byte, 32) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - fetcher := NewFetcher(ctx, addr, requester.doRequest, true) - - peersToSkip := &sync.Map{} - - go fetcher.run(peersToSkip) - - // if hopCount is already at max no request should be initiated - select { - case <-requester.requestC: - t.Fatalf("cancelled fetcher initiated request") - case <-time.After(200 * time.Millisecond): - } -} diff --git a/swarm/network/hive.go b/swarm/network/hive.go deleted file mode 100644 index 3aa9188098a8..000000000000 --- a/swarm/network/hive.go +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package network - -import ( - "fmt" - "sync" - "time" - - "github.com/ubiq/go-ubiq/common/hexutil" - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/state" -) - -/* -Hive is the logistic manager of the swarm - -When the hive is started, a forever loop is launched that -asks the kademlia nodetable -to suggest peers to bootstrap connectivity -*/ - -// HiveParams holds the config options to hive -type HiveParams struct { - Discovery bool // if want discovery of not - PeersBroadcastSetSize uint8 // how many peers to use when relaying - MaxPeersPerRequest uint8 // max size for peer address batches - KeepAliveInterval time.Duration -} - -// NewHiveParams returns hive config with only the -func NewHiveParams() *HiveParams { - return &HiveParams{ - Discovery: true, - PeersBroadcastSetSize: 3, - MaxPeersPerRequest: 5, - KeepAliveInterval: 500 * time.Millisecond, - } -} - -// Hive manages network connections of the swarm node -type Hive struct { - *HiveParams // settings - *Kademlia // the overlay connectiviy driver - Store state.Store // storage interface to save peers across sessions - addPeer func(*enode.Node) // server callback to connect to a peer - // bookkeeping - lock sync.Mutex - peers map[enode.ID]*BzzPeer - ticker *time.Ticker -} - -// NewHive constructs a new hive -// HiveParams: config parameters -// Kademlia: connectivity driver using a network topology -// StateStore: to save peers across sessions -func NewHive(params *HiveParams, kad *Kademlia, store state.Store) *Hive { - return &Hive{ - HiveParams: params, - Kademlia: kad, - Store: store, - peers: make(map[enode.ID]*BzzPeer), - } -} - -// Start stars the hive, receives p2p.Server only at startup -// server is used to connect to a peer based on its NodeID or enode URL -// these are called on the p2p.Server which runs on the node -func (h *Hive) Start(server *p2p.Server) error { - log.Info("Starting hive", "baseaddr", fmt.Sprintf("%x", h.BaseAddr()[:4])) - // if state store is specified, load peers to prepopulate the overlay address book - if h.Store != nil { - log.Info("Detected an existing store. trying to load peers") - if err := h.loadPeers(); err != nil { - log.Error(fmt.Sprintf("%08x hive encoutered an error trying to load peers", h.BaseAddr()[:4])) - return err - } - } - // assigns the p2p.Server#AddPeer function to connect to peers - h.addPeer = server.AddPeer - // ticker to keep the hive alive - h.ticker = time.NewTicker(h.KeepAliveInterval) - // this loop is doing bootstrapping and maintains a healthy table - go h.connect() - return nil -} - -// Stop terminates the updateloop and saves the peers -func (h *Hive) Stop() error { - log.Info(fmt.Sprintf("%08x hive stopping, saving peers", h.BaseAddr()[:4])) - h.ticker.Stop() - if h.Store != nil { - if err := h.savePeers(); err != nil { - return fmt.Errorf("could not save peers to persistence store: %v", err) - } - if err := h.Store.Close(); err != nil { - return fmt.Errorf("could not close file handle to persistence store: %v", err) - } - } - log.Info(fmt.Sprintf("%08x hive stopped, dropping peers", h.BaseAddr()[:4])) - h.EachConn(nil, 255, func(p *Peer, _ int) bool { - log.Info(fmt.Sprintf("%08x dropping peer %08x", h.BaseAddr()[:4], p.Address()[:4])) - p.Drop(nil) - return true - }) - - log.Info(fmt.Sprintf("%08x all peers dropped", h.BaseAddr()[:4])) - return nil -} - -// connect is a forever loop -// at each iteration, ask the overlay driver to suggest the most preferred peer to connect to -// as well as advertises saturation depth if needed -func (h *Hive) connect() { - for range h.ticker.C { - - addr, depth, changed := h.SuggestPeer() - if h.Discovery && changed { - NotifyDepth(uint8(depth), h.Kademlia) - } - if addr == nil { - continue - } - - log.Trace(fmt.Sprintf("%08x hive connect() suggested %08x", h.BaseAddr()[:4], addr.Address()[:4])) - under, err := enode.ParseV4(string(addr.Under())) - if err != nil { - log.Warn(fmt.Sprintf("%08x unable to connect to bee %08x: invalid node URL: %v", h.BaseAddr()[:4], addr.Address()[:4], err)) - continue - } - log.Trace(fmt.Sprintf("%08x attempt to connect to bee %08x", h.BaseAddr()[:4], addr.Address()[:4])) - h.addPeer(under) - } -} - -// Run protocol run function -func (h *Hive) Run(p *BzzPeer) error { - h.trackPeer(p) - defer h.untrackPeer(p) - - dp := NewPeer(p, h.Kademlia) - depth, changed := h.On(dp) - // if we want discovery, advertise change of depth - if h.Discovery { - if changed { - // if depth changed, send to all peers - NotifyDepth(depth, h.Kademlia) - } else { - // otherwise just send depth to new peer - dp.NotifyDepth(depth) - } - NotifyPeer(p.BzzAddr, h.Kademlia) - } - defer h.Off(dp) - return dp.Run(dp.HandleMsg) -} - -func (h *Hive) trackPeer(p *BzzPeer) { - h.lock.Lock() - h.peers[p.ID()] = p - h.lock.Unlock() -} - -func (h *Hive) untrackPeer(p *BzzPeer) { - h.lock.Lock() - delete(h.peers, p.ID()) - h.lock.Unlock() -} - -// NodeInfo function is used by the p2p.server RPC interface to display -// protocol specific node information -func (h *Hive) NodeInfo() interface{} { - return h.String() -} - -// PeerInfo function is used by the p2p.server RPC interface to display -// protocol specific information any connected peer referred to by their NodeID -func (h *Hive) PeerInfo(id enode.ID) interface{} { - h.lock.Lock() - p := h.peers[id] - h.lock.Unlock() - - if p == nil { - return nil - } - addr := NewAddr(p.Node()) - return struct { - OAddr hexutil.Bytes - UAddr hexutil.Bytes - }{ - OAddr: addr.OAddr, - UAddr: addr.UAddr, - } -} - -// loadPeers, savePeer implement persistence callback/ -func (h *Hive) loadPeers() error { - var as []*BzzAddr - err := h.Store.Get("peers", &as) - if err != nil { - if err == state.ErrNotFound { - log.Info(fmt.Sprintf("hive %08x: no persisted peers found", h.BaseAddr()[:4])) - return nil - } - return err - } - log.Info(fmt.Sprintf("hive %08x: peers loaded", h.BaseAddr()[:4])) - - return h.Register(as...) -} - -// savePeers, savePeer implement persistence callback/ -func (h *Hive) savePeers() error { - var peers []*BzzAddr - h.Kademlia.EachAddr(nil, 256, func(pa *BzzAddr, i int) bool { - if pa == nil { - log.Warn(fmt.Sprintf("empty addr: %v", i)) - return true - } - log.Trace("saving peer", "peer", pa) - peers = append(peers, pa) - return true - }) - if err := h.Store.Put("peers", peers); err != nil { - return fmt.Errorf("could not save peers: %v", err) - } - return nil -} diff --git a/swarm/network/hive_test.go b/swarm/network/hive_test.go deleted file mode 100644 index 809e30c84bb5..000000000000 --- a/swarm/network/hive_test.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package network - -import ( - "io/ioutil" - "os" - "testing" - "time" - - p2ptest "github.com/ubiq/go-ubiq/p2p/testing" - "github.com/ubiq/go-ubiq/swarm/state" -) - -func newHiveTester(t *testing.T, params *HiveParams, n int, store state.Store) (*bzzTester, *Hive) { - // setup - addr := RandomAddr() // tested peers peer address - to := NewKademlia(addr.OAddr, NewKadParams()) - pp := NewHive(params, to, store) // hive - - return newBzzBaseTester(t, n, addr, DiscoverySpec, pp.Run), pp -} - -// TestRegisterAndConnect verifies that the protocol runs successfully -// and that the peer connection exists afterwards -func TestRegisterAndConnect(t *testing.T) { - params := NewHiveParams() - s, pp := newHiveTester(t, params, 1, nil) - - node := s.Nodes[0] - raddr := NewAddr(node) - pp.Register(raddr) - - // start the hive - err := pp.Start(s.Server) - if err != nil { - t.Fatal(err) - } - defer pp.Stop() - - // both hive connect and disconect check have time delays - // therefore we need to verify that peer is connected - // so that we are sure that the disconnect timeout doesn't complete - // before the hive connect method is run at least once - timeout := time.After(time.Second) - for { - select { - case <-timeout: - t.Fatalf("expected connection") - default: - } - i := 0 - pp.Kademlia.EachConn(nil, 256, func(addr *Peer, po int) bool { - i++ - return true - }) - if i > 0 { - break - } - time.Sleep(time.Millisecond) - } - - // check that the connection actually exists - // the timeout error means no disconnection events - // were received within the a certain timeout - err = s.TestDisconnected(&p2ptest.Disconnect{ - Peer: s.Nodes[0].ID(), - Error: nil, - }) - - if err == nil || err.Error() != "timed out waiting for peers to disconnect" { - t.Fatalf("expected no disconnection event") - } -} - -// TestHiveStatePersistance creates a protocol simulation with n peers for a node -// After protocols complete, the node is shut down and the state is stored. -// Another simulation is created, where 0 nodes are created, but where the stored state is passed -// The test succeeds if all the peers from the stored state are known after the protocols of the -// second simulation have completed -// -// Actual connectivity is not in scope for this test, as the peers loaded from state are not known to -// the simulation; the test only verifies that the peers are known to the node -func TestHiveStatePersistance(t *testing.T) { - - dir, err := ioutil.TempDir("", "hive_test_store") - if err != nil { - panic(err) - } - defer os.RemoveAll(dir) - - store, err := state.NewDBStore(dir) //start the hive with an empty dbstore - if err != nil { - t.Fatal(err) - } - - params := NewHiveParams() - s, pp := newHiveTester(t, params, 5, store) - - peers := make(map[string]bool) - for _, node := range s.Nodes { - raddr := NewAddr(node) - pp.Register(raddr) - peers[raddr.String()] = true - } - - // start and stop the hive - // the known peers should be saved upon stopping - err = pp.Start(s.Server) - if err != nil { - t.Fatal(err) - } - pp.Stop() - store.Close() - - // start the hive with an empty dbstore - persistedStore, err := state.NewDBStore(dir) - if err != nil { - t.Fatal(err) - } - - s1, pp := newHiveTester(t, params, 0, persistedStore) - - // start the hive and check that we know of all expected peers - pp.Start(s1.Server) - i := 0 - pp.Kademlia.EachAddr(nil, 256, func(addr *BzzAddr, po int) bool { - delete(peers, addr.String()) - i++ - return true - }) - // TODO remove this line when verified that test passes - time.Sleep(time.Second) - if i != 5 { - t.Fatalf("invalid number of entries: got %v, want %v", i, 5) - } - if len(peers) != 0 { - t.Fatalf("%d peers left over: %v", len(peers), peers) - } - -} diff --git a/swarm/network/kademlia.go b/swarm/network/kademlia.go deleted file mode 100644 index 5422fa405eda..000000000000 --- a/swarm/network/kademlia.go +++ /dev/null @@ -1,870 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package network - -import ( - "bytes" - "fmt" - "math/rand" - "strings" - "sync" - "time" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/pot" - sv "github.com/ubiq/go-ubiq/swarm/version" -) - -/* - -Taking the proximity order relative to a fix point x classifies the points in -the space (n byte long byte sequences) into bins. Items in each are at -most half as distant from x as items in the previous bin. Given a sample of -uniformly distributed items (a hash function over arbitrary sequence) the -proximity scale maps onto series of subsets with cardinalities on a negative -exponential scale. - -It also has the property that any two item belonging to the same bin are at -most half as distant from each other as they are from x. - -If we think of random sample of items in the bins as connections in a network of -interconnected nodes then relative proximity can serve as the basis for local -decisions for graph traversal where the task is to find a route between two -points. Since in every hop, the finite distance halves, there is -a guaranteed constant maximum limit on the number of hops needed to reach one -node from the other. -*/ - -var Pof = pot.DefaultPof(256) - -// KadParams holds the config params for Kademlia -type KadParams struct { - // adjustable parameters - MaxProxDisplay int // number of rows the table shows - NeighbourhoodSize int // nearest neighbour core minimum cardinality - MinBinSize int // minimum number of peers in a row - MaxBinSize int // maximum number of peers in a row before pruning - RetryInterval int64 // initial interval before a peer is first redialed - RetryExponent int // exponent to multiply retry intervals with - MaxRetries int // maximum number of redial attempts - // function to sanction or prevent suggesting a peer - Reachable func(*BzzAddr) bool `json:"-"` -} - -// NewKadParams returns a params struct with default values -func NewKadParams() *KadParams { - return &KadParams{ - MaxProxDisplay: 16, - NeighbourhoodSize: 2, - MinBinSize: 2, - MaxBinSize: 4, - RetryInterval: 4200000000, // 4.2 sec - MaxRetries: 42, - RetryExponent: 2, - } -} - -// Kademlia is a table of live peers and a db of known peers (node records) -type Kademlia struct { - lock sync.RWMutex - *KadParams // Kademlia configuration parameters - base []byte // immutable baseaddress of the table - addrs *pot.Pot // pots container for known peer addresses - conns *pot.Pot // pots container for live peer connections - depth uint8 // stores the last current depth of saturation - nDepth int // stores the last neighbourhood depth - nDepthC chan int // returned by DepthC function to signal neighbourhood depth change - addrCountC chan int // returned by AddrCountC function to signal peer count change -} - -// NewKademlia creates a Kademlia table for base address addr -// with parameters as in params -// if params is nil, it uses default values -func NewKademlia(addr []byte, params *KadParams) *Kademlia { - if params == nil { - params = NewKadParams() - } - return &Kademlia{ - base: addr, - KadParams: params, - addrs: pot.NewPot(nil, 0), - conns: pot.NewPot(nil, 0), - } -} - -// entry represents a Kademlia table entry (an extension of BzzAddr) -type entry struct { - *BzzAddr - conn *Peer - seenAt time.Time - retries int -} - -// newEntry creates a kademlia peer from a *Peer -func newEntry(p *BzzAddr) *entry { - return &entry{ - BzzAddr: p, - seenAt: time.Now(), - } -} - -// Label is a short tag for the entry for debug -func Label(e *entry) string { - return fmt.Sprintf("%s (%d)", e.Hex()[:4], e.retries) -} - -// Hex is the hexadecimal serialisation of the entry address -func (e *entry) Hex() string { - return fmt.Sprintf("%x", e.Address()) -} - -// Register enters each address as kademlia peer record into the -// database of known peer addresses -func (k *Kademlia) Register(peers ...*BzzAddr) error { - k.lock.Lock() - defer k.lock.Unlock() - var known, size int - for _, p := range peers { - // error if self received, peer should know better - // and should be punished for this - if bytes.Equal(p.Address(), k.base) { - return fmt.Errorf("add peers: %x is self", k.base) - } - var found bool - k.addrs, _, found, _ = pot.Swap(k.addrs, p, Pof, func(v pot.Val) pot.Val { - // if not found - if v == nil { - // insert new offline peer into conns - return newEntry(p) - } - // found among known peers, do nothing - return v - }) - if found { - known++ - } - size++ - } - // send new address count value only if there are new addresses - if k.addrCountC != nil && size-known > 0 { - k.addrCountC <- k.addrs.Size() - } - - k.sendNeighbourhoodDepthChange() - return nil -} - -// SuggestPeer returns an unconnected peer address as a peer suggestion for connection -func (k *Kademlia) SuggestPeer() (suggestedPeer *BzzAddr, saturationDepth int, changed bool) { - k.lock.Lock() - defer k.lock.Unlock() - radius := neighbourhoodRadiusForPot(k.conns, k.NeighbourhoodSize, k.base) - // collect undersaturated bins in ascending order of number of connected peers - // and from shallow to deep (ascending order of PO) - // insert them in a map of bin arrays, keyed with the number of connected peers - saturation := make(map[int][]int) - var lastPO int // the last non-empty PO bin in the iteration - saturationDepth = -1 // the deepest PO such that all shallower bins have >= k.MinBinSize peers - var pastDepth bool // whether po of iteration >= depth - k.conns.EachBin(k.base, Pof, 0, func(po, size int, f func(func(val pot.Val) bool) bool) bool { - // process skipped empty bins - for ; lastPO < po; lastPO++ { - // find the lowest unsaturated bin - if saturationDepth == -1 { - saturationDepth = lastPO - } - // if there is an empty bin, depth is surely passed - pastDepth = true - saturation[0] = append(saturation[0], lastPO) - } - lastPO = po + 1 - // past radius, depth is surely passed - if po >= radius { - pastDepth = true - } - // beyond depth the bin is treated as unsaturated even if size >= k.MinBinSize - // in order to achieve full connectivity to all neighbours - if pastDepth && size >= k.MinBinSize { - size = k.MinBinSize - 1 - } - // process non-empty unsaturated bins - if size < k.MinBinSize { - // find the lowest unsaturated bin - if saturationDepth == -1 { - saturationDepth = po - } - saturation[size] = append(saturation[size], po) - } - return true - }) - // to trigger peer requests for peers closer than closest connection, include - // all bins from nearest connection upto nearest address as unsaturated - var nearestAddrAt int - k.addrs.EachNeighbour(k.base, Pof, func(_ pot.Val, po int) bool { - nearestAddrAt = po - return false - }) - // including bins as size 0 has the effect that requesting connection - // is prioritised over non-empty shallower bins - for ; lastPO <= nearestAddrAt; lastPO++ { - saturation[0] = append(saturation[0], lastPO) - } - // all PO bins are saturated, ie., minsize >= k.MinBinSize, no peer suggested - if len(saturation) == 0 { - return nil, 0, false - } - // find the first callable peer in the address book - // starting from the bins with smallest size proceeding from shallow to deep - // for each bin (up until neighbourhood radius) we find callable candidate peers - for size := 0; size < k.MinBinSize && suggestedPeer == nil; size++ { - bins, ok := saturation[size] - if !ok { - // no bin with this size - continue - } - cur := 0 - curPO := bins[0] - k.addrs.EachBin(k.base, Pof, curPO, func(po, _ int, f func(func(pot.Val) bool) bool) bool { - curPO = bins[cur] - // find the next bin that has size size - if curPO == po { - cur++ - } else { - // skip bins that have no addresses - for ; cur < len(bins) && curPO < po; cur++ { - curPO = bins[cur] - } - if po < curPO { - cur-- - return true - } - // stop if there are no addresses - if curPO < po { - return false - } - } - // curPO found - // find a callable peer out of the addresses in the unsaturated bin - // stop if found - f(func(val pot.Val) bool { - e := val.(*entry) - if k.callable(e) { - suggestedPeer = e.BzzAddr - return false - } - return true - }) - return cur < len(bins) && suggestedPeer == nil - }) - } - - if uint8(saturationDepth) < k.depth { - k.depth = uint8(saturationDepth) - return suggestedPeer, saturationDepth, true - } - return suggestedPeer, 0, false -} - -// On inserts the peer as a kademlia peer into the live peers -func (k *Kademlia) On(p *Peer) (uint8, bool) { - k.lock.Lock() - defer k.lock.Unlock() - var ins bool - k.conns, _, _, _ = pot.Swap(k.conns, p, Pof, func(v pot.Val) pot.Val { - // if not found live - if v == nil { - ins = true - // insert new online peer into conns - return p - } - // found among live peers, do nothing - return v - }) - if ins && !p.BzzPeer.LightNode { - a := newEntry(p.BzzAddr) - a.conn = p - // insert new online peer into addrs - k.addrs, _, _, _ = pot.Swap(k.addrs, p, Pof, func(v pot.Val) pot.Val { - return a - }) - // send new address count value only if the peer is inserted - if k.addrCountC != nil { - k.addrCountC <- k.addrs.Size() - } - } - log.Trace(k.string()) - // calculate if depth of saturation changed - depth := uint8(k.saturation()) - var changed bool - if depth != k.depth { - changed = true - k.depth = depth - } - k.sendNeighbourhoodDepthChange() - return k.depth, changed -} - -// NeighbourhoodDepthC returns the channel that sends a new kademlia -// neighbourhood depth on each change. -// Not receiving from the returned channel will block On function -// when the neighbourhood depth is changed. -// TODO: Why is this exported, and if it should be; why can't we have more subscribers than one? -func (k *Kademlia) NeighbourhoodDepthC() <-chan int { - k.lock.Lock() - defer k.lock.Unlock() - if k.nDepthC == nil { - k.nDepthC = make(chan int) - } - return k.nDepthC -} - -// sendNeighbourhoodDepthChange sends new neighbourhood depth to k.nDepth channel -// if it is initialized. -func (k *Kademlia) sendNeighbourhoodDepthChange() { - // nDepthC is initialized when NeighbourhoodDepthC is called and returned by it. - // It provides signaling of neighbourhood depth change. - // This part of the code is sending new neighbourhood depth to nDepthC if that condition is met. - if k.nDepthC != nil { - nDepth := depthForPot(k.conns, k.NeighbourhoodSize, k.base) - if nDepth != k.nDepth { - k.nDepth = nDepth - k.nDepthC <- nDepth - } - } -} - -// AddrCountC returns the channel that sends a new -// address count value on each change. -// Not receiving from the returned channel will block Register function -// when address count value changes. -func (k *Kademlia) AddrCountC() <-chan int { - k.lock.Lock() - defer k.lock.Unlock() - - if k.addrCountC == nil { - k.addrCountC = make(chan int) - } - return k.addrCountC -} - -// Off removes a peer from among live peers -func (k *Kademlia) Off(p *Peer) { - k.lock.Lock() - defer k.lock.Unlock() - var del bool - if !p.BzzPeer.LightNode { - k.addrs, _, _, _ = pot.Swap(k.addrs, p, Pof, func(v pot.Val) pot.Val { - // v cannot be nil, must check otherwise we overwrite entry - if v == nil { - panic(fmt.Sprintf("connected peer not found %v", p)) - } - del = true - return newEntry(p.BzzAddr) - }) - } else { - del = true - } - - if del { - k.conns, _, _, _ = pot.Swap(k.conns, p, Pof, func(_ pot.Val) pot.Val { - // v cannot be nil, but no need to check - return nil - }) - // send new address count value only if the peer is deleted - if k.addrCountC != nil { - k.addrCountC <- k.addrs.Size() - } - k.sendNeighbourhoodDepthChange() - } -} - -// EachConn is an iterator with args (base, po, f) applies f to each live peer -// that has proximity order po or less as measured from the base -// if base is nil, kademlia base address is used -func (k *Kademlia) EachConn(base []byte, o int, f func(*Peer, int) bool) { - k.lock.RLock() - defer k.lock.RUnlock() - k.eachConn(base, o, f) -} - -func (k *Kademlia) eachConn(base []byte, o int, f func(*Peer, int) bool) { - if len(base) == 0 { - base = k.base - } - k.conns.EachNeighbour(base, Pof, func(val pot.Val, po int) bool { - if po > o { - return true - } - return f(val.(*Peer), po) - }) -} - -// EachAddr called with (base, po, f) is an iterator applying f to each known peer -// that has proximity order o or less as measured from the base -// if base is nil, kademlia base address is used -func (k *Kademlia) EachAddr(base []byte, o int, f func(*BzzAddr, int) bool) { - k.lock.RLock() - defer k.lock.RUnlock() - k.eachAddr(base, o, f) -} - -func (k *Kademlia) eachAddr(base []byte, o int, f func(*BzzAddr, int) bool) { - if len(base) == 0 { - base = k.base - } - k.addrs.EachNeighbour(base, Pof, func(val pot.Val, po int) bool { - if po > o { - return true - } - return f(val.(*entry).BzzAddr, po) - }) -} - -// NeighbourhoodDepth returns the depth for the pot, see depthForPot -func (k *Kademlia) NeighbourhoodDepth() (depth int) { - k.lock.RLock() - defer k.lock.RUnlock() - return depthForPot(k.conns, k.NeighbourhoodSize, k.base) -} - -// neighbourhoodRadiusForPot returns the neighbourhood radius of the kademlia -// neighbourhood radius encloses the nearest neighbour set with size >= neighbourhoodSize -// i.e., neighbourhood radius is the deepest PO such that all bins not shallower altogether -// contain at least neighbourhoodSize connected peers -// if there is altogether less than neighbourhoodSize peers connected, it returns 0 -// caller must hold the lock -func neighbourhoodRadiusForPot(p *pot.Pot, neighbourhoodSize int, pivotAddr []byte) (depth int) { - if p.Size() <= neighbourhoodSize { - return 0 - } - // total number of peers in iteration - var size int - f := func(v pot.Val, i int) bool { - // po == 256 means that addr is the pivot address(self) - if i == 256 { - return true - } - size++ - - // this means we have all nn-peers. - // depth is by default set to the bin of the farthest nn-peer - if size == neighbourhoodSize { - depth = i - return false - } - - return true - } - p.EachNeighbour(pivotAddr, Pof, f) - return depth -} - -// depthForPot returns the depth for the pot -// depth is the radius of the minimal extension of nearest neighbourhood that -// includes all empty PO bins. I.e., depth is the deepest PO such that -// - it is not deeper than neighbourhood radius -// - all bins shallower than depth are not empty -// caller must hold the lock -func depthForPot(p *pot.Pot, neighbourhoodSize int, pivotAddr []byte) (depth int) { - if p.Size() <= neighbourhoodSize { - return 0 - } - // determining the depth is a two-step process - // first we find the proximity bin of the shallowest of the neighbourhoodSize peers - // the numeric value of depth cannot be higher than this - maxDepth := neighbourhoodRadiusForPot(p, neighbourhoodSize, pivotAddr) - - // the second step is to test for empty bins in order from shallowest to deepest - // if an empty bin is found, this will be the actual depth - // we stop iterating if we hit the maxDepth determined in the first step - p.EachBin(pivotAddr, Pof, 0, func(po int, _ int, f func(func(pot.Val) bool) bool) bool { - if po == depth { - if maxDepth == depth { - return false - } - depth++ - return true - } - return false - }) - - return depth -} - -// callable decides if an address entry represents a callable peer -func (k *Kademlia) callable(e *entry) bool { - // not callable if peer is live or exceeded maxRetries - if e.conn != nil || e.retries > k.MaxRetries { - return false - } - // calculate the allowed number of retries based on time lapsed since last seen - timeAgo := int64(time.Since(e.seenAt)) - div := int64(k.RetryExponent) - div += (150000 - rand.Int63n(300000)) * div / 1000000 - var retries int - for delta := timeAgo; delta > k.RetryInterval; delta /= div { - retries++ - } - // this is never called concurrently, so safe to increment - // peer can be retried again - if retries < e.retries { - log.Trace(fmt.Sprintf("%08x: %v long time since last try (at %v) needed before retry %v, wait only warrants %v", k.BaseAddr()[:4], e, timeAgo, e.retries, retries)) - return false - } - // function to sanction or prevent suggesting a peer - if k.Reachable != nil && !k.Reachable(e.BzzAddr) { - log.Trace(fmt.Sprintf("%08x: peer %v is temporarily not callable", k.BaseAddr()[:4], e)) - return false - } - e.retries++ - log.Trace(fmt.Sprintf("%08x: peer %v is callable", k.BaseAddr()[:4], e)) - - return true -} - -// BaseAddr return the kademlia base address -func (k *Kademlia) BaseAddr() []byte { - return k.base -} - -// String returns kademlia table + kaddb table displayed with ascii -func (k *Kademlia) String() string { - k.lock.RLock() - defer k.lock.RUnlock() - return k.string() -} - -// string returns kademlia table + kaddb table displayed with ascii -// caller must hold the lock -func (k *Kademlia) string() string { - wsrow := " " - var rows []string - - rows = append(rows, "=========================================================================") - if len(sv.GitCommit) > 0 { - rows = append(rows, fmt.Sprintf("commit hash: %s", sv.GitCommit)) - } - rows = append(rows, fmt.Sprintf("%v KΛÐΞMLIΛ hive: queen's address: %x", time.Now().UTC().Format(time.UnixDate), k.BaseAddr()[:3])) - rows = append(rows, fmt.Sprintf("population: %d (%d), NeighbourhoodSize: %d, MinBinSize: %d, MaxBinSize: %d", k.conns.Size(), k.addrs.Size(), k.NeighbourhoodSize, k.MinBinSize, k.MaxBinSize)) - - liverows := make([]string, k.MaxProxDisplay) - peersrows := make([]string, k.MaxProxDisplay) - - depth := depthForPot(k.conns, k.NeighbourhoodSize, k.base) - rest := k.conns.Size() - k.conns.EachBin(k.base, Pof, 0, func(po, size int, f func(func(val pot.Val) bool) bool) bool { - var rowlen int - if po >= k.MaxProxDisplay { - po = k.MaxProxDisplay - 1 - } - row := []string{fmt.Sprintf("%2d", size)} - rest -= size - f(func(val pot.Val) bool { - e := val.(*Peer) - row = append(row, fmt.Sprintf("%x", e.Address()[:2])) - rowlen++ - return rowlen < 4 - }) - r := strings.Join(row, " ") - r = r + wsrow - liverows[po] = r[:31] - return true - }) - - k.addrs.EachBin(k.base, Pof, 0, func(po, size int, f func(func(val pot.Val) bool) bool) bool { - var rowlen int - if po >= k.MaxProxDisplay { - po = k.MaxProxDisplay - 1 - } - if size < 0 { - panic("wtf") - } - row := []string{fmt.Sprintf("%2d", size)} - // we are displaying live peers too - f(func(val pot.Val) bool { - e := val.(*entry) - row = append(row, Label(e)) - rowlen++ - return rowlen < 4 - }) - peersrows[po] = strings.Join(row, " ") - return true - }) - - for i := 0; i < k.MaxProxDisplay; i++ { - if i == depth { - rows = append(rows, fmt.Sprintf("============ DEPTH: %d ==========================================", i)) - } - left := liverows[i] - right := peersrows[i] - if len(left) == 0 { - left = " 0 " - } - if len(right) == 0 { - right = " 0" - } - rows = append(rows, fmt.Sprintf("%03d %v | %v", i, left, right)) - } - rows = append(rows, "=========================================================================") - return "\n" + strings.Join(rows, "\n") -} - -// PeerPot keeps info about expected nearest neighbours -// used for testing only -// TODO move to separate testing tools file -type PeerPot struct { - NNSet [][]byte - PeersPerBin []int -} - -// NewPeerPotMap creates a map of pot record of *BzzAddr with keys -// as hexadecimal representations of the address. -// the NeighbourhoodSize of the passed kademlia is used -// used for testing only -// TODO move to separate testing tools file -func NewPeerPotMap(neighbourhoodSize int, addrs [][]byte) map[string]*PeerPot { - - // create a table of all nodes for health check - np := pot.NewPot(nil, 0) - for _, addr := range addrs { - np, _, _ = pot.Add(np, addr, Pof) - } - ppmap := make(map[string]*PeerPot) - - // generate an allknowing source of truth for connections - // for every kademlia passed - for i, a := range addrs { - - // actual kademlia depth - depth := depthForPot(np, neighbourhoodSize, a) - - // all nn-peers - var nns [][]byte - peersPerBin := make([]int, depth) - - // iterate through the neighbours, going from the deepest to the shallowest - np.EachNeighbour(a, Pof, func(val pot.Val, po int) bool { - addr := val.([]byte) - // po == 256 means that addr is the pivot address(self) - // we do not include self in the map - if po == 256 { - return true - } - // append any neighbors found - // a neighbor is any peer in or deeper than the depth - if po >= depth { - nns = append(nns, addr) - } else { - // for peers < depth, we just count the number in each bin - // the bin is the index of the slice - peersPerBin[po]++ - } - return true - }) - - log.Trace(fmt.Sprintf("%x PeerPotMap NNS: %s, peersPerBin", addrs[i][:4], LogAddrs(nns))) - ppmap[common.Bytes2Hex(a)] = &PeerPot{ - NNSet: nns, - PeersPerBin: peersPerBin, - } - } - return ppmap -} - -// saturation returns the smallest po value in which the node has less than MinBinSize peers -// if the iterator reaches neighbourhood radius, then the last bin + 1 is returned -func (k *Kademlia) saturation() int { - prev := -1 - radius := neighbourhoodRadiusForPot(k.conns, k.NeighbourhoodSize, k.base) - k.conns.EachBin(k.base, Pof, 0, func(po, size int, f func(func(val pot.Val) bool) bool) bool { - prev++ - if po >= radius { - return false - } - return prev == po && size >= k.MinBinSize - }) - if prev < 0 { - return 0 - } - return prev -} - -// isSaturated returns true if the kademlia is considered saturated, or false if not. -// It checks this by checking an array of ints called unsaturatedBins; each item in that array corresponds -// to the bin which is unsaturated (number of connections < k.MinBinSize). -// The bin is considered unsaturated only if there are actual peers in that PeerPot's bin (peersPerBin) -// (if there is no peer for a given bin, then no connection could ever be established; -// in a God's view this is relevant as no more peers will ever appear on that bin) -func (k *Kademlia) isSaturated(peersPerBin []int, depth int) bool { - // depth could be calculated from k but as this is called from `GetHealthInfo()`, - // the depth has already been calculated so we can require it as a parameter - - // early check for depth - if depth != len(peersPerBin) { - return false - } - unsaturatedBins := make([]int, 0) - k.conns.EachBin(k.base, Pof, 0, func(po, size int, f func(func(val pot.Val) bool) bool) bool { - - if po >= depth { - return false - } - log.Trace("peers per bin", "peersPerBin[po]", peersPerBin[po], "po", po) - // if there are actually peers in the PeerPot who can fulfill k.MinBinSize - if size < k.MinBinSize && size < peersPerBin[po] { - log.Trace("connections for po", "po", po, "size", size) - unsaturatedBins = append(unsaturatedBins, po) - } - return true - }) - - log.Trace("list of unsaturated bins", "unsaturatedBins", unsaturatedBins) - return len(unsaturatedBins) == 0 -} - -// knowNeighbours tests if all neighbours in the peerpot -// are found among the peers known to the kademlia -// It is used in Healthy function for testing only -// TODO move to separate testing tools file -func (k *Kademlia) knowNeighbours(addrs [][]byte) (got bool, n int, missing [][]byte) { - pm := make(map[string]bool) - depth := depthForPot(k.conns, k.NeighbourhoodSize, k.base) - // create a map with all peers at depth and deeper known in the kademlia - k.eachAddr(nil, 255, func(p *BzzAddr, po int) bool { - // in order deepest to shallowest compared to the kademlia base address - // all bins (except self) are included (0 <= bin <= 255) - if po < depth { - return false - } - pk := common.Bytes2Hex(p.Address()) - pm[pk] = true - return true - }) - - // iterate through nearest neighbors in the peerpot map - // if we can't find the neighbor in the map we created above - // then we don't know all our neighbors - // (which sadly is all too common in modern society) - var gots int - var culprits [][]byte - for _, p := range addrs { - pk := common.Bytes2Hex(p) - if pm[pk] { - gots++ - } else { - log.Trace(fmt.Sprintf("%08x: known nearest neighbour %s not found", k.base, pk)) - culprits = append(culprits, p) - } - } - return gots == len(addrs), gots, culprits -} - -// connectedNeighbours tests if all neighbours in the peerpot -// are currently connected in the kademlia -// It is used in Healthy function for testing only -func (k *Kademlia) connectedNeighbours(peers [][]byte) (got bool, n int, missing [][]byte) { - pm := make(map[string]bool) - - // create a map with all peers at depth and deeper that are connected in the kademlia - // in order deepest to shallowest compared to the kademlia base address - // all bins (except self) are included (0 <= bin <= 255) - depth := depthForPot(k.conns, k.NeighbourhoodSize, k.base) - k.eachConn(nil, 255, func(p *Peer, po int) bool { - if po < depth { - return false - } - pk := common.Bytes2Hex(p.Address()) - pm[pk] = true - return true - }) - - // iterate through nearest neighbors in the peerpot map - // if we can't find the neighbor in the map we created above - // then we don't know all our neighbors - var gots int - var culprits [][]byte - for _, p := range peers { - pk := common.Bytes2Hex(p) - if pm[pk] { - gots++ - } else { - log.Trace(fmt.Sprintf("%08x: ExpNN: %s not found", k.base, pk)) - culprits = append(culprits, p) - } - } - return gots == len(peers), gots, culprits -} - -// Health state of the Kademlia -// used for testing only -type Health struct { - KnowNN bool // whether node knows all its neighbours - CountKnowNN int // amount of neighbors known - MissingKnowNN [][]byte // which neighbours we should have known but we don't - ConnectNN bool // whether node is connected to all its neighbours - CountConnectNN int // amount of neighbours connected to - MissingConnectNN [][]byte // which neighbours we should have been connected to but we're not - // Saturated: if in all bins < depth number of connections >= MinBinsize or, - // if number of connections < MinBinSize, to the number of available peers in that bin - Saturated bool - Hive string -} - -// GetHealthInfo reports the health state of the kademlia connectivity -// -// The PeerPot argument provides an all-knowing view of the network -// The resulting Health object is a result of comparisons between -// what is the actual composition of the kademlia in question (the receiver), and -// what SHOULD it have been when we take all we know about the network into consideration. -// -// used for testing only -func (k *Kademlia) GetHealthInfo(pp *PeerPot) *Health { - k.lock.RLock() - defer k.lock.RUnlock() - if len(pp.NNSet) < k.NeighbourhoodSize { - log.Warn("peerpot NNSet < NeighbourhoodSize") - } - gotnn, countgotnn, culpritsgotnn := k.connectedNeighbours(pp.NNSet) - knownn, countknownn, culpritsknownn := k.knowNeighbours(pp.NNSet) - depth := depthForPot(k.conns, k.NeighbourhoodSize, k.base) - - // check saturation - saturated := k.isSaturated(pp.PeersPerBin, depth) - - log.Trace(fmt.Sprintf("%08x: healthy: knowNNs: %v, gotNNs: %v, saturated: %v\n", k.base, knownn, gotnn, saturated)) - return &Health{ - KnowNN: knownn, - CountKnowNN: countknownn, - MissingKnowNN: culpritsknownn, - ConnectNN: gotnn, - CountConnectNN: countgotnn, - MissingConnectNN: culpritsgotnn, - Saturated: saturated, - Hive: k.string(), - } -} - -// Healthy return the strict interpretation of `Healthy` given a `Health` struct -// definition of strict health: all conditions must be true: -// - we at least know one peer -// - we know all neighbors -// - we are connected to all known neighbors -// - it is saturated -func (h *Health) Healthy() bool { - return h.KnowNN && h.ConnectNN && h.CountKnowNN > 0 && h.Saturated -} diff --git a/swarm/network/kademlia_test.go b/swarm/network/kademlia_test.go deleted file mode 100644 index c94ef3bbeb7f..000000000000 --- a/swarm/network/kademlia_test.go +++ /dev/null @@ -1,562 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package network - -import ( - "fmt" - "os" - "testing" - "time" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/protocols" - "github.com/ubiq/go-ubiq/swarm/pot" -) - -func init() { - h := log.LvlFilterHandler(log.LvlWarn, log.StreamHandler(os.Stderr, log.TerminalFormat(true))) - log.Root().SetHandler(h) -} - -func testKadPeerAddr(s string) *BzzAddr { - a := pot.NewAddressFromString(s) - return &BzzAddr{OAddr: a, UAddr: a} -} - -func newTestKademliaParams() *KadParams { - params := NewKadParams() - params.MinBinSize = 2 - params.NeighbourhoodSize = 2 - return params -} - -type testKademlia struct { - *Kademlia - t *testing.T -} - -func newTestKademlia(t *testing.T, b string) *testKademlia { - base := pot.NewAddressFromString(b) - return &testKademlia{ - Kademlia: NewKademlia(base, newTestKademliaParams()), - t: t, - } -} - -func (tk *testKademlia) newTestKadPeer(s string, lightNode bool) *Peer { - return NewPeer(&BzzPeer{BzzAddr: testKadPeerAddr(s), LightNode: lightNode}, tk.Kademlia) -} - -func (tk *testKademlia) On(ons ...string) { - for _, s := range ons { - tk.Kademlia.On(tk.newTestKadPeer(s, false)) - } -} - -func (tk *testKademlia) Off(offs ...string) { - for _, s := range offs { - tk.Kademlia.Off(tk.newTestKadPeer(s, false)) - } -} - -func (tk *testKademlia) Register(regs ...string) { - var as []*BzzAddr - for _, s := range regs { - as = append(as, testKadPeerAddr(s)) - } - err := tk.Kademlia.Register(as...) - if err != nil { - panic(err.Error()) - } -} - -// tests the validity of neighborhood depth calculations -// -// in particular, it tests that if there are one or more consecutive -// empty bins above the farthest "nearest neighbor-peer" then -// the depth should be set at the farthest of those empty bins -// -// TODO: Make test adapt to change in NeighbourhoodSize -func TestNeighbourhoodDepth(t *testing.T) { - baseAddressBytes := RandomAddr().OAddr - kad := NewKademlia(baseAddressBytes, NewKadParams()) - - baseAddress := pot.NewAddressFromBytes(baseAddressBytes) - - // generate the peers - var peers []*Peer - for i := 0; i < 7; i++ { - addr := pot.RandomAddressAt(baseAddress, i) - peers = append(peers, newTestDiscoveryPeer(addr, kad)) - } - var sevenPeers []*Peer - for i := 0; i < 2; i++ { - addr := pot.RandomAddressAt(baseAddress, 7) - sevenPeers = append(sevenPeers, newTestDiscoveryPeer(addr, kad)) - } - - testNum := 0 - // first try with empty kademlia - depth := kad.NeighbourhoodDepth() - if depth != 0 { - t.Fatalf("%d expected depth 0, was %d", testNum, depth) - } - testNum++ - - // add one peer on 7 - kad.On(sevenPeers[0]) - depth = kad.NeighbourhoodDepth() - if depth != 0 { - t.Fatalf("%d expected depth 0, was %d", testNum, depth) - } - testNum++ - - // add a second on 7 - kad.On(sevenPeers[1]) - depth = kad.NeighbourhoodDepth() - if depth != 0 { - t.Fatalf("%d expected depth 0, was %d", testNum, depth) - } - testNum++ - - // add from 0 to 6 - for i, p := range peers { - kad.On(p) - depth = kad.NeighbourhoodDepth() - if depth != i+1 { - t.Fatalf("%d.%d expected depth %d, was %d", i+1, testNum, i, depth) - } - } - testNum++ - - kad.Off(sevenPeers[1]) - depth = kad.NeighbourhoodDepth() - if depth != 6 { - t.Fatalf("%d expected depth 6, was %d", testNum, depth) - } - testNum++ - - kad.Off(peers[4]) - depth = kad.NeighbourhoodDepth() - if depth != 4 { - t.Fatalf("%d expected depth 4, was %d", testNum, depth) - } - testNum++ - - kad.Off(peers[3]) - depth = kad.NeighbourhoodDepth() - if depth != 3 { - t.Fatalf("%d expected depth 3, was %d", testNum, depth) - } - testNum++ -} - -// TestHighMinBinSize tests that the saturation function also works -// if MinBinSize is > 2, the connection count is < k.MinBinSize -// and there are more peers available than connected -func TestHighMinBinSize(t *testing.T) { - // a function to test for different MinBinSize values - testKad := func(minBinSize int) { - // create a test kademlia - tk := newTestKademlia(t, "11111111") - // set its MinBinSize to desired value - tk.KadParams.MinBinSize = minBinSize - - // add a couple of peers (so we have NN and depth) - tk.On("00000000") // bin 0 - tk.On("11100000") // bin 3 - tk.On("11110000") // bin 4 - - first := "10000000" // add a first peer at bin 1 - tk.Register(first) // register it - // we now have one registered peer at bin 1; - // iterate and connect one peer at each iteration; - // should be unhealthy until at minBinSize - 1 - // we connect the unconnected but registered peer - for i := 1; i < minBinSize; i++ { - peer := fmt.Sprintf("1000%b", 8|i) - tk.On(peer) - if i == minBinSize-1 { - tk.On(first) - tk.checkHealth(true) - return - } - tk.checkHealth(false) - } - } - // test MinBinSizes of 3 to 5 - testMinBinSizes := []int{3, 4, 5} - for _, k := range testMinBinSizes { - testKad(k) - } -} - -// TestHealthStrict tests the simplest definition of health -// Which means whether we are connected to all neighbors we know of -func TestHealthStrict(t *testing.T) { - - // base address is all zeros - // no peers - // unhealthy (and lonely) - tk := newTestKademlia(t, "11111111") - tk.checkHealth(false) - - // know one peer but not connected - // unhealthy - tk.Register("11100000") - tk.checkHealth(false) - - // know one peer and connected - // unhealthy: not saturated - tk.On("11100000") - tk.checkHealth(true) - - // know two peers, only one connected - // unhealthy - tk.Register("11111100") - tk.checkHealth(false) - - // know two peers and connected to both - // healthy - tk.On("11111100") - tk.checkHealth(true) - - // know three peers, connected to the two deepest - // healthy - tk.Register("00000000") - tk.checkHealth(false) - - // know three peers, connected to all three - // healthy - tk.On("00000000") - tk.checkHealth(true) - - // add fourth peer deeper than current depth - // unhealthy - tk.Register("11110000") - tk.checkHealth(false) - - // connected to three deepest peers - // healthy - tk.On("11110000") - tk.checkHealth(true) - - // add additional peer in same bin as deepest peer - // unhealthy - tk.Register("11111101") - tk.checkHealth(false) - - // four deepest of five peers connected - // healthy - tk.On("11111101") - tk.checkHealth(true) - - // add additional peer in bin 0 - // unhealthy: unsaturated bin 0, 2 known but 1 connected - tk.Register("00000001") - tk.checkHealth(false) - - // Connect second in bin 0 - // healthy - tk.On("00000001") - tk.checkHealth(true) - - // add peer in bin 1 - // unhealthy, as it is known but not connected - tk.Register("10000000") - tk.checkHealth(false) - - // connect peer in bin 1 - // depth change, is now 1 - // healthy, 1 peer in bin 1 known and connected - tk.On("10000000") - tk.checkHealth(true) - - // add second peer in bin 1 - // unhealthy, as it is known but not connected - tk.Register("10000001") - tk.checkHealth(false) - - // connect second peer in bin 1 - // healthy, - tk.On("10000001") - tk.checkHealth(true) - - // connect third peer in bin 1 - // healthy, - tk.On("10000011") - tk.checkHealth(true) - - // add peer in bin 2 - // unhealthy, no depth change - tk.Register("11000000") - tk.checkHealth(false) - - // connect peer in bin 2 - // depth change - as we already have peers in bin 3 and 4, - // we have contiguous bins, no bin < po 5 is empty -> depth 5 - // healthy, every bin < depth has the max available peers, - // even if they are < MinBinSize - tk.On("11000000") - tk.checkHealth(true) - - // add peer in bin 2 - // unhealthy, peer bin is below depth 5 but - // has more available peers (2) than connected ones (1) - // --> unsaturated - tk.Register("11000011") - tk.checkHealth(false) -} - -func (tk *testKademlia) checkHealth(expectHealthy bool) { - tk.t.Helper() - kid := common.Bytes2Hex(tk.BaseAddr()) - addrs := [][]byte{tk.BaseAddr()} - tk.EachAddr(nil, 255, func(addr *BzzAddr, po int) bool { - addrs = append(addrs, addr.Address()) - return true - }) - - pp := NewPeerPotMap(tk.NeighbourhoodSize, addrs) - healthParams := tk.GetHealthInfo(pp[kid]) - - // definition of health, all conditions but be true: - // - we at least know one peer - // - we know all neighbors - // - we are connected to all known neighbors - health := healthParams.Healthy() - if expectHealthy != health { - tk.t.Fatalf("expected kademlia health %v, is %v\n%v", expectHealthy, health, tk.String()) - } -} - -func (tk *testKademlia) checkSuggestPeer(expAddr string, expDepth int, expChanged bool) { - tk.t.Helper() - addr, depth, changed := tk.SuggestPeer() - log.Trace("suggestPeer return", "addr", addr, "depth", depth, "changed", changed) - if binStr(addr) != expAddr { - tk.t.Fatalf("incorrect peer address suggested. expected %v, got %v", expAddr, binStr(addr)) - } - if depth != expDepth { - tk.t.Fatalf("incorrect saturation depth suggested. expected %v, got %v", expDepth, depth) - } - if changed != expChanged { - tk.t.Fatalf("expected depth change = %v, got %v", expChanged, changed) - } -} - -func binStr(a *BzzAddr) string { - if a == nil { - return "" - } - return pot.ToBin(a.Address())[:8] -} - -func TestSuggestPeerFindPeers(t *testing.T) { - tk := newTestKademlia(t, "00000000") - tk.On("00100000") - tk.checkSuggestPeer("", 0, false) - - tk.On("00010000") - tk.checkSuggestPeer("", 0, false) - - tk.On("10000000", "10000001") - tk.checkSuggestPeer("", 0, false) - - tk.On("01000000") - tk.Off("10000001") - tk.checkSuggestPeer("10000001", 0, true) - - tk.On("00100001") - tk.Off("01000000") - tk.checkSuggestPeer("01000000", 0, false) - - // second time disconnected peer not callable - // with reasonably set Interval - tk.checkSuggestPeer("", 0, false) - - // on and off again, peer callable again - tk.On("01000000") - tk.Off("01000000") - tk.checkSuggestPeer("01000000", 0, false) - - tk.On("01000000", "10000001") - tk.checkSuggestPeer("", 0, false) - - tk.Register("00010001") - tk.checkSuggestPeer("00010001", 0, false) - - tk.On("00010001") - tk.Off("01000000") - tk.checkSuggestPeer("01000000", 0, false) - - tk.On("01000000") - tk.checkSuggestPeer("", 0, false) - - tk.Register("01000001") - tk.checkSuggestPeer("01000001", 0, false) - - tk.On("01000001") - tk.checkSuggestPeer("", 0, false) - - tk.Register("10000010", "01000010", "00100010") - tk.checkSuggestPeer("", 0, false) - - tk.Register("00010010") - tk.checkSuggestPeer("00010010", 0, false) - - tk.Off("00100001") - tk.checkSuggestPeer("00100010", 2, true) - - tk.Off("01000001") - tk.checkSuggestPeer("01000010", 1, true) - - tk.checkSuggestPeer("01000001", 0, false) - tk.checkSuggestPeer("00100001", 0, false) - tk.checkSuggestPeer("", 0, false) - - tk.On("01000001", "00100001") - tk.Register("10000100", "01000100", "00100100") - tk.Register("00000100", "00000101", "00000110") - tk.Register("00000010", "00000011", "00000001") - - tk.checkSuggestPeer("00000110", 0, false) - tk.checkSuggestPeer("00000101", 0, false) - tk.checkSuggestPeer("00000100", 0, false) - tk.checkSuggestPeer("00000011", 0, false) - tk.checkSuggestPeer("00000010", 0, false) - tk.checkSuggestPeer("00000001", 0, false) - tk.checkSuggestPeer("", 0, false) - -} - -// a node should stay in the address book if it's removed from the kademlia -func TestOffEffectingAddressBookNormalNode(t *testing.T) { - tk := newTestKademlia(t, "00000000") - // peer added to kademlia - tk.On("01000000") - // peer should be in the address book - if tk.addrs.Size() != 1 { - t.Fatal("known peer addresses should contain 1 entry") - } - // peer should be among live connections - if tk.conns.Size() != 1 { - t.Fatal("live peers should contain 1 entry") - } - // remove peer from kademlia - tk.Off("01000000") - // peer should be in the address book - if tk.addrs.Size() != 1 { - t.Fatal("known peer addresses should contain 1 entry") - } - // peer should not be among live connections - if tk.conns.Size() != 0 { - t.Fatal("live peers should contain 0 entry") - } -} - -// a light node should not be in the address book -func TestOffEffectingAddressBookLightNode(t *testing.T) { - tk := newTestKademlia(t, "00000000") - // light node peer added to kademlia - tk.Kademlia.On(tk.newTestKadPeer("01000000", true)) - // peer should not be in the address book - if tk.addrs.Size() != 0 { - t.Fatal("known peer addresses should contain 0 entry") - } - // peer should be among live connections - if tk.conns.Size() != 1 { - t.Fatal("live peers should contain 1 entry") - } - // remove peer from kademlia - tk.Kademlia.Off(tk.newTestKadPeer("01000000", true)) - // peer should not be in the address book - if tk.addrs.Size() != 0 { - t.Fatal("known peer addresses should contain 0 entry") - } - // peer should not be among live connections - if tk.conns.Size() != 0 { - t.Fatal("live peers should contain 0 entry") - } -} - -func TestSuggestPeerRetries(t *testing.T) { - tk := newTestKademlia(t, "00000000") - tk.RetryInterval = int64(300 * time.Millisecond) // cycle - tk.MaxRetries = 50 - tk.RetryExponent = 2 - sleep := func(n int) { - ts := tk.RetryInterval - for i := 1; i < n; i++ { - ts *= int64(tk.RetryExponent) - } - time.Sleep(time.Duration(ts)) - } - - tk.Register("01000000") - tk.On("00000001", "00000010") - tk.checkSuggestPeer("01000000", 0, false) - - tk.checkSuggestPeer("", 0, false) - - sleep(1) - tk.checkSuggestPeer("01000000", 0, false) - - tk.checkSuggestPeer("", 0, false) - - sleep(1) - tk.checkSuggestPeer("01000000", 0, false) - - tk.checkSuggestPeer("", 0, false) - - sleep(2) - tk.checkSuggestPeer("01000000", 0, false) - - tk.checkSuggestPeer("", 0, false) - - sleep(2) - tk.checkSuggestPeer("", 0, false) -} - -func TestKademliaHiveString(t *testing.T) { - tk := newTestKademlia(t, "00000000") - tk.On("01000000", "00100000") - tk.Register("10000000", "10000001") - tk.MaxProxDisplay = 8 - h := tk.String() - expH := "\n=========================================================================\nMon Feb 27 12:10:28 UTC 2017 KΛÐΞMLIΛ hive: queen's address: 000000\npopulation: 2 (4), NeighbourhoodSize: 2, MinBinSize: 2, MaxBinSize: 4\n============ DEPTH: 0 ==========================================\n000 0 | 2 8100 (0) 8000 (0)\n001 1 4000 | 1 4000 (0)\n002 1 2000 | 1 2000 (0)\n003 0 | 0\n004 0 | 0\n005 0 | 0\n006 0 | 0\n007 0 | 0\n=========================================================================" - if expH[104:] != h[104:] { - t.Fatalf("incorrect hive output. expected %v, got %v", expH, h) - } -} - -func newTestDiscoveryPeer(addr pot.Address, kad *Kademlia) *Peer { - rw := &p2p.MsgPipeRW{} - p := p2p.NewPeer(enode.ID{}, "foo", []p2p.Cap{}) - pp := protocols.NewPeer(p, rw, &protocols.Spec{}) - bp := &BzzPeer{ - Peer: pp, - BzzAddr: &BzzAddr{ - OAddr: addr.Bytes(), - UAddr: []byte(fmt.Sprintf("%x", addr[:])), - }, - } - return NewPeer(bp, kad) -} diff --git a/swarm/network/networkid_test.go b/swarm/network/networkid_test.go deleted file mode 100644 index 0da318e56e1f..000000000000 --- a/swarm/network/networkid_test.go +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package network - -import ( - "bytes" - "context" - "flag" - "fmt" - "math/rand" - "strings" - "testing" - "time" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/simulations" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" - "github.com/ubiq/go-ubiq/rpc" -) - -var ( - currentNetworkID int - cnt int - nodeMap map[int][]enode.ID - kademlias map[enode.ID]*Kademlia -) - -const ( - NumberOfNets = 4 - MaxTimeout = 15 * time.Second -) - -func init() { - flag.Parse() - rand.Seed(time.Now().Unix()) -} - -/* -Run the network ID test. -The test creates one simulations.Network instance, -a number of nodes, then connects nodes with each other in this network. - -Each node gets a network ID assigned according to the number of networks. -Having more network IDs is just arbitrary in order to exclude -false positives. - -Nodes should only connect with other nodes with the same network ID. -After the setup phase, the test checks on each node if it has the -expected node connections (excluding those not sharing the network ID). -*/ -func TestNetworkID(t *testing.T) { - log.Debug("Start test") - //arbitrarily set the number of nodes. It could be any number - numNodes := 24 - //the nodeMap maps all nodes (slice value) with the same network ID (key) - nodeMap = make(map[int][]enode.ID) - //set up the network and connect nodes - net, err := setupNetwork(numNodes) - if err != nil { - t.Fatalf("Error setting up network: %v", err) - } - //let's sleep to ensure all nodes are connected - time.Sleep(1 * time.Second) - // shutdown the the network to avoid race conditions - // on accessing kademlias global map while network nodes - // are accepting messages - net.Shutdown() - //for each group sharing the same network ID... - for _, netIDGroup := range nodeMap { - log.Trace("netIDGroup size", "size", len(netIDGroup)) - //...check that their size of the kademlia is of the expected size - //the assumption is that it should be the size of the group minus 1 (the node itself) - for _, node := range netIDGroup { - if kademlias[node].addrs.Size() != len(netIDGroup)-1 { - t.Fatalf("Kademlia size has not expected peer size. Kademlia size: %d, expected size: %d", kademlias[node].addrs.Size(), len(netIDGroup)-1) - } - kademlias[node].EachAddr(nil, 0, func(addr *BzzAddr, _ int) bool { - found := false - for _, nd := range netIDGroup { - if bytes.Equal(kademlias[nd].BaseAddr(), addr.Address()) { - found = true - } - } - if !found { - t.Fatalf("Expected node not found for node %s", node.String()) - } - return true - }) - } - } - log.Info("Test terminated successfully") -} - -// setup simulated network with bzz/discovery and pss services. -// connects nodes in a circle -// if allowRaw is set, omission of builtin pss encryption is enabled (see PssParams) -func setupNetwork(numnodes int) (net *simulations.Network, err error) { - log.Debug("Setting up network") - quitC := make(chan struct{}) - errc := make(chan error) - nodes := make([]*simulations.Node, numnodes) - if numnodes < 16 { - return nil, fmt.Errorf("Minimum sixteen nodes in network") - } - adapter := adapters.NewSimAdapter(newServices()) - //create the network - net = simulations.NewNetwork(adapter, &simulations.NetworkConfig{ - ID: "NetworkIdTestNet", - DefaultService: "bzz", - }) - log.Debug("Creating networks and nodes") - - var connCount int - - //create nodes and connect them to each other - for i := 0; i < numnodes; i++ { - log.Trace("iteration: ", "i", i) - nodeconf := adapters.RandomNodeConfig() - nodes[i], err = net.NewNodeWithConfig(nodeconf) - if err != nil { - return nil, fmt.Errorf("error creating node %d: %v", i, err) - } - err = net.Start(nodes[i].ID()) - if err != nil { - return nil, fmt.Errorf("error starting node %d: %v", i, err) - } - client, err := nodes[i].Client() - if err != nil { - return nil, fmt.Errorf("create node %d rpc client fail: %v", i, err) - } - //now setup and start event watching in order to know when we can upload - ctx, watchCancel := context.WithTimeout(context.Background(), MaxTimeout) - defer watchCancel() - watchSubscriptionEvents(ctx, nodes[i].ID(), client, errc, quitC) - //on every iteration we connect to all previous ones - for k := i - 1; k >= 0; k-- { - connCount++ - log.Debug(fmt.Sprintf("Connecting node %d with node %d; connection count is %d", i, k, connCount)) - err = net.Connect(nodes[i].ID(), nodes[k].ID()) - if err != nil { - if !strings.Contains(err.Error(), "already connected") { - return nil, fmt.Errorf("error connecting nodes: %v", err) - } - } - } - } - //now wait until the number of expected subscriptions has been finished - //`watchSubscriptionEvents` will write with a `nil` value to errc - for err := range errc { - if err != nil { - return nil, err - } - //`nil` received, decrement count - connCount-- - log.Trace("count down", "cnt", connCount) - //all subscriptions received - if connCount == 0 { - close(quitC) - break - } - } - log.Debug("Network setup phase terminated") - return net, nil -} - -func newServices() adapters.Services { - kademlias = make(map[enode.ID]*Kademlia) - kademlia := func(id enode.ID) *Kademlia { - if k, ok := kademlias[id]; ok { - return k - } - params := NewKadParams() - params.NeighbourhoodSize = 2 - params.MaxBinSize = 3 - params.MinBinSize = 1 - params.MaxRetries = 1000 - params.RetryExponent = 2 - params.RetryInterval = 1000000 - kademlias[id] = NewKademlia(id[:], params) - return kademlias[id] - } - return adapters.Services{ - "bzz": func(ctx *adapters.ServiceContext) (node.Service, error) { - addr := NewAddr(ctx.Config.Node()) - hp := NewHiveParams() - hp.Discovery = false - cnt++ - //assign the network ID - currentNetworkID = cnt % NumberOfNets - if ok := nodeMap[currentNetworkID]; ok == nil { - nodeMap[currentNetworkID] = make([]enode.ID, 0) - } - //add this node to the group sharing the same network ID - nodeMap[currentNetworkID] = append(nodeMap[currentNetworkID], ctx.Config.ID) - log.Debug("current network ID:", "id", currentNetworkID) - config := &BzzConfig{ - OverlayAddr: addr.Over(), - UnderlayAddr: addr.Under(), - HiveParams: hp, - NetworkID: uint64(currentNetworkID), - } - return NewBzz(config, kademlia(ctx.Config.ID), nil, nil, nil), nil - }, - } -} - -func watchSubscriptionEvents(ctx context.Context, id enode.ID, client *rpc.Client, errc chan error, quitC chan struct{}) { - events := make(chan *p2p.PeerEvent) - sub, err := client.Subscribe(context.Background(), "admin", events, "peerEvents") - if err != nil { - log.Error(err.Error()) - errc <- fmt.Errorf("error getting peer events for node %v: %s", id, err) - return - } - go func() { - defer func() { - sub.Unsubscribe() - log.Trace("watch subscription events: unsubscribe", "id", id) - }() - - for { - select { - case <-quitC: - return - case <-ctx.Done(): - select { - case errc <- ctx.Err(): - case <-quitC: - } - return - case e := <-events: - if e.Type == p2p.PeerEventTypeAdd { - errc <- nil - } - case err := <-sub.Err(): - if err != nil { - select { - case errc <- fmt.Errorf("error getting peer events for node %v: %v", id, err): - case <-quitC: - } - return - } - } - } - }() -} diff --git a/swarm/network/priorityqueue/priorityqueue.go b/swarm/network/priorityqueue/priorityqueue.go deleted file mode 100644 index 513a0aa6d445..000000000000 --- a/swarm/network/priorityqueue/priorityqueue.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// package priority_queue implement a channel based priority queue -// over arbitrary types. It provides an -// an autopop loop applying a function to the items always respecting -// their priority. The structure is only quasi consistent ie., if a lower -// priority item is autopopped, it is guaranteed that there was a point -// when no higher priority item was present, ie. it is not guaranteed -// that there was any point where the lower priority item was present -// but the higher was not - -package priorityqueue - -import ( - "context" - "errors" - - "github.com/ubiq/go-ubiq/log" -) - -var ( - ErrContention = errors.New("contention") - - errBadPriority = errors.New("bad priority") - - wakey = struct{}{} -) - -// PriorityQueue is the basic structure -type PriorityQueue struct { - Queues []chan interface{} - wakeup chan struct{} -} - -// New is the constructor for PriorityQueue -func New(n int, l int) *PriorityQueue { - var queues = make([]chan interface{}, n) - for i := range queues { - queues[i] = make(chan interface{}, l) - } - return &PriorityQueue{ - Queues: queues, - wakeup: make(chan struct{}, 1), - } -} - -// Run is a forever loop popping items from the queues -func (pq *PriorityQueue) Run(ctx context.Context, f func(interface{})) { - top := len(pq.Queues) - 1 - p := top -READ: - for { - q := pq.Queues[p] - select { - case <-ctx.Done(): - return - case x := <-q: - log.Trace("priority.queue f(x)", "p", p, "len(Queues[p])", len(pq.Queues[p])) - f(x) - p = top - default: - if p > 0 { - p-- - log.Trace("priority.queue p > 0", "p", p) - continue READ - } - p = top - select { - case <-ctx.Done(): - return - case <-pq.wakeup: - log.Trace("priority.queue wakeup", "p", p) - } - } - } -} - -// Push pushes an item to the appropriate queue specified in the priority argument -// if context is given it waits until either the item is pushed or the Context aborts -func (pq *PriorityQueue) Push(x interface{}, p int) error { - if p < 0 || p >= len(pq.Queues) { - return errBadPriority - } - log.Trace("priority.queue push", "p", p, "len(Queues[p])", len(pq.Queues[p])) - select { - case pq.Queues[p] <- x: - default: - return ErrContention - } - select { - case pq.wakeup <- wakey: - default: - } - return nil -} diff --git a/swarm/network/priorityqueue/priorityqueue_test.go b/swarm/network/priorityqueue/priorityqueue_test.go deleted file mode 100644 index ed8b575c2013..000000000000 --- a/swarm/network/priorityqueue/priorityqueue_test.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . -package priorityqueue - -import ( - "context" - "sync" - "testing" -) - -func TestPriorityQueue(t *testing.T) { - var results []string - wg := sync.WaitGroup{} - pq := New(3, 2) - wg.Add(1) - go pq.Run(context.Background(), func(v interface{}) { - results = append(results, v.(string)) - wg.Done() - }) - pq.Push("2.0", 2) - wg.Wait() - if results[0] != "2.0" { - t.Errorf("expected first result %q, got %q", "2.0", results[0]) - } - -Loop: - for i, tc := range []struct { - priorities []int - values []string - results []string - errors []error - }{ - { - priorities: []int{0}, - values: []string{""}, - results: []string{""}, - }, - { - priorities: []int{0, 1}, - values: []string{"0.0", "1.0"}, - results: []string{"1.0", "0.0"}, - }, - { - priorities: []int{1, 0}, - values: []string{"1.0", "0.0"}, - results: []string{"1.0", "0.0"}, - }, - { - priorities: []int{0, 1, 1}, - values: []string{"0.0", "1.0", "1.1"}, - results: []string{"1.0", "1.1", "0.0"}, - }, - { - priorities: []int{0, 0, 0}, - values: []string{"0.0", "0.0", "0.1"}, - errors: []error{nil, nil, ErrContention}, - }, - } { - var results []string - wg := sync.WaitGroup{} - pq := New(3, 2) - wg.Add(len(tc.values)) - for j, value := range tc.values { - err := pq.Push(value, tc.priorities[j]) - if tc.errors != nil && err != tc.errors[j] { - t.Errorf("expected push error %v, got %v", tc.errors[j], err) - continue Loop - } - if err != nil { - continue Loop - } - } - go pq.Run(context.Background(), func(v interface{}) { - results = append(results, v.(string)) - wg.Done() - }) - wg.Wait() - for k, result := range tc.results { - if results[k] != result { - t.Errorf("test case %v: expected %v element %q, got %q", i, k, result, results[k]) - } - } - } -} diff --git a/swarm/network/protocol.go b/swarm/network/protocol.go deleted file mode 100644 index aa0d403a86d3..000000000000 --- a/swarm/network/protocol.go +++ /dev/null @@ -1,389 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package network - -import ( - "context" - "errors" - "fmt" - "net" - "sync" - "time" - - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/protocols" - "github.com/ubiq/go-ubiq/rpc" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/state" -) - -const ( - DefaultNetworkID = 3 - // timeout for waiting - bzzHandshakeTimeout = 3000 * time.Millisecond -) - -// BzzSpec is the spec of the generic swarm handshake -var BzzSpec = &protocols.Spec{ - Name: "bzz", - Version: 8, - MaxMsgSize: 10 * 1024 * 1024, - Messages: []interface{}{ - HandshakeMsg{}, - }, -} - -// DiscoverySpec is the spec for the bzz discovery subprotocols -var DiscoverySpec = &protocols.Spec{ - Name: "hive", - Version: 8, - MaxMsgSize: 10 * 1024 * 1024, - Messages: []interface{}{ - peersMsg{}, - subPeersMsg{}, - }, -} - -// BzzConfig captures the config params used by the hive -type BzzConfig struct { - OverlayAddr []byte // base address of the overlay network - UnderlayAddr []byte // node's underlay address - HiveParams *HiveParams - NetworkID uint64 - LightNode bool - BootnodeMode bool -} - -// Bzz is the swarm protocol bundle -type Bzz struct { - *Hive - NetworkID uint64 - LightNode bool - localAddr *BzzAddr - mtx sync.Mutex - handshakes map[enode.ID]*HandshakeMsg - streamerSpec *protocols.Spec - streamerRun func(*BzzPeer) error -} - -// NewBzz is the swarm protocol constructor -// arguments -// * bzz config -// * overlay driver -// * peer store -func NewBzz(config *BzzConfig, kad *Kademlia, store state.Store, streamerSpec *protocols.Spec, streamerRun func(*BzzPeer) error) *Bzz { - bzz := &Bzz{ - Hive: NewHive(config.HiveParams, kad, store), - NetworkID: config.NetworkID, - LightNode: config.LightNode, - localAddr: &BzzAddr{config.OverlayAddr, config.UnderlayAddr}, - handshakes: make(map[enode.ID]*HandshakeMsg), - streamerRun: streamerRun, - streamerSpec: streamerSpec, - } - - if config.BootnodeMode { - bzz.streamerRun = nil - bzz.streamerSpec = nil - } - - return bzz -} - -// UpdateLocalAddr updates underlayaddress of the running node -func (b *Bzz) UpdateLocalAddr(byteaddr []byte) *BzzAddr { - b.localAddr = b.localAddr.Update(&BzzAddr{ - UAddr: byteaddr, - OAddr: b.localAddr.OAddr, - }) - return b.localAddr -} - -// NodeInfo returns the node's overlay address -func (b *Bzz) NodeInfo() interface{} { - return b.localAddr.Address() -} - -// Protocols return the protocols swarm offers -// Bzz implements the node.Service interface -// * handshake/hive -// * discovery -func (b *Bzz) Protocols() []p2p.Protocol { - protocol := []p2p.Protocol{ - { - Name: BzzSpec.Name, - Version: BzzSpec.Version, - Length: BzzSpec.Length(), - Run: b.runBzz, - NodeInfo: b.NodeInfo, - }, - { - Name: DiscoverySpec.Name, - Version: DiscoverySpec.Version, - Length: DiscoverySpec.Length(), - Run: b.RunProtocol(DiscoverySpec, b.Hive.Run), - NodeInfo: b.Hive.NodeInfo, - PeerInfo: b.Hive.PeerInfo, - }, - } - if b.streamerSpec != nil && b.streamerRun != nil { - protocol = append(protocol, p2p.Protocol{ - Name: b.streamerSpec.Name, - Version: b.streamerSpec.Version, - Length: b.streamerSpec.Length(), - Run: b.RunProtocol(b.streamerSpec, b.streamerRun), - }) - } - return protocol -} - -// APIs returns the APIs offered by bzz -// * hive -// Bzz implements the node.Service interface -func (b *Bzz) APIs() []rpc.API { - return []rpc.API{{ - Namespace: "hive", - Version: "3.0", - Service: b.Hive, - }} -} - -// RunProtocol is a wrapper for swarm subprotocols -// returns a p2p protocol run function that can be assigned to p2p.Protocol#Run field -// arguments: -// * p2p protocol spec -// * run function taking BzzPeer as argument -// this run function is meant to block for the duration of the protocol session -// on return the session is terminated and the peer is disconnected -// the protocol waits for the bzz handshake is negotiated -// the overlay address on the BzzPeer is set from the remote handshake -func (b *Bzz) RunProtocol(spec *protocols.Spec, run func(*BzzPeer) error) func(*p2p.Peer, p2p.MsgReadWriter) error { - return func(p *p2p.Peer, rw p2p.MsgReadWriter) error { - // wait for the bzz protocol to perform the handshake - handshake, _ := b.GetOrCreateHandshake(p.ID()) - defer b.removeHandshake(p.ID()) - select { - case <-handshake.done: - case <-time.After(bzzHandshakeTimeout): - return fmt.Errorf("%08x: %s protocol timeout waiting for handshake on %08x", b.BaseAddr()[:4], spec.Name, p.ID().Bytes()[:4]) - } - if handshake.err != nil { - return fmt.Errorf("%08x: %s protocol closed: %v", b.BaseAddr()[:4], spec.Name, handshake.err) - } - // the handshake has succeeded so construct the BzzPeer and run the protocol - peer := &BzzPeer{ - Peer: protocols.NewPeer(p, rw, spec), - BzzAddr: handshake.peerAddr, - lastActive: time.Now(), - LightNode: handshake.LightNode, - } - - log.Debug("peer created", "addr", handshake.peerAddr.String()) - - return run(peer) - } -} - -// performHandshake implements the negotiation of the bzz handshake -// shared among swarm subprotocols -func (b *Bzz) performHandshake(p *protocols.Peer, handshake *HandshakeMsg) error { - ctx, cancel := context.WithTimeout(context.Background(), bzzHandshakeTimeout) - defer func() { - close(handshake.done) - cancel() - }() - rsh, err := p.Handshake(ctx, handshake, b.checkHandshake) - if err != nil { - handshake.err = err - return err - } - handshake.peerAddr = rsh.(*HandshakeMsg).Addr - handshake.LightNode = rsh.(*HandshakeMsg).LightNode - return nil -} - -// runBzz is the p2p protocol run function for the bzz base protocol -// that negotiates the bzz handshake -func (b *Bzz) runBzz(p *p2p.Peer, rw p2p.MsgReadWriter) error { - handshake, _ := b.GetOrCreateHandshake(p.ID()) - if !<-handshake.init { - return fmt.Errorf("%08x: bzz already started on peer %08x", b.localAddr.Over()[:4], p.ID().Bytes()[:4]) - } - close(handshake.init) - defer b.removeHandshake(p.ID()) - peer := protocols.NewPeer(p, rw, BzzSpec) - err := b.performHandshake(peer, handshake) - if err != nil { - log.Warn(fmt.Sprintf("%08x: handshake failed with remote peer %08x: %v", b.localAddr.Over()[:4], p.ID().Bytes()[:4], err)) - - return err - } - // fail if we get another handshake - msg, err := rw.ReadMsg() - if err != nil { - return err - } - msg.Discard() - return errors.New("received multiple handshakes") -} - -// BzzPeer is the bzz protocol view of a protocols.Peer (itself an extension of p2p.Peer) -// implements the Peer interface and all interfaces Peer implements: Addr, OverlayPeer -type BzzPeer struct { - *protocols.Peer // represents the connection for online peers - *BzzAddr // remote address -> implements Addr interface = protocols.Peer - lastActive time.Time // time is updated whenever mutexes are releasing - LightNode bool -} - -func NewBzzPeer(p *protocols.Peer) *BzzPeer { - return &BzzPeer{Peer: p, BzzAddr: NewAddr(p.Node())} -} - -// ID returns the peer's underlay node identifier. -func (p *BzzPeer) ID() enode.ID { - // This is here to resolve a method tie: both protocols.Peer and BzzAddr are embedded - // into the struct and provide ID(). The protocols.Peer version is faster, ensure it - // gets used. - return p.Peer.ID() -} - -/* - Handshake - -* Version: 8 byte integer version of the protocol -* NetworkID: 8 byte integer network identifier -* Addr: the address advertised by the node including underlay and overlay connecctions -*/ -type HandshakeMsg struct { - Version uint64 - NetworkID uint64 - Addr *BzzAddr - LightNode bool - - // peerAddr is the address received in the peer handshake - peerAddr *BzzAddr - - init chan bool - done chan struct{} - err error -} - -// String pretty prints the handshake -func (bh *HandshakeMsg) String() string { - return fmt.Sprintf("Handshake: Version: %v, NetworkID: %v, Addr: %v, LightNode: %v, peerAddr: %v", bh.Version, bh.NetworkID, bh.Addr, bh.LightNode, bh.peerAddr) -} - -// Perform initiates the handshake and validates the remote handshake message -func (b *Bzz) checkHandshake(hs interface{}) error { - rhs := hs.(*HandshakeMsg) - if rhs.NetworkID != b.NetworkID { - return fmt.Errorf("network id mismatch %d (!= %d)", rhs.NetworkID, b.NetworkID) - } - if rhs.Version != uint64(BzzSpec.Version) { - return fmt.Errorf("version mismatch %d (!= %d)", rhs.Version, BzzSpec.Version) - } - return nil -} - -// removeHandshake removes handshake for peer with peerID -// from the bzz handshake store -func (b *Bzz) removeHandshake(peerID enode.ID) { - b.mtx.Lock() - defer b.mtx.Unlock() - delete(b.handshakes, peerID) -} - -// GetHandshake returns the bzz handhake that the remote peer with peerID sent -func (b *Bzz) GetOrCreateHandshake(peerID enode.ID) (*HandshakeMsg, bool) { - b.mtx.Lock() - defer b.mtx.Unlock() - handshake, found := b.handshakes[peerID] - if !found { - handshake = &HandshakeMsg{ - Version: uint64(BzzSpec.Version), - NetworkID: b.NetworkID, - Addr: b.localAddr, - LightNode: b.LightNode, - init: make(chan bool, 1), - done: make(chan struct{}), - } - // when handhsake is first created for a remote peer - // it is initialised with the init - handshake.init <- true - b.handshakes[peerID] = handshake - } - - return handshake, found -} - -// BzzAddr implements the PeerAddr interface -type BzzAddr struct { - OAddr []byte - UAddr []byte -} - -// Address implements OverlayPeer interface to be used in Overlay. -func (a *BzzAddr) Address() []byte { - return a.OAddr -} - -// Over returns the overlay address. -func (a *BzzAddr) Over() []byte { - return a.OAddr -} - -// Under returns the underlay address. -func (a *BzzAddr) Under() []byte { - return a.UAddr -} - -// ID returns the node identifier in the underlay. -func (a *BzzAddr) ID() enode.ID { - n, err := enode.ParseV4(string(a.UAddr)) - if err != nil { - return enode.ID{} - } - return n.ID() -} - -// Update updates the underlay address of a peer record -func (a *BzzAddr) Update(na *BzzAddr) *BzzAddr { - return &BzzAddr{a.OAddr, na.UAddr} -} - -// String pretty prints the address -func (a *BzzAddr) String() string { - return fmt.Sprintf("%x <%s>", a.OAddr, a.UAddr) -} - -// RandomAddr is a utility method generating an address from a public key -func RandomAddr() *BzzAddr { - key, err := crypto.GenerateKey() - if err != nil { - panic("unable to generate key") - } - node := enode.NewV4(&key.PublicKey, net.IP{127, 0, 0, 1}, 30388, 30388) - return NewAddr(node) -} - -// NewAddr constucts a BzzAddr from a node record. -func NewAddr(node *enode.Node) *BzzAddr { - return &BzzAddr{OAddr: node.ID().Bytes(), UAddr: []byte(node.String())} -} diff --git a/swarm/network/protocol_test.go b/swarm/network/protocol_test.go deleted file mode 100644 index 5c2ec5f9f474..000000000000 --- a/swarm/network/protocol_test.go +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package network - -import ( - "flag" - "fmt" - "os" - "testing" - "time" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/protocols" - p2ptest "github.com/ubiq/go-ubiq/p2p/testing" -) - -const ( - TestProtocolVersion = 8 - TestProtocolNetworkID = 3 -) - -var ( - loglevel = flag.Int("loglevel", 2, "verbosity of logs") -) - -func init() { - flag.Parse() - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(true)))) -} - -func HandshakeMsgExchange(lhs, rhs *HandshakeMsg, id enode.ID) []p2ptest.Exchange { - return []p2ptest.Exchange{ - { - Expects: []p2ptest.Expect{ - { - Code: 0, - Msg: lhs, - Peer: id, - }, - }, - }, - { - Triggers: []p2ptest.Trigger{ - { - Code: 0, - Msg: rhs, - Peer: id, - }, - }, - }, - } -} - -func newBzzBaseTester(t *testing.T, n int, addr *BzzAddr, spec *protocols.Spec, run func(*BzzPeer) error) *bzzTester { - cs := make(map[string]chan bool) - - srv := func(p *BzzPeer) error { - defer func() { - if cs[p.ID().String()] != nil { - close(cs[p.ID().String()]) - } - }() - return run(p) - } - - protocol := func(p *p2p.Peer, rw p2p.MsgReadWriter) error { - return srv(&BzzPeer{Peer: protocols.NewPeer(p, rw, spec), BzzAddr: NewAddr(p.Node())}) - } - - s := p2ptest.NewProtocolTester(addr.ID(), n, protocol) - - for _, node := range s.Nodes { - cs[node.ID().String()] = make(chan bool) - } - - return &bzzTester{ - addr: addr, - ProtocolTester: s, - cs: cs, - } -} - -type bzzTester struct { - *p2ptest.ProtocolTester - addr *BzzAddr - cs map[string]chan bool - bzz *Bzz -} - -func newBzz(addr *BzzAddr, lightNode bool) *Bzz { - config := &BzzConfig{ - OverlayAddr: addr.Over(), - UnderlayAddr: addr.Under(), - HiveParams: NewHiveParams(), - NetworkID: DefaultNetworkID, - LightNode: lightNode, - } - kad := NewKademlia(addr.OAddr, NewKadParams()) - bzz := NewBzz(config, kad, nil, nil, nil) - return bzz -} - -func newBzzHandshakeTester(n int, addr *BzzAddr, lightNode bool) *bzzTester { - bzz := newBzz(addr, lightNode) - pt := p2ptest.NewProtocolTester(addr.ID(), n, bzz.runBzz) - - return &bzzTester{ - addr: addr, - ProtocolTester: pt, - bzz: bzz, - } -} - -// should test handshakes in one exchange? parallelisation -func (s *bzzTester) testHandshake(lhs, rhs *HandshakeMsg, disconnects ...*p2ptest.Disconnect) error { - if err := s.TestExchanges(HandshakeMsgExchange(lhs, rhs, rhs.Addr.ID())...); err != nil { - return err - } - - if len(disconnects) > 0 { - return s.TestDisconnected(disconnects...) - } - - // If we don't expect disconnect, ensure peers remain connected - err := s.TestDisconnected(&p2ptest.Disconnect{ - Peer: s.Nodes[0].ID(), - Error: nil, - }) - - if err == nil { - return fmt.Errorf("Unexpected peer disconnect") - } - - if err.Error() != "timed out waiting for peers to disconnect" { - return err - } - - return nil -} - -func correctBzzHandshake(addr *BzzAddr, lightNode bool) *HandshakeMsg { - return &HandshakeMsg{ - Version: TestProtocolVersion, - NetworkID: TestProtocolNetworkID, - Addr: addr, - LightNode: lightNode, - } -} - -func TestBzzHandshakeNetworkIDMismatch(t *testing.T) { - lightNode := false - addr := RandomAddr() - s := newBzzHandshakeTester(1, addr, lightNode) - node := s.Nodes[0] - - err := s.testHandshake( - correctBzzHandshake(addr, lightNode), - &HandshakeMsg{Version: TestProtocolVersion, NetworkID: 321, Addr: NewAddr(node)}, - &p2ptest.Disconnect{Peer: node.ID(), Error: fmt.Errorf("Handshake error: Message handler error: (msg code 0): network id mismatch 321 (!= 3)")}, - ) - - if err != nil { - t.Fatal(err) - } -} - -func TestBzzHandshakeVersionMismatch(t *testing.T) { - lightNode := false - addr := RandomAddr() - s := newBzzHandshakeTester(1, addr, lightNode) - node := s.Nodes[0] - - err := s.testHandshake( - correctBzzHandshake(addr, lightNode), - &HandshakeMsg{Version: 0, NetworkID: TestProtocolNetworkID, Addr: NewAddr(node)}, - &p2ptest.Disconnect{Peer: node.ID(), Error: fmt.Errorf("Handshake error: Message handler error: (msg code 0): version mismatch 0 (!= %d)", TestProtocolVersion)}, - ) - - if err != nil { - t.Fatal(err) - } -} - -func TestBzzHandshakeSuccess(t *testing.T) { - lightNode := false - addr := RandomAddr() - s := newBzzHandshakeTester(1, addr, lightNode) - node := s.Nodes[0] - - err := s.testHandshake( - correctBzzHandshake(addr, lightNode), - &HandshakeMsg{Version: TestProtocolVersion, NetworkID: TestProtocolNetworkID, Addr: NewAddr(node)}, - ) - - if err != nil { - t.Fatal(err) - } -} - -func TestBzzHandshakeLightNode(t *testing.T) { - var lightNodeTests = []struct { - name string - lightNode bool - }{ - {"on", true}, - {"off", false}, - } - - for _, test := range lightNodeTests { - t.Run(test.name, func(t *testing.T) { - randomAddr := RandomAddr() - pt := newBzzHandshakeTester(1, randomAddr, false) - - node := pt.Nodes[0] - addr := NewAddr(node) - - err := pt.testHandshake( - correctBzzHandshake(randomAddr, false), - &HandshakeMsg{Version: TestProtocolVersion, NetworkID: TestProtocolNetworkID, Addr: addr, LightNode: test.lightNode}, - ) - - if err != nil { - t.Fatal(err) - } - - select { - - case <-pt.bzz.handshakes[node.ID()].done: - if pt.bzz.handshakes[node.ID()].LightNode != test.lightNode { - t.Fatalf("peer LightNode flag is %v, should be %v", pt.bzz.handshakes[node.ID()].LightNode, test.lightNode) - } - case <-time.After(10 * time.Second): - t.Fatal("test timeout") - } - }) - } -} diff --git a/swarm/network/simulation/bucket.go b/swarm/network/simulation/bucket.go deleted file mode 100644 index b6a69ca1e30b..000000000000 --- a/swarm/network/simulation/bucket.go +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package simulation - -import "github.com/ubiq/go-ubiq/p2p/enode" - -// BucketKey is the type that should be used for keys in simulation buckets. -type BucketKey string - -// NodeItem returns an item set in ServiceFunc function for a particular node. -func (s *Simulation) NodeItem(id enode.ID, key interface{}) (value interface{}, ok bool) { - s.mu.Lock() - defer s.mu.Unlock() - - if _, ok := s.buckets[id]; !ok { - return nil, false - } - return s.buckets[id].Load(key) -} - -// SetNodeItem sets a new item associated with the node with provided NodeID. -// Buckets should be used to avoid managing separate simulation global state. -func (s *Simulation) SetNodeItem(id enode.ID, key interface{}, value interface{}) { - s.mu.Lock() - defer s.mu.Unlock() - - s.buckets[id].Store(key, value) -} - -// NodesItems returns a map of items from all nodes that are all set under the -// same BucketKey. -func (s *Simulation) NodesItems(key interface{}) (values map[enode.ID]interface{}) { - s.mu.RLock() - defer s.mu.RUnlock() - - ids := s.NodeIDs() - values = make(map[enode.ID]interface{}, len(ids)) - for _, id := range ids { - if _, ok := s.buckets[id]; !ok { - continue - } - if v, ok := s.buckets[id].Load(key); ok { - values[id] = v - } - } - return values -} - -// UpNodesItems returns a map of items with the same BucketKey from all nodes that are up. -func (s *Simulation) UpNodesItems(key interface{}) (values map[enode.ID]interface{}) { - s.mu.RLock() - defer s.mu.RUnlock() - - ids := s.UpNodeIDs() - values = make(map[enode.ID]interface{}) - for _, id := range ids { - if _, ok := s.buckets[id]; !ok { - continue - } - if v, ok := s.buckets[id].Load(key); ok { - values[id] = v - } - } - return values -} diff --git a/swarm/network/simulation/bucket_test.go b/swarm/network/simulation/bucket_test.go deleted file mode 100644 index f50ad619982a..000000000000 --- a/swarm/network/simulation/bucket_test.go +++ /dev/null @@ -1,155 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package simulation - -import ( - "sync" - "testing" - - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" -) - -// TestServiceBucket tests all bucket functionality using subtests. -// It constructs a simulation of two nodes by adding items to their buckets -// in ServiceFunc constructor, then by SetNodeItem. Testing UpNodesItems -// is done by stopping one node and validating availability of its items. -func TestServiceBucket(t *testing.T) { - testKey := "Key" - testValue := "Value" - - sim := New(map[string]ServiceFunc{ - "noop": func(ctx *adapters.ServiceContext, b *sync.Map) (node.Service, func(), error) { - b.Store(testKey, testValue+ctx.Config.ID.String()) - return newNoopService(), nil, nil - }, - }) - defer sim.Close() - - id1, err := sim.AddNode() - if err != nil { - t.Fatal(err) - } - - id2, err := sim.AddNode() - if err != nil { - t.Fatal(err) - } - - t.Run("ServiceFunc bucket Store", func(t *testing.T) { - v, ok := sim.NodeItem(id1, testKey) - if !ok { - t.Fatal("bucket item not found") - } - s, ok := v.(string) - if !ok { - t.Fatal("bucket item value is not string") - } - if s != testValue+id1.String() { - t.Fatalf("expected %q, got %q", testValue+id1.String(), s) - } - - v, ok = sim.NodeItem(id2, testKey) - if !ok { - t.Fatal("bucket item not found") - } - s, ok = v.(string) - if !ok { - t.Fatal("bucket item value is not string") - } - if s != testValue+id2.String() { - t.Fatalf("expected %q, got %q", testValue+id2.String(), s) - } - }) - - customKey := "anotherKey" - customValue := "anotherValue" - - t.Run("SetNodeItem", func(t *testing.T) { - sim.SetNodeItem(id1, customKey, customValue) - - v, ok := sim.NodeItem(id1, customKey) - if !ok { - t.Fatal("bucket item not found") - } - s, ok := v.(string) - if !ok { - t.Fatal("bucket item value is not string") - } - if s != customValue { - t.Fatalf("expected %q, got %q", customValue, s) - } - - _, ok = sim.NodeItem(id2, customKey) - if ok { - t.Fatal("bucket item should not be found") - } - }) - - if err := sim.StopNode(id2); err != nil { - t.Fatal(err) - } - - t.Run("UpNodesItems", func(t *testing.T) { - items := sim.UpNodesItems(testKey) - - v, ok := items[id1] - if !ok { - t.Errorf("node 1 item not found") - } - s, ok := v.(string) - if !ok { - t.Fatal("node 1 item value is not string") - } - if s != testValue+id1.String() { - t.Fatalf("expected %q, got %q", testValue+id1.String(), s) - } - - _, ok = items[id2] - if ok { - t.Errorf("node 2 item should not be found") - } - }) - - t.Run("NodeItems", func(t *testing.T) { - items := sim.NodesItems(testKey) - - v, ok := items[id1] - if !ok { - t.Errorf("node 1 item not found") - } - s, ok := v.(string) - if !ok { - t.Fatal("node 1 item value is not string") - } - if s != testValue+id1.String() { - t.Fatalf("expected %q, got %q", testValue+id1.String(), s) - } - - v, ok = items[id2] - if !ok { - t.Errorf("node 2 item not found") - } - s, ok = v.(string) - if !ok { - t.Fatal("node 1 item value is not string") - } - if s != testValue+id2.String() { - t.Fatalf("expected %q, got %q", testValue+id2.String(), s) - } - }) -} diff --git a/swarm/network/simulation/events.go b/swarm/network/simulation/events.go deleted file mode 100644 index 5382045dda37..000000000000 --- a/swarm/network/simulation/events.go +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package simulation - -import ( - "context" - "sync" - - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/simulations" -) - -// PeerEvent is the type of the channel returned by Simulation.PeerEvents. -type PeerEvent struct { - // NodeID is the ID of node that the event is caught on. - NodeID enode.ID - // PeerID is the ID of the peer node that the event is caught on. - PeerID enode.ID - // Event is the event that is caught. - Event *simulations.Event - // Error is the error that may have happened during event watching. - Error error -} - -// PeerEventsFilter defines a filter on PeerEvents to exclude messages with -// defined properties. Use PeerEventsFilter methods to set required options. -type PeerEventsFilter struct { - eventType simulations.EventType - - connUp *bool - - msgReceive *bool - protocol *string - msgCode *uint64 -} - -// NewPeerEventsFilter returns a new PeerEventsFilter instance. -func NewPeerEventsFilter() *PeerEventsFilter { - return &PeerEventsFilter{} -} - -// Connect sets the filter to events when two nodes connect. -func (f *PeerEventsFilter) Connect() *PeerEventsFilter { - f.eventType = simulations.EventTypeConn - b := true - f.connUp = &b - return f -} - -// Drop sets the filter to events when two nodes disconnect. -func (f *PeerEventsFilter) Drop() *PeerEventsFilter { - f.eventType = simulations.EventTypeConn - b := false - f.connUp = &b - return f -} - -// ReceivedMessages sets the filter to only messages that are received. -func (f *PeerEventsFilter) ReceivedMessages() *PeerEventsFilter { - f.eventType = simulations.EventTypeMsg - b := true - f.msgReceive = &b - return f -} - -// SentMessages sets the filter to only messages that are sent. -func (f *PeerEventsFilter) SentMessages() *PeerEventsFilter { - f.eventType = simulations.EventTypeMsg - b := false - f.msgReceive = &b - return f -} - -// Protocol sets the filter to only one message protocol. -func (f *PeerEventsFilter) Protocol(p string) *PeerEventsFilter { - f.eventType = simulations.EventTypeMsg - f.protocol = &p - return f -} - -// MsgCode sets the filter to only one msg code. -func (f *PeerEventsFilter) MsgCode(c uint64) *PeerEventsFilter { - f.eventType = simulations.EventTypeMsg - f.msgCode = &c - return f -} - -// PeerEvents returns a channel of events that are captured by admin peerEvents -// subscription nodes with provided NodeIDs. Additional filters can be set to ignore -// events that are not relevant. -func (s *Simulation) PeerEvents(ctx context.Context, ids []enode.ID, filters ...*PeerEventsFilter) <-chan PeerEvent { - eventC := make(chan PeerEvent) - - // wait group to make sure all subscriptions to admin peerEvents are established - // before this function returns. - var subsWG sync.WaitGroup - for _, id := range ids { - s.shutdownWG.Add(1) - subsWG.Add(1) - go func(id enode.ID) { - defer s.shutdownWG.Done() - - events := make(chan *simulations.Event) - sub := s.Net.Events().Subscribe(events) - defer sub.Unsubscribe() - - subsWG.Done() - - for { - select { - case <-ctx.Done(): - if err := ctx.Err(); err != nil { - select { - case eventC <- PeerEvent{NodeID: id, Error: err}: - case <-s.Done(): - } - } - return - case <-s.Done(): - return - case e := <-events: - // ignore control events - if e.Control { - continue - } - match := len(filters) == 0 // if there are no filters match all events - for _, f := range filters { - if f.eventType == simulations.EventTypeConn && e.Conn != nil { - if *f.connUp != e.Conn.Up { - continue - } - // all connection filter parameters matched, break the loop - match = true - break - } - if f.eventType == simulations.EventTypeMsg && e.Msg != nil { - if f.msgReceive != nil && *f.msgReceive != e.Msg.Received { - continue - } - if f.protocol != nil && *f.protocol != e.Msg.Protocol { - continue - } - if f.msgCode != nil && *f.msgCode != e.Msg.Code { - continue - } - // all message filter parameters matched, break the loop - match = true - break - } - } - var peerID enode.ID - switch e.Type { - case simulations.EventTypeConn: - peerID = e.Conn.One - if peerID == id { - peerID = e.Conn.Other - } - case simulations.EventTypeMsg: - peerID = e.Msg.One - if peerID == id { - peerID = e.Msg.Other - } - } - if match { - select { - case eventC <- PeerEvent{NodeID: id, PeerID: peerID, Event: e}: - case <-ctx.Done(): - if err := ctx.Err(); err != nil { - select { - case eventC <- PeerEvent{NodeID: id, PeerID: peerID, Error: err}: - case <-s.Done(): - } - } - return - case <-s.Done(): - return - } - } - case err := <-sub.Err(): - if err != nil { - select { - case eventC <- PeerEvent{NodeID: id, Error: err}: - case <-ctx.Done(): - if err := ctx.Err(); err != nil { - select { - case eventC <- PeerEvent{NodeID: id, Error: err}: - case <-s.Done(): - } - } - return - case <-s.Done(): - return - } - } - } - } - }(id) - } - - // wait all subscriptions - subsWG.Wait() - return eventC -} diff --git a/swarm/network/simulation/events_test.go b/swarm/network/simulation/events_test.go deleted file mode 100644 index 529844816f4a..000000000000 --- a/swarm/network/simulation/events_test.go +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package simulation - -import ( - "context" - "sync" - "testing" - "time" -) - -// TestPeerEvents creates simulation, adds two nodes, -// register for peer events, connects nodes in a chain -// and waits for the number of connection events to -// be received. -func TestPeerEvents(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - _, err := sim.AddNodes(2) - if err != nil { - t.Fatal(err) - } - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - events := sim.PeerEvents(ctx, sim.NodeIDs()) - - // two nodes -> two connection events - expectedEventCount := 2 - - var wg sync.WaitGroup - wg.Add(expectedEventCount) - - go func() { - for e := range events { - if e.Error != nil { - if e.Error == context.Canceled { - return - } - t.Error(e.Error) - continue - } - wg.Done() - } - }() - - err = sim.Net.ConnectNodesChain(sim.NodeIDs()) - if err != nil { - t.Fatal(err) - } - - wg.Wait() -} - -func TestPeerEventsTimeout(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - _, err := sim.AddNodes(2) - if err != nil { - t.Fatal(err) - } - - ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) - defer cancel() - events := sim.PeerEvents(ctx, sim.NodeIDs()) - - done := make(chan struct{}) - errC := make(chan error) - go func() { - for e := range events { - if e.Error == context.Canceled { - return - } - if e.Error == context.DeadlineExceeded { - close(done) - return - } else { - errC <- e.Error - } - } - }() - - select { - case <-time.After(time.Second): - t.Fatal("no context deadline received") - case err := <-errC: - t.Fatal(err) - case <-done: - // all good, context deadline detected - } -} diff --git a/swarm/network/simulation/example_test.go b/swarm/network/simulation/example_test.go deleted file mode 100644 index 9fd52eff4b96..000000000000 --- a/swarm/network/simulation/example_test.go +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package simulation_test - -import ( - "context" - "fmt" - "sync" - "time" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" - "github.com/ubiq/go-ubiq/swarm/network" - "github.com/ubiq/go-ubiq/swarm/network/simulation" -) - -// Every node can have a Kademlia associated using the node bucket under -// BucketKeyKademlia key. This allows to use WaitTillHealthy to block until -// all nodes have the their Kademlias healthy. -func ExampleSimulation_WaitTillHealthy() { - - sim := simulation.New(map[string]simulation.ServiceFunc{ - "bzz": func(ctx *adapters.ServiceContext, b *sync.Map) (node.Service, func(), error) { - addr := network.NewAddr(ctx.Config.Node()) - hp := network.NewHiveParams() - hp.Discovery = false - config := &network.BzzConfig{ - OverlayAddr: addr.Over(), - UnderlayAddr: addr.Under(), - HiveParams: hp, - } - kad := network.NewKademlia(addr.Over(), network.NewKadParams()) - // store kademlia in node's bucket under BucketKeyKademlia - // so that it can be found by WaitTillHealthy method. - b.Store(simulation.BucketKeyKademlia, kad) - return network.NewBzz(config, kad, nil, nil, nil), nil, nil - }, - }) - defer sim.Close() - - _, err := sim.AddNodesAndConnectRing(10) - if err != nil { - // handle error properly... - panic(err) - } - - ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) - defer cancel() - ill, err := sim.WaitTillHealthy(ctx) - if err != nil { - // inspect the latest detected not healthy kademlias - for id, kad := range ill { - fmt.Println("Node", id) - fmt.Println(kad.String()) - } - // handle error... - } - - // continue with the test - -} - -// Watch all peer events in the simulation network, buy receiving from a channel. -func ExampleSimulation_PeerEvents() { - sim := simulation.New(nil) - defer sim.Close() - - events := sim.PeerEvents(context.Background(), sim.NodeIDs()) - - go func() { - for e := range events { - if e.Error != nil { - log.Error("peer event", "err", e.Error) - continue - } - log.Info("peer event", "node", e.NodeID, "peer", e.PeerID, "type", e.Event.Type) - } - }() -} - -// Detect when a nodes drop a peer. -func ExampleSimulation_PeerEvents_disconnections() { - sim := simulation.New(nil) - defer sim.Close() - - disconnections := sim.PeerEvents( - context.Background(), - sim.NodeIDs(), - simulation.NewPeerEventsFilter().Drop(), - ) - - go func() { - for d := range disconnections { - if d.Error != nil { - log.Error("peer drop", "err", d.Error) - continue - } - log.Warn("peer drop", "node", d.NodeID, "peer", d.PeerID) - } - }() -} - -// Watch multiple types of events or messages. In this case, they differ only -// by MsgCode, but filters can be set for different types or protocols, too. -func ExampleSimulation_PeerEvents_multipleFilters() { - sim := simulation.New(nil) - defer sim.Close() - - msgs := sim.PeerEvents( - context.Background(), - sim.NodeIDs(), - // Watch when bzz messages 1 and 4 are received. - simulation.NewPeerEventsFilter().ReceivedMessages().Protocol("bzz").MsgCode(1), - simulation.NewPeerEventsFilter().ReceivedMessages().Protocol("bzz").MsgCode(4), - ) - - go func() { - for m := range msgs { - if m.Error != nil { - log.Error("bzz message", "err", m.Error) - continue - } - log.Info("bzz message", "node", m.NodeID, "peer", m.PeerID) - } - }() -} diff --git a/swarm/network/simulation/http.go b/swarm/network/simulation/http.go deleted file mode 100644 index a1334921d68a..000000000000 --- a/swarm/network/simulation/http.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package simulation - -import ( - "fmt" - "net/http" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/p2p/simulations" -) - -// Package defaults. -var ( - DefaultHTTPSimAddr = ":8888" -) - -//WithServer implements the builder pattern constructor for Simulation to -//start with a HTTP server -func (s *Simulation) WithServer(addr string) *Simulation { - //assign default addr if nothing provided - if addr == "" { - addr = DefaultHTTPSimAddr - } - log.Info(fmt.Sprintf("Initializing simulation server on %s...", addr)) - //initialize the HTTP server - s.handler = simulations.NewServer(s.Net) - s.runC = make(chan struct{}) - //add swarm specific routes to the HTTP server - s.addSimulationRoutes() - s.httpSrv = &http.Server{ - Addr: addr, - Handler: s.handler, - } - go func() { - err := s.httpSrv.ListenAndServe() - if err != nil { - log.Error("Error starting the HTTP server", "error", err) - } - }() - return s -} - -//register additional HTTP routes -func (s *Simulation) addSimulationRoutes() { - s.handler.POST("/runsim", s.RunSimulation) -} - -// RunSimulation is the actual POST endpoint runner -func (s *Simulation) RunSimulation(w http.ResponseWriter, req *http.Request) { - log.Debug("RunSimulation endpoint running") - s.runC <- struct{}{} - w.WriteHeader(http.StatusOK) -} diff --git a/swarm/network/simulation/http_test.go b/swarm/network/simulation/http_test.go deleted file mode 100644 index ae17f546dfb7..000000000000 --- a/swarm/network/simulation/http_test.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package simulation - -import ( - "context" - "fmt" - "net/http" - "sync" - "testing" - "time" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" -) - -func TestSimulationWithHTTPServer(t *testing.T) { - log.Debug("Init simulation") - - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - - sim := New( - map[string]ServiceFunc{ - "noop": func(_ *adapters.ServiceContext, b *sync.Map) (node.Service, func(), error) { - return newNoopService(), nil, nil - }, - }).WithServer(DefaultHTTPSimAddr) - defer sim.Close() - log.Debug("Done.") - - _, err := sim.AddNode() - if err != nil { - t.Fatal(err) - } - - log.Debug("Starting sim round and let it time out...") - //first test that running without sending to the channel will actually - //block the simulation, so let it time out - result := sim.Run(ctx, func(ctx context.Context, sim *Simulation) error { - log.Debug("Just start the sim without any action and wait for the timeout") - //ensure with a Sleep that simulation doesn't terminate before the timeout - time.Sleep(2 * time.Second) - return nil - }) - - if result.Error != nil { - if result.Error.Error() == "context deadline exceeded" { - log.Debug("Expected timeout error received") - } else { - t.Fatal(result.Error) - } - } - - //now run it again and send the expected signal on the waiting channel, - //then close the simulation - log.Debug("Starting sim round and wait for frontend signal...") - //this time the timeout should be long enough so that it doesn't kick in too early - ctx, cancel2 := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel2() - errC := make(chan error, 1) - go triggerSimulationRun(t, errC) - result = sim.Run(ctx, func(ctx context.Context, sim *Simulation) error { - log.Debug("This run waits for the run signal from `frontend`...") - //ensure with a Sleep that simulation doesn't terminate before the signal is received - time.Sleep(2 * time.Second) - return nil - }) - if result.Error != nil { - t.Fatal(result.Error) - } - if err := <-errC; err != nil { - t.Fatal(err) - } - log.Debug("Test terminated successfully") -} - -func triggerSimulationRun(t *testing.T, errC chan error) { - //We need to first wait for the sim HTTP server to start running... - time.Sleep(2 * time.Second) - //then we can send the signal - - log.Debug("Sending run signal to simulation: POST /runsim...") - resp, err := http.Post(fmt.Sprintf("http://localhost%s/runsim", DefaultHTTPSimAddr), "application/json", nil) - if err != nil { - errC <- fmt.Errorf("Request failed: %v", err) - return - } - log.Debug("Signal sent") - if resp.StatusCode != http.StatusOK { - errC <- fmt.Errorf("err %s", resp.Status) - return - } - errC <- resp.Body.Close() -} diff --git a/swarm/network/simulation/kademlia.go b/swarm/network/simulation/kademlia.go deleted file mode 100644 index 166b37d32b34..000000000000 --- a/swarm/network/simulation/kademlia.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package simulation - -import ( - "context" - "encoding/hex" - "time" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/swarm/network" -) - -// BucketKeyKademlia is the key to be used for storing the kademlia -// instance for particular node, usually inside the ServiceFunc function. -var BucketKeyKademlia BucketKey = "kademlia" - -// WaitTillHealthy is blocking until the health of all kademlias is true. -// If error is not nil, a map of kademlia that was found not healthy is returned. -// TODO: Check correctness since change in kademlia depth calculation logic -func (s *Simulation) WaitTillHealthy(ctx context.Context) (ill map[enode.ID]*network.Kademlia, err error) { - // Prepare PeerPot map for checking Kademlia health - var ppmap map[string]*network.PeerPot - kademlias := s.kademlias() - addrs := make([][]byte, 0, len(kademlias)) - // TODO verify that all kademlias have same params - for _, k := range kademlias { - addrs = append(addrs, k.BaseAddr()) - } - ppmap = network.NewPeerPotMap(s.neighbourhoodSize, addrs) - - // Wait for healthy Kademlia on every node before checking files - ticker := time.NewTicker(200 * time.Millisecond) - defer ticker.Stop() - - ill = make(map[enode.ID]*network.Kademlia) - for { - select { - case <-ctx.Done(): - return ill, ctx.Err() - case <-ticker.C: - for k := range ill { - delete(ill, k) - } - log.Debug("kademlia health check", "addr count", len(addrs)) - for id, k := range kademlias { - //PeerPot for this node - addr := common.Bytes2Hex(k.BaseAddr()) - pp := ppmap[addr] - //call Healthy RPC - h := k.GetHealthInfo(pp) - //print info - log.Debug(k.String()) - log.Debug("kademlia", "connectNN", h.ConnectNN, "knowNN", h.KnowNN) - log.Debug("kademlia", "health", h.ConnectNN && h.KnowNN, "addr", hex.EncodeToString(k.BaseAddr()), "node", id) - log.Debug("kademlia", "ill condition", !h.ConnectNN, "addr", hex.EncodeToString(k.BaseAddr()), "node", id) - if !h.ConnectNN { - ill[id] = k - } - } - if len(ill) == 0 { - return nil, nil - } - } - } -} - -// kademlias returns all Kademlia instances that are set -// in simulation bucket. -func (s *Simulation) kademlias() (ks map[enode.ID]*network.Kademlia) { - items := s.UpNodesItems(BucketKeyKademlia) - ks = make(map[enode.ID]*network.Kademlia, len(items)) - for id, v := range items { - k, ok := v.(*network.Kademlia) - if !ok { - continue - } - ks[id] = k - } - return ks -} diff --git a/swarm/network/simulation/kademlia_test.go b/swarm/network/simulation/kademlia_test.go deleted file mode 100644 index 4efaa2f7862a..000000000000 --- a/swarm/network/simulation/kademlia_test.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package simulation - -import ( - "context" - "sync" - "testing" - "time" - - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" - "github.com/ubiq/go-ubiq/swarm/network" -) - -func TestWaitTillHealthy(t *testing.T) { - t.Skip("WaitTillHealthy depends on discovery, which relies on a reliable SuggestPeer, which is not reliable") - - sim := New(map[string]ServiceFunc{ - "bzz": func(ctx *adapters.ServiceContext, b *sync.Map) (node.Service, func(), error) { - addr := network.NewAddr(ctx.Config.Node()) - hp := network.NewHiveParams() - config := &network.BzzConfig{ - OverlayAddr: addr.Over(), - UnderlayAddr: addr.Under(), - HiveParams: hp, - } - kad := network.NewKademlia(addr.Over(), network.NewKadParams()) - // store kademlia in node's bucket under BucketKeyKademlia - // so that it can be found by WaitTillHealthy method. - b.Store(BucketKeyKademlia, kad) - return network.NewBzz(config, kad, nil, nil, nil), nil, nil - }, - }) - defer sim.Close() - - _, err := sim.AddNodesAndConnectRing(10) - if err != nil { - t.Fatal(err) - } - - ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) - defer cancel() - ill, err := sim.WaitTillHealthy(ctx) - if err != nil { - for id, kad := range ill { - t.Log("Node", id) - t.Log(kad.String()) - } - if err != nil { - t.Fatal(err) - } - } -} diff --git a/swarm/network/simulation/node.go b/swarm/network/simulation/node.go deleted file mode 100644 index dba8a6197a2d..000000000000 --- a/swarm/network/simulation/node.go +++ /dev/null @@ -1,309 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package simulation - -import ( - "encoding/json" - "errors" - "io/ioutil" - "math/rand" - "os" - "time" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/simulations" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" -) - -// NodeIDs returns NodeIDs for all nodes in the network. -func (s *Simulation) NodeIDs() (ids []enode.ID) { - nodes := s.Net.GetNodes() - ids = make([]enode.ID, len(nodes)) - for i, node := range nodes { - ids[i] = node.ID() - } - return ids -} - -// UpNodeIDs returns NodeIDs for nodes that are up in the network. -func (s *Simulation) UpNodeIDs() (ids []enode.ID) { - nodes := s.Net.GetNodes() - for _, node := range nodes { - if node.Up() { - ids = append(ids, node.ID()) - } - } - return ids -} - -// DownNodeIDs returns NodeIDs for nodes that are stopped in the network. -func (s *Simulation) DownNodeIDs() (ids []enode.ID) { - nodes := s.Net.GetNodes() - for _, node := range nodes { - if !node.Up() { - ids = append(ids, node.ID()) - } - } - return ids -} - -// AddNodeOption defines the option that can be passed -// to Simulation.AddNode method. -type AddNodeOption func(*adapters.NodeConfig) - -// AddNodeWithMsgEvents sets the EnableMsgEvents option -// to NodeConfig. -func AddNodeWithMsgEvents(enable bool) AddNodeOption { - return func(o *adapters.NodeConfig) { - o.EnableMsgEvents = enable - } -} - -// AddNodeWithService specifies a service that should be -// started on a node. This option can be repeated as variadic -// argument toe AddNode and other add node related methods. -// If AddNodeWithService is not specified, all services will be started. -func AddNodeWithService(serviceName string) AddNodeOption { - return func(o *adapters.NodeConfig) { - o.Services = append(o.Services, serviceName) - } -} - -// AddNode creates a new node with random configuration, -// applies provided options to the config and adds the node to network. -// By default all services will be started on a node. If one or more -// AddNodeWithService option are provided, only specified services will be started. -func (s *Simulation) AddNode(opts ...AddNodeOption) (id enode.ID, err error) { - conf := adapters.RandomNodeConfig() - for _, o := range opts { - o(conf) - } - if len(conf.Services) == 0 { - conf.Services = s.serviceNames - } - node, err := s.Net.NewNodeWithConfig(conf) - if err != nil { - return id, err - } - return node.ID(), s.Net.Start(node.ID()) -} - -// AddNodes creates new nodes with random configurations, -// applies provided options to the config and adds nodes to network. -func (s *Simulation) AddNodes(count int, opts ...AddNodeOption) (ids []enode.ID, err error) { - ids = make([]enode.ID, 0, count) - for i := 0; i < count; i++ { - id, err := s.AddNode(opts...) - if err != nil { - return nil, err - } - ids = append(ids, id) - } - return ids, nil -} - -// AddNodesAndConnectFull is a helpper method that combines -// AddNodes and ConnectNodesFull. Only new nodes will be connected. -func (s *Simulation) AddNodesAndConnectFull(count int, opts ...AddNodeOption) (ids []enode.ID, err error) { - if count < 2 { - return nil, errors.New("count of nodes must be at least 2") - } - ids, err = s.AddNodes(count, opts...) - if err != nil { - return nil, err - } - err = s.Net.ConnectNodesFull(ids) - if err != nil { - return nil, err - } - return ids, nil -} - -// AddNodesAndConnectChain is a helpper method that combines -// AddNodes and ConnectNodesChain. The chain will be continued from the last -// added node, if there is one in simulation using ConnectToLastNode method. -func (s *Simulation) AddNodesAndConnectChain(count int, opts ...AddNodeOption) (ids []enode.ID, err error) { - if count < 2 { - return nil, errors.New("count of nodes must be at least 2") - } - id, err := s.AddNode(opts...) - if err != nil { - return nil, err - } - err = s.Net.ConnectToLastNode(id) - if err != nil { - return nil, err - } - ids, err = s.AddNodes(count-1, opts...) - if err != nil { - return nil, err - } - ids = append([]enode.ID{id}, ids...) - err = s.Net.ConnectNodesChain(ids) - if err != nil { - return nil, err - } - return ids, nil -} - -// AddNodesAndConnectRing is a helpper method that combines -// AddNodes and ConnectNodesRing. -func (s *Simulation) AddNodesAndConnectRing(count int, opts ...AddNodeOption) (ids []enode.ID, err error) { - if count < 2 { - return nil, errors.New("count of nodes must be at least 2") - } - ids, err = s.AddNodes(count, opts...) - if err != nil { - return nil, err - } - err = s.Net.ConnectNodesRing(ids) - if err != nil { - return nil, err - } - return ids, nil -} - -// AddNodesAndConnectStar is a helpper method that combines -// AddNodes and ConnectNodesStar. -func (s *Simulation) AddNodesAndConnectStar(count int, opts ...AddNodeOption) (ids []enode.ID, err error) { - if count < 2 { - return nil, errors.New("count of nodes must be at least 2") - } - ids, err = s.AddNodes(count, opts...) - if err != nil { - return nil, err - } - err = s.Net.ConnectNodesStar(ids[1:], ids[0]) - if err != nil { - return nil, err - } - return ids, nil -} - -// UploadSnapshot uploads a snapshot to the simulation -// This method tries to open the json file provided, applies the config to all nodes -// and then loads the snapshot into the Simulation network -func (s *Simulation) UploadSnapshot(snapshotFile string, opts ...AddNodeOption) error { - f, err := os.Open(snapshotFile) - if err != nil { - return err - } - defer func() { - err := f.Close() - if err != nil { - log.Error("Error closing snapshot file", "err", err) - } - }() - jsonbyte, err := ioutil.ReadAll(f) - if err != nil { - return err - } - var snap simulations.Snapshot - err = json.Unmarshal(jsonbyte, &snap) - if err != nil { - return err - } - - //the snapshot probably has the property EnableMsgEvents not set - //just in case, set it to true! - //(we need this to wait for messages before uploading) - for _, n := range snap.Nodes { - n.Node.Config.EnableMsgEvents = true - n.Node.Config.Services = s.serviceNames - for _, o := range opts { - o(n.Node.Config) - } - } - - log.Info("Waiting for p2p connections to be established...") - - //now we can load the snapshot - err = s.Net.Load(&snap) - if err != nil { - return err - } - log.Info("Snapshot loaded") - return nil -} - -// StartNode starts a node by NodeID. -func (s *Simulation) StartNode(id enode.ID) (err error) { - return s.Net.Start(id) -} - -// StartRandomNode starts a random node. -func (s *Simulation) StartRandomNode() (id enode.ID, err error) { - n := s.Net.GetRandomDownNode() - if n == nil { - return id, ErrNodeNotFound - } - return n.ID(), s.Net.Start(n.ID()) -} - -// StartRandomNodes starts random nodes. -func (s *Simulation) StartRandomNodes(count int) (ids []enode.ID, err error) { - ids = make([]enode.ID, 0, count) - for i := 0; i < count; i++ { - n := s.Net.GetRandomDownNode() - if n == nil { - return nil, ErrNodeNotFound - } - err = s.Net.Start(n.ID()) - if err != nil { - return nil, err - } - ids = append(ids, n.ID()) - } - return ids, nil -} - -// StopNode stops a node by NodeID. -func (s *Simulation) StopNode(id enode.ID) (err error) { - return s.Net.Stop(id) -} - -// StopRandomNode stops a random node. -func (s *Simulation) StopRandomNode() (id enode.ID, err error) { - n := s.Net.GetRandomUpNode() - if n == nil { - return id, ErrNodeNotFound - } - return n.ID(), s.Net.Stop(n.ID()) -} - -// StopRandomNodes stops random nodes. -func (s *Simulation) StopRandomNodes(count int) (ids []enode.ID, err error) { - ids = make([]enode.ID, 0, count) - for i := 0; i < count; i++ { - n := s.Net.GetRandomUpNode() - if n == nil { - return nil, ErrNodeNotFound - } - err = s.Net.Stop(n.ID()) - if err != nil { - return nil, err - } - ids = append(ids, n.ID()) - } - return ids, nil -} - -// seed the random generator for Simulation.randomNode. -func init() { - rand.Seed(time.Now().UnixNano()) -} diff --git a/swarm/network/simulation/node_test.go b/swarm/network/simulation/node_test.go deleted file mode 100644 index a2cbbe7a1066..000000000000 --- a/swarm/network/simulation/node_test.go +++ /dev/null @@ -1,444 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package simulation - -import ( - "context" - "fmt" - "sync" - "testing" - "time" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/simulations" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" - "github.com/ubiq/go-ubiq/swarm/network" -) - -func TestUpDownNodeIDs(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - ids, err := sim.AddNodes(10) - if err != nil { - t.Fatal(err) - } - - gotIDs := sim.NodeIDs() - - if !equalNodeIDs(ids, gotIDs) { - t.Error("returned nodes are not equal to added ones") - } - - stoppedIDs, err := sim.StopRandomNodes(3) - if err != nil { - t.Fatal(err) - } - - gotIDs = sim.UpNodeIDs() - - for _, id := range gotIDs { - if !sim.Net.GetNode(id).Up() { - t.Errorf("node %s should not be down", id) - } - } - - if !equalNodeIDs(ids, append(gotIDs, stoppedIDs...)) { - t.Error("returned nodes are not equal to added ones") - } - - gotIDs = sim.DownNodeIDs() - - for _, id := range gotIDs { - if sim.Net.GetNode(id).Up() { - t.Errorf("node %s should not be up", id) - } - } - - if !equalNodeIDs(stoppedIDs, gotIDs) { - t.Error("returned nodes are not equal to the stopped ones") - } -} - -func equalNodeIDs(one, other []enode.ID) bool { - if len(one) != len(other) { - return false - } - var count int - for _, a := range one { - var found bool - for _, b := range other { - if a == b { - found = true - break - } - } - if found { - count++ - } else { - return false - } - } - return count == len(one) -} - -func TestAddNode(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - id, err := sim.AddNode() - if err != nil { - t.Fatal(err) - } - - n := sim.Net.GetNode(id) - if n == nil { - t.Fatal("node not found") - } - - if !n.Up() { - t.Error("node not started") - } -} - -func TestAddNodeWithMsgEvents(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - id, err := sim.AddNode(AddNodeWithMsgEvents(true)) - if err != nil { - t.Fatal(err) - } - - if !sim.Net.GetNode(id).Config.EnableMsgEvents { - t.Error("EnableMsgEvents is false") - } - - id, err = sim.AddNode(AddNodeWithMsgEvents(false)) - if err != nil { - t.Fatal(err) - } - - if sim.Net.GetNode(id).Config.EnableMsgEvents { - t.Error("EnableMsgEvents is true") - } -} - -func TestAddNodeWithService(t *testing.T) { - sim := New(map[string]ServiceFunc{ - "noop1": noopServiceFunc, - "noop2": noopServiceFunc, - }) - defer sim.Close() - - id, err := sim.AddNode(AddNodeWithService("noop1")) - if err != nil { - t.Fatal(err) - } - - n := sim.Net.GetNode(id).Node.(*adapters.SimNode) - if n.Service("noop1") == nil { - t.Error("service noop1 not found on node") - } - if n.Service("noop2") != nil { - t.Error("service noop2 should not be found on node") - } -} - -func TestAddNodeMultipleServices(t *testing.T) { - sim := New(map[string]ServiceFunc{ - "noop1": noopServiceFunc, - "noop2": noopService2Func, - }) - defer sim.Close() - - id, err := sim.AddNode() - if err != nil { - t.Fatal(err) - } - - n := sim.Net.GetNode(id).Node.(*adapters.SimNode) - if n.Service("noop1") == nil { - t.Error("service noop1 not found on node") - } - if n.Service("noop2") == nil { - t.Error("service noop2 not found on node") - } -} - -func TestAddNodeDuplicateServiceError(t *testing.T) { - sim := New(map[string]ServiceFunc{ - "noop1": noopServiceFunc, - "noop2": noopServiceFunc, - }) - defer sim.Close() - - wantErr := "duplicate service: *simulation.noopService" - _, err := sim.AddNode() - if err.Error() != wantErr { - t.Errorf("got error %q, want %q", err, wantErr) - } -} - -func TestAddNodes(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - nodesCount := 12 - - ids, err := sim.AddNodes(nodesCount) - if err != nil { - t.Fatal(err) - } - - count := len(ids) - if count != nodesCount { - t.Errorf("expected %v nodes, got %v", nodesCount, count) - } - - count = len(sim.Net.GetNodes()) - if count != nodesCount { - t.Errorf("expected %v nodes, got %v", nodesCount, count) - } -} - -func TestAddNodesAndConnectFull(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - n := 12 - - ids, err := sim.AddNodesAndConnectFull(n) - if err != nil { - t.Fatal(err) - } - - simulations.VerifyFull(t, sim.Net, ids) -} - -func TestAddNodesAndConnectChain(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - _, err := sim.AddNodesAndConnectChain(12) - if err != nil { - t.Fatal(err) - } - - // add another set of nodes to test - // if two chains are connected - _, err = sim.AddNodesAndConnectChain(7) - if err != nil { - t.Fatal(err) - } - - simulations.VerifyChain(t, sim.Net, sim.UpNodeIDs()) -} - -func TestAddNodesAndConnectRing(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - ids, err := sim.AddNodesAndConnectRing(12) - if err != nil { - t.Fatal(err) - } - - simulations.VerifyRing(t, sim.Net, ids) -} - -func TestAddNodesAndConnectStar(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - ids, err := sim.AddNodesAndConnectStar(12) - if err != nil { - t.Fatal(err) - } - - simulations.VerifyStar(t, sim.Net, ids, 0) -} - -//To test that uploading a snapshot works -func TestUploadSnapshot(t *testing.T) { - log.Debug("Creating simulation") - s := New(map[string]ServiceFunc{ - "bzz": func(ctx *adapters.ServiceContext, b *sync.Map) (node.Service, func(), error) { - addr := network.NewAddr(ctx.Config.Node()) - hp := network.NewHiveParams() - hp.Discovery = false - config := &network.BzzConfig{ - OverlayAddr: addr.Over(), - UnderlayAddr: addr.Under(), - HiveParams: hp, - } - kad := network.NewKademlia(addr.Over(), network.NewKadParams()) - return network.NewBzz(config, kad, nil, nil, nil), nil, nil - }, - }) - defer s.Close() - - nodeCount := 16 - log.Debug("Uploading snapshot") - err := s.UploadSnapshot(fmt.Sprintf("../stream/testing/snapshot_%d.json", nodeCount)) - if err != nil { - t.Fatalf("Error uploading snapshot to simulation network: %v", err) - } - - ctx := context.Background() - log.Debug("Starting simulation...") - s.Run(ctx, func(ctx context.Context, sim *Simulation) error { - log.Debug("Checking") - nodes := sim.UpNodeIDs() - if len(nodes) != nodeCount { - t.Fatal("Simulation network node number doesn't match snapshot node number") - } - return nil - }) - log.Debug("Done.") -} - -func TestStartStopNode(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - id, err := sim.AddNode() - if err != nil { - t.Fatal(err) - } - - n := sim.Net.GetNode(id) - if n == nil { - t.Fatal("node not found") - } - if !n.Up() { - t.Error("node not started") - } - - err = sim.StopNode(id) - if err != nil { - t.Fatal(err) - } - if n.Up() { - t.Error("node not stopped") - } - - waitForPeerEventPropagation() - - err = sim.StartNode(id) - if err != nil { - t.Fatal(err) - } - if !n.Up() { - t.Error("node not started") - } -} - -func TestStartStopRandomNode(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - _, err := sim.AddNodes(3) - if err != nil { - t.Fatal(err) - } - - id, err := sim.StopRandomNode() - if err != nil { - t.Fatal(err) - } - - n := sim.Net.GetNode(id) - if n == nil { - t.Fatal("node not found") - } - if n.Up() { - t.Error("node not stopped") - } - - id2, err := sim.StopRandomNode() - if err != nil { - t.Fatal(err) - } - - waitForPeerEventPropagation() - - idStarted, err := sim.StartRandomNode() - if err != nil { - t.Fatal(err) - } - - if idStarted != id && idStarted != id2 { - t.Error("unexpected started node ID") - } -} - -func TestStartStopRandomNodes(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - _, err := sim.AddNodes(10) - if err != nil { - t.Fatal(err) - } - - ids, err := sim.StopRandomNodes(3) - if err != nil { - t.Fatal(err) - } - - for _, id := range ids { - n := sim.Net.GetNode(id) - if n == nil { - t.Fatal("node not found") - } - if n.Up() { - t.Error("node not stopped") - } - } - - waitForPeerEventPropagation() - - ids, err = sim.StartRandomNodes(2) - if err != nil { - t.Fatal(err) - } - - for _, id := range ids { - n := sim.Net.GetNode(id) - if n == nil { - t.Fatal("node not found") - } - if !n.Up() { - t.Error("node not started") - } - } -} - -func waitForPeerEventPropagation() { - // Sleep here to ensure that Network.watchPeerEvents defer function - // has set the `node.Up() = false` before we start the node again. - // - // The same node is stopped and started again, and upon start - // watchPeerEvents is started in a goroutine. If the node is stopped - // and then very quickly started, that goroutine may be scheduled later - // then start and force `node.Up() = false` in its defer function. - // This will make this test unreliable. - time.Sleep(1 * time.Second) -} diff --git a/swarm/network/simulation/service.go b/swarm/network/simulation/service.go deleted file mode 100644 index e8fa655a5bf2..000000000000 --- a/swarm/network/simulation/service.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package simulation - -import ( - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" -) - -// Service returns a single Service by name on a particular node -// with provided id. -func (s *Simulation) Service(name string, id enode.ID) node.Service { - simNode, ok := s.Net.GetNode(id).Node.(*adapters.SimNode) - if !ok { - return nil - } - services := simNode.ServiceMap() - if len(services) == 0 { - return nil - } - return services[name] -} - -// RandomService returns a single Service by name on a -// randomly chosen node that is up. -func (s *Simulation) RandomService(name string) node.Service { - n := s.Net.GetRandomUpNode().Node.(*adapters.SimNode) - if n == nil { - return nil - } - return n.Service(name) -} - -// Services returns all services with a provided name -// from nodes that are up. -func (s *Simulation) Services(name string) (services map[enode.ID]node.Service) { - nodes := s.Net.GetNodes() - services = make(map[enode.ID]node.Service) - for _, node := range nodes { - if !node.Up() { - continue - } - simNode, ok := node.Node.(*adapters.SimNode) - if !ok { - continue - } - services[node.ID()] = simNode.Service(name) - } - return services -} diff --git a/swarm/network/simulation/service_test.go b/swarm/network/simulation/service_test.go deleted file mode 100644 index 23b0d86f243f..000000000000 --- a/swarm/network/simulation/service_test.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package simulation - -import ( - "testing" -) - -func TestService(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - id, err := sim.AddNode() - if err != nil { - t.Fatal(err) - } - - _, ok := sim.Service("noop", id).(*noopService) - if !ok { - t.Fatalf("service is not of %T type", &noopService{}) - } - - _, ok = sim.RandomService("noop").(*noopService) - if !ok { - t.Fatalf("service is not of %T type", &noopService{}) - } - - _, ok = sim.Services("noop")[id].(*noopService) - if !ok { - t.Fatalf("service is not of %T type", &noopService{}) - } -} diff --git a/swarm/network/simulation/simulation.go b/swarm/network/simulation/simulation.go deleted file mode 100644 index 9d7acbd1b3bb..000000000000 --- a/swarm/network/simulation/simulation.go +++ /dev/null @@ -1,215 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package simulation - -import ( - "context" - "errors" - "net/http" - "sync" - "time" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/simulations" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" - "github.com/ubiq/go-ubiq/swarm/network" -) - -// Common errors that are returned by functions in this package. -var ( - ErrNodeNotFound = errors.New("node not found") -) - -// Simulation provides methods on network, nodes and services -// to manage them. -type Simulation struct { - // Net is exposed as a way to access lower level functionalities - // of p2p/simulations.Network. - Net *simulations.Network - - serviceNames []string - cleanupFuncs []func() - buckets map[enode.ID]*sync.Map - shutdownWG sync.WaitGroup - done chan struct{} - mu sync.RWMutex - neighbourhoodSize int - - httpSrv *http.Server //attach a HTTP server via SimulationOptions - handler *simulations.Server //HTTP handler for the server - runC chan struct{} //channel where frontend signals it is ready -} - -// ServiceFunc is used in New to declare new service constructor. -// The first argument provides ServiceContext from the adapters package -// giving for example the access to NodeID. Second argument is the sync.Map -// where all "global" state related to the service should be kept. -// All cleanups needed for constructed service and any other constructed -// objects should ne provided in a single returned cleanup function. -// Returned cleanup function will be called by Close function -// after network shutdown. -type ServiceFunc func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) - -// New creates a new simulation instance -// Services map must have unique keys as service names and -// every ServiceFunc must return a node.Service of the unique type. -// This restriction is required by node.Node.Start() function -// which is used to start node.Service returned by ServiceFunc. -func New(services map[string]ServiceFunc) (s *Simulation) { - s = &Simulation{ - buckets: make(map[enode.ID]*sync.Map), - done: make(chan struct{}), - neighbourhoodSize: network.NewKadParams().NeighbourhoodSize, - } - - adapterServices := make(map[string]adapters.ServiceFunc, len(services)) - for name, serviceFunc := range services { - // Scope this variables correctly - // as they will be in the adapterServices[name] function accessed later. - name, serviceFunc := name, serviceFunc - s.serviceNames = append(s.serviceNames, name) - adapterServices[name] = func(ctx *adapters.ServiceContext) (node.Service, error) { - b := new(sync.Map) - service, cleanup, err := serviceFunc(ctx, b) - if err != nil { - return nil, err - } - s.mu.Lock() - defer s.mu.Unlock() - if cleanup != nil { - s.cleanupFuncs = append(s.cleanupFuncs, cleanup) - } - s.buckets[ctx.Config.ID] = b - return service, nil - } - } - - s.Net = simulations.NewNetwork( - adapters.NewTCPAdapter(adapterServices), - &simulations.NetworkConfig{ID: "0"}, - ) - - return s -} - -// RunFunc is the function that will be called -// on Simulation.Run method call. -type RunFunc func(context.Context, *Simulation) error - -// Result is the returned value of Simulation.Run method. -type Result struct { - Duration time.Duration - Error error -} - -// Run calls the RunFunc function while taking care of -// cancellation provided through the Context. -func (s *Simulation) Run(ctx context.Context, f RunFunc) (r Result) { - //if the option is set to run a HTTP server with the simulation, - //init the server and start it - start := time.Now() - if s.httpSrv != nil { - log.Info("Waiting for frontend to be ready...(send POST /runsim to HTTP server)") - //wait for the frontend to connect - select { - case <-s.runC: - case <-ctx.Done(): - return Result{ - Duration: time.Since(start), - Error: ctx.Err(), - } - } - log.Info("Received signal from frontend - starting simulation run.") - } - errc := make(chan error) - quit := make(chan struct{}) - defer close(quit) - go func() { - select { - case errc <- f(ctx, s): - case <-quit: - } - }() - var err error - select { - case <-ctx.Done(): - err = ctx.Err() - case err = <-errc: - } - return Result{ - Duration: time.Since(start), - Error: err, - } -} - -// Maximal number of parallel calls to cleanup functions on -// Simulation.Close. -var maxParallelCleanups = 10 - -// Close calls all cleanup functions that are returned by -// ServiceFunc, waits for all of them to finish and other -// functions that explicitly block shutdownWG -// (like Simulation.PeerEvents) and shuts down the network -// at the end. It is used to clean all resources from the -// simulation. -func (s *Simulation) Close() { - close(s.done) - - sem := make(chan struct{}, maxParallelCleanups) - s.mu.RLock() - cleanupFuncs := make([]func(), len(s.cleanupFuncs)) - for i, f := range s.cleanupFuncs { - if f != nil { - cleanupFuncs[i] = f - } - } - s.mu.RUnlock() - var cleanupWG sync.WaitGroup - for _, cleanup := range cleanupFuncs { - cleanupWG.Add(1) - sem <- struct{}{} - go func(cleanup func()) { - defer cleanupWG.Done() - defer func() { <-sem }() - - cleanup() - }(cleanup) - } - cleanupWG.Wait() - - if s.httpSrv != nil { - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - err := s.httpSrv.Shutdown(ctx) - if err != nil { - log.Error("Error shutting down HTTP server!", "err", err) - } - close(s.runC) - } - - s.shutdownWG.Wait() - s.Net.Shutdown() -} - -// Done returns a channel that is closed when the simulation -// is closed by Close method. It is useful for signaling termination -// of all possible goroutines that are created within the test. -func (s *Simulation) Done() <-chan struct{} { - return s.done -} diff --git a/swarm/network/simulation/simulation_test.go b/swarm/network/simulation/simulation_test.go deleted file mode 100644 index 0213d4b3e86d..000000000000 --- a/swarm/network/simulation/simulation_test.go +++ /dev/null @@ -1,203 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package simulation - -import ( - "context" - "errors" - "flag" - "sync" - "testing" - "time" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p/simulations" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" - "github.com/mattn/go-colorable" -) - -var ( - loglevel = flag.Int("loglevel", 2, "verbosity of logs") -) - -func init() { - flag.Parse() - log.PrintOrigins(true) - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))) -} - -// TestRun tests if Run method calls RunFunc and if it handles context properly. -func TestRun(t *testing.T) { - sim := New(noopServiceFuncMap) - defer sim.Close() - - t.Run("call", func(t *testing.T) { - expect := "something" - var got string - r := sim.Run(context.Background(), func(ctx context.Context, sim *Simulation) error { - got = expect - return nil - }) - - if r.Error != nil { - t.Errorf("unexpected error: %v", r.Error) - } - if got != expect { - t.Errorf("expected %q, got %q", expect, got) - } - }) - - t.Run("cancellation", func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond) - defer cancel() - - r := sim.Run(ctx, func(ctx context.Context, sim *Simulation) error { - time.Sleep(time.Second) - return nil - }) - - if r.Error != context.DeadlineExceeded { - t.Errorf("unexpected error: %v", r.Error) - } - }) - - t.Run("context value and duration", func(t *testing.T) { - ctx := context.WithValue(context.Background(), "hey", "there") - sleep := 50 * time.Millisecond - - r := sim.Run(ctx, func(ctx context.Context, sim *Simulation) error { - if ctx.Value("hey") != "there" { - return errors.New("expected context value not passed") - } - time.Sleep(sleep) - return nil - }) - - if r.Error != nil { - t.Errorf("unexpected error: %v", r.Error) - } - if r.Duration < sleep { - t.Errorf("reported run duration less then expected: %s", r.Duration) - } - }) -} - -// TestClose tests are Close method triggers all close functions and are all nodes not up anymore. -func TestClose(t *testing.T) { - var mu sync.Mutex - var cleanupCount int - - sleep := 50 * time.Millisecond - - sim := New(map[string]ServiceFunc{ - "noop": func(ctx *adapters.ServiceContext, b *sync.Map) (node.Service, func(), error) { - return newNoopService(), func() { - time.Sleep(sleep) - mu.Lock() - defer mu.Unlock() - cleanupCount++ - }, nil - }, - }) - - nodeCount := 30 - - _, err := sim.AddNodes(nodeCount) - if err != nil { - t.Fatal(err) - } - - var upNodeCount int - for _, n := range sim.Net.GetNodes() { - if n.Up() { - upNodeCount++ - } - } - if upNodeCount != nodeCount { - t.Errorf("all nodes should be up, insted only %v are up", upNodeCount) - } - - sim.Close() - - if cleanupCount != nodeCount { - t.Errorf("number of cleanups expected %v, got %v", nodeCount, cleanupCount) - } - - upNodeCount = 0 - for _, n := range sim.Net.GetNodes() { - if n.Up() { - upNodeCount++ - } - } - if upNodeCount != 0 { - t.Errorf("all nodes should be down, insted %v are up", upNodeCount) - } -} - -// TestDone checks if Close method triggers the closing of done channel. -func TestDone(t *testing.T) { - sim := New(noopServiceFuncMap) - sleep := 50 * time.Millisecond - timeout := 2 * time.Second - - start := time.Now() - go func() { - time.Sleep(sleep) - sim.Close() - }() - - select { - case <-time.After(timeout): - t.Error("done channel closing timed out") - case <-sim.Done(): - if d := time.Since(start); d < sleep { - t.Errorf("done channel closed sooner then expected: %s", d) - } - } -} - -// a helper map for usual services that do not do anything -var noopServiceFuncMap = map[string]ServiceFunc{ - "noop": noopServiceFunc, -} - -// a helper function for most basic noop service -func noopServiceFunc(_ *adapters.ServiceContext, _ *sync.Map) (node.Service, func(), error) { - return newNoopService(), nil, nil -} - -func newNoopService() node.Service { - return &noopService{} -} - -// a helper function for most basic Noop service -// of a different type then NoopService to test -// multiple services on one node. -func noopService2Func(_ *adapters.ServiceContext, _ *sync.Map) (node.Service, func(), error) { - return new(noopService2), nil, nil -} - -// NoopService2 is the service that does not do anything -// but implements node.Service interface. -type noopService2 struct { - simulations.NoopService -} - -type noopService struct { - simulations.NoopService -} diff --git a/swarm/network/simulations/discovery/discovery.go b/swarm/network/simulations/discovery/discovery.go deleted file mode 100644 index a6ff5fd45e67..000000000000 --- a/swarm/network/simulations/discovery/discovery.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discovery diff --git a/swarm/network/simulations/discovery/discovery_test.go b/swarm/network/simulations/discovery/discovery_test.go deleted file mode 100644 index f07013c419bf..000000000000 --- a/swarm/network/simulations/discovery/discovery_test.go +++ /dev/null @@ -1,527 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package discovery - -import ( - "context" - "flag" - "fmt" - "io/ioutil" - "os" - "path" - "strings" - "testing" - "time" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/simulations" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" - "github.com/ubiq/go-ubiq/swarm/network" - "github.com/ubiq/go-ubiq/swarm/state" - colorable "github.com/mattn/go-colorable" -) - -// serviceName is used with the exec adapter so the exec'd binary knows which -// service to execute -const serviceName = "discovery" -const testNeighbourhoodSize = 2 -const discoveryPersistenceDatadir = "discovery_persistence_test_store" - -var discoveryPersistencePath = path.Join(os.TempDir(), discoveryPersistenceDatadir) -var discoveryEnabled = true -var persistenceEnabled = false - -var services = adapters.Services{ - serviceName: newService, -} - -func cleanDbStores() error { - entries, err := ioutil.ReadDir(os.TempDir()) - if err != nil { - return err - } - - for _, f := range entries { - if strings.HasPrefix(f.Name(), discoveryPersistenceDatadir) { - os.RemoveAll(path.Join(os.TempDir(), f.Name())) - } - } - return nil - -} - -func getDbStore(nodeID string) (*state.DBStore, error) { - if _, err := os.Stat(discoveryPersistencePath + "_" + nodeID); os.IsNotExist(err) { - log.Info(fmt.Sprintf("directory for nodeID %s does not exist. creating...", nodeID)) - ioutil.TempDir("", discoveryPersistencePath+"_"+nodeID) - } - log.Info(fmt.Sprintf("opening storage directory for nodeID %s", nodeID)) - store, err := state.NewDBStore(discoveryPersistencePath + "_" + nodeID) - if err != nil { - return nil, err - } - return store, nil -} - -var ( - nodeCount = flag.Int("nodes", 32, "number of nodes to create (default 32)") - initCount = flag.Int("conns", 1, "number of originally connected peers (default 1)") - loglevel = flag.Int("loglevel", 3, "verbosity of logs") - rawlog = flag.Bool("rawlog", false, "remove terminal formatting from logs") -) - -func init() { - flag.Parse() - // register the discovery service which will run as a devp2p - // protocol when using the exec adapter - adapters.RegisterServices(services) - - log.PrintOrigins(true) - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(!*rawlog)))) -} - -// Benchmarks to test the average time it takes for an N-node ring -// to full a healthy kademlia topology -func BenchmarkDiscovery_8_1(b *testing.B) { benchmarkDiscovery(b, 8, 1) } -func BenchmarkDiscovery_16_1(b *testing.B) { benchmarkDiscovery(b, 16, 1) } -func BenchmarkDiscovery_32_1(b *testing.B) { benchmarkDiscovery(b, 32, 1) } -func BenchmarkDiscovery_64_1(b *testing.B) { benchmarkDiscovery(b, 64, 1) } -func BenchmarkDiscovery_128_1(b *testing.B) { benchmarkDiscovery(b, 128, 1) } -func BenchmarkDiscovery_256_1(b *testing.B) { benchmarkDiscovery(b, 256, 1) } - -func BenchmarkDiscovery_8_2(b *testing.B) { benchmarkDiscovery(b, 8, 2) } -func BenchmarkDiscovery_16_2(b *testing.B) { benchmarkDiscovery(b, 16, 2) } -func BenchmarkDiscovery_32_2(b *testing.B) { benchmarkDiscovery(b, 32, 2) } -func BenchmarkDiscovery_64_2(b *testing.B) { benchmarkDiscovery(b, 64, 2) } -func BenchmarkDiscovery_128_2(b *testing.B) { benchmarkDiscovery(b, 128, 2) } -func BenchmarkDiscovery_256_2(b *testing.B) { benchmarkDiscovery(b, 256, 2) } - -func BenchmarkDiscovery_8_4(b *testing.B) { benchmarkDiscovery(b, 8, 4) } -func BenchmarkDiscovery_16_4(b *testing.B) { benchmarkDiscovery(b, 16, 4) } -func BenchmarkDiscovery_32_4(b *testing.B) { benchmarkDiscovery(b, 32, 4) } -func BenchmarkDiscovery_64_4(b *testing.B) { benchmarkDiscovery(b, 64, 4) } -func BenchmarkDiscovery_128_4(b *testing.B) { benchmarkDiscovery(b, 128, 4) } -func BenchmarkDiscovery_256_4(b *testing.B) { benchmarkDiscovery(b, 256, 4) } - -func TestDiscoverySimulationExecAdapter(t *testing.T) { - testDiscoverySimulationExecAdapter(t, *nodeCount, *initCount) -} - -func testDiscoverySimulationExecAdapter(t *testing.T, nodes, conns int) { - baseDir, err := ioutil.TempDir("", "swarm-test") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(baseDir) - testDiscoverySimulation(t, nodes, conns, adapters.NewExecAdapter(baseDir)) -} - -func TestDiscoverySimulationSimAdapter(t *testing.T) { - testDiscoverySimulationSimAdapter(t, *nodeCount, *initCount) -} - -func TestDiscoveryPersistenceSimulationSimAdapter(t *testing.T) { - testDiscoveryPersistenceSimulationSimAdapter(t, *nodeCount, *initCount) -} - -func testDiscoveryPersistenceSimulationSimAdapter(t *testing.T, nodes, conns int) { - testDiscoveryPersistenceSimulation(t, nodes, conns, adapters.NewSimAdapter(services)) -} - -func testDiscoverySimulationSimAdapter(t *testing.T, nodes, conns int) { - testDiscoverySimulation(t, nodes, conns, adapters.NewSimAdapter(services)) -} - -func testDiscoverySimulation(t *testing.T, nodes, conns int, adapter adapters.NodeAdapter) { - startedAt := time.Now() - result, err := discoverySimulation(nodes, conns, adapter) - if err != nil { - t.Fatalf("Setting up simulation failed: %v", err) - } - if result.Error != nil { - t.Fatalf("Simulation failed: %s", result.Error) - } - t.Logf("Simulation with %d nodes passed in %s", nodes, result.FinishedAt.Sub(result.StartedAt)) - var min, max time.Duration - var sum int - for _, pass := range result.Passes { - duration := pass.Sub(result.StartedAt) - if sum == 0 || duration < min { - min = duration - } - if duration > max { - max = duration - } - sum += int(duration.Nanoseconds()) - } - t.Logf("Min: %s, Max: %s, Average: %s", min, max, time.Duration(sum/len(result.Passes))*time.Nanosecond) - finishedAt := time.Now() - t.Logf("Setup: %s, shutdown: %s", result.StartedAt.Sub(startedAt), finishedAt.Sub(result.FinishedAt)) -} - -func testDiscoveryPersistenceSimulation(t *testing.T, nodes, conns int, adapter adapters.NodeAdapter) map[int][]byte { - persistenceEnabled = true - discoveryEnabled = true - - result, err := discoveryPersistenceSimulation(nodes, conns, adapter) - - if err != nil { - t.Fatalf("Setting up simulation failed: %v", err) - } - if result.Error != nil { - t.Fatalf("Simulation failed: %s", result.Error) - } - t.Logf("Simulation with %d nodes passed in %s", nodes, result.FinishedAt.Sub(result.StartedAt)) - // set the discovery and persistence flags again to default so other - // tests will not be affected - discoveryEnabled = true - persistenceEnabled = false - return nil -} - -func benchmarkDiscovery(b *testing.B, nodes, conns int) { - for i := 0; i < b.N; i++ { - result, err := discoverySimulation(nodes, conns, adapters.NewSimAdapter(services)) - if err != nil { - b.Fatalf("setting up simulation failed: %v", err) - } - if result.Error != nil { - b.Logf("simulation failed: %s", result.Error) - } - } -} - -func discoverySimulation(nodes, conns int, adapter adapters.NodeAdapter) (*simulations.StepResult, error) { - // create network - net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{ - ID: "0", - DefaultService: serviceName, - }) - defer net.Shutdown() - trigger := make(chan enode.ID) - ids := make([]enode.ID, nodes) - for i := 0; i < nodes; i++ { - conf := adapters.RandomNodeConfig() - node, err := net.NewNodeWithConfig(conf) - if err != nil { - return nil, fmt.Errorf("error starting node: %s", err) - } - if err := net.Start(node.ID()); err != nil { - return nil, fmt.Errorf("error starting node %s: %s", node.ID().TerminalString(), err) - } - if err := triggerChecks(trigger, net, node.ID()); err != nil { - return nil, fmt.Errorf("error triggering checks for node %s: %s", node.ID().TerminalString(), err) - } - ids[i] = node.ID() - } - - // run a simulation which connects the 10 nodes in a ring and waits - // for full peer discovery - var addrs [][]byte - action := func(ctx context.Context) error { - return nil - } - for i := range ids { - // collect the overlay addresses, to - addrs = append(addrs, ids[i].Bytes()) - } - err := net.ConnectNodesChain(nil) - if err != nil { - return nil, err - } - log.Debug(fmt.Sprintf("nodes: %v", len(addrs))) - // construct the peer pot, so that kademlia health can be checked - ppmap := network.NewPeerPotMap(network.NewKadParams().NeighbourhoodSize, addrs) - check := func(ctx context.Context, id enode.ID) (bool, error) { - select { - case <-ctx.Done(): - return false, ctx.Err() - default: - } - - node := net.GetNode(id) - if node == nil { - return false, fmt.Errorf("unknown node: %s", id) - } - client, err := node.Client() - if err != nil { - return false, fmt.Errorf("error getting node client: %s", err) - } - - healthy := &network.Health{} - if err := client.Call(&healthy, "hive_getHealthInfo", ppmap[common.Bytes2Hex(id.Bytes())]); err != nil { - return false, fmt.Errorf("error getting node health: %s", err) - } - log.Debug(fmt.Sprintf("node %4s healthy: connected nearest neighbours: %v, know nearest neighbours: %v,\n\n%v", id, healthy.ConnectNN, healthy.KnowNN, healthy.Hive)) - return healthy.KnowNN && healthy.ConnectNN, nil - } - - // 64 nodes ~ 1min - // 128 nodes ~ - timeout := 300 * time.Second - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - result := simulations.NewSimulation(net).Run(ctx, &simulations.Step{ - Action: action, - Trigger: trigger, - Expect: &simulations.Expectation{ - Nodes: ids, - Check: check, - }, - }) - if result.Error != nil { - return result, nil - } - return result, nil -} - -func discoveryPersistenceSimulation(nodes, conns int, adapter adapters.NodeAdapter) (*simulations.StepResult, error) { - cleanDbStores() - defer cleanDbStores() - - // create network - net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{ - ID: "0", - DefaultService: serviceName, - }) - defer net.Shutdown() - trigger := make(chan enode.ID) - ids := make([]enode.ID, nodes) - var addrs [][]byte - - for i := 0; i < nodes; i++ { - conf := adapters.RandomNodeConfig() - node, err := net.NewNodeWithConfig(conf) - if err != nil { - panic(err) - } - if err != nil { - return nil, fmt.Errorf("error starting node: %s", err) - } - if err := net.Start(node.ID()); err != nil { - return nil, fmt.Errorf("error starting node %s: %s", node.ID().TerminalString(), err) - } - if err := triggerChecks(trigger, net, node.ID()); err != nil { - return nil, fmt.Errorf("error triggering checks for node %s: %s", node.ID().TerminalString(), err) - } - // TODO we shouldn't be equating underaddr and overaddr like this, as they are not the same in production - ids[i] = node.ID() - a := ids[i].Bytes() - - addrs = append(addrs, a) - } - - // run a simulation which connects the 10 nodes in a ring and waits - // for full peer discovery - - var restartTime time.Time - - action := func(ctx context.Context) error { - ticker := time.NewTicker(500 * time.Millisecond) - - for range ticker.C { - isHealthy := true - for _, id := range ids { - //call Healthy RPC - node := net.GetNode(id) - if node == nil { - return fmt.Errorf("unknown node: %s", id) - } - client, err := node.Client() - if err != nil { - return fmt.Errorf("error getting node client: %s", err) - } - healthy := &network.Health{} - addr := id.String() - ppmap := network.NewPeerPotMap(network.NewKadParams().NeighbourhoodSize, addrs) - if err := client.Call(&healthy, "hive_getHealthInfo", ppmap[common.Bytes2Hex(id.Bytes())]); err != nil { - return fmt.Errorf("error getting node health: %s", err) - } - - log.Info(fmt.Sprintf("NODE: %s, IS HEALTHY: %t", addr, healthy.ConnectNN && healthy.KnowNN && healthy.CountKnowNN > 0)) - var nodeStr string - if err := client.Call(&nodeStr, "hive_string"); err != nil { - return fmt.Errorf("error getting node string %s", err) - } - log.Info(nodeStr) - if !healthy.ConnectNN || healthy.CountKnowNN == 0 { - isHealthy = false - break - } - } - if isHealthy { - break - } - } - ticker.Stop() - - log.Info("reached healthy kademlia. starting to shutdown nodes.") - shutdownStarted := time.Now() - // stop all ids, then start them again - for _, id := range ids { - node := net.GetNode(id) - - if err := net.Stop(node.ID()); err != nil { - return fmt.Errorf("error stopping node %s: %s", node.ID().TerminalString(), err) - } - } - log.Info(fmt.Sprintf("shutting down nodes took: %s", time.Since(shutdownStarted))) - persistenceEnabled = true - discoveryEnabled = false - restartTime = time.Now() - for _, id := range ids { - node := net.GetNode(id) - if err := net.Start(node.ID()); err != nil { - return fmt.Errorf("error starting node %s: %s", node.ID().TerminalString(), err) - } - if err := triggerChecks(trigger, net, node.ID()); err != nil { - return fmt.Errorf("error triggering checks for node %s: %s", node.ID().TerminalString(), err) - } - } - - log.Info(fmt.Sprintf("restarting nodes took: %s", time.Since(restartTime))) - - return nil - } - net.ConnectNodesChain(nil) - log.Debug(fmt.Sprintf("nodes: %v", len(addrs))) - // construct the peer pot, so that kademlia health can be checked - check := func(ctx context.Context, id enode.ID) (bool, error) { - select { - case <-ctx.Done(): - return false, ctx.Err() - default: - } - - node := net.GetNode(id) - if node == nil { - return false, fmt.Errorf("unknown node: %s", id) - } - client, err := node.Client() - if err != nil { - return false, fmt.Errorf("error getting node client: %s", err) - } - healthy := &network.Health{} - ppmap := network.NewPeerPotMap(network.NewKadParams().NeighbourhoodSize, addrs) - - if err := client.Call(&healthy, "hive_getHealthInfo", ppmap[common.Bytes2Hex(id.Bytes())]); err != nil { - return false, fmt.Errorf("error getting node health: %s", err) - } - log.Info(fmt.Sprintf("node %4s healthy: got nearest neighbours: %v, know nearest neighbours: %v", id, healthy.ConnectNN, healthy.KnowNN)) - - return healthy.KnowNN && healthy.ConnectNN, nil - } - - // 64 nodes ~ 1min - // 128 nodes ~ - timeout := 300 * time.Second - ctx, cancel := context.WithTimeout(context.Background(), timeout) - defer cancel() - result := simulations.NewSimulation(net).Run(ctx, &simulations.Step{ - Action: action, - Trigger: trigger, - Expect: &simulations.Expectation{ - Nodes: ids, - Check: check, - }, - }) - if result.Error != nil { - return result, nil - } - - return result, nil -} - -// triggerChecks triggers a simulation step check whenever a peer is added or -// removed from the given node, and also every second to avoid a race between -// peer events and kademlia becoming healthy -func triggerChecks(trigger chan enode.ID, net *simulations.Network, id enode.ID) error { - node := net.GetNode(id) - if node == nil { - return fmt.Errorf("unknown node: %s", id) - } - client, err := node.Client() - if err != nil { - return err - } - events := make(chan *p2p.PeerEvent) - sub, err := client.Subscribe(context.Background(), "admin", events, "peerEvents") - if err != nil { - return fmt.Errorf("error getting peer events for node %v: %s", id, err) - } - go func() { - defer sub.Unsubscribe() - - tick := time.NewTicker(time.Second) - defer tick.Stop() - - for { - select { - case <-events: - trigger <- id - case <-tick.C: - trigger <- id - case err := <-sub.Err(): - if err != nil { - log.Error(fmt.Sprintf("error getting peer events for node %v", id), "err", err) - } - return - } - } - }() - return nil -} - -func newService(ctx *adapters.ServiceContext) (node.Service, error) { - addr := network.NewAddr(ctx.Config.Node()) - - kp := network.NewKadParams() - kp.NeighbourhoodSize = testNeighbourhoodSize - - if ctx.Config.Reachable != nil { - kp.Reachable = func(o *network.BzzAddr) bool { - return ctx.Config.Reachable(o.ID()) - } - } - kad := network.NewKademlia(addr.Over(), kp) - hp := network.NewHiveParams() - hp.KeepAliveInterval = time.Duration(200) * time.Millisecond - hp.Discovery = discoveryEnabled - - log.Info(fmt.Sprintf("discovery for nodeID %s is %t", ctx.Config.ID.String(), hp.Discovery)) - - config := &network.BzzConfig{ - OverlayAddr: addr.Over(), - UnderlayAddr: addr.Under(), - HiveParams: hp, - } - - if persistenceEnabled { - log.Info(fmt.Sprintf("persistence enabled for nodeID %s", ctx.Config.ID.String())) - store, err := getDbStore(ctx.Config.ID.String()) - if err != nil { - return nil, err - } - return network.NewBzz(config, kad, store, nil, nil), nil - } - - return network.NewBzz(config, kad, nil, nil, nil), nil -} diff --git a/swarm/network/simulations/discovery/snapshot.json b/swarm/network/simulations/discovery/snapshot.json deleted file mode 100755 index f7f400eb67a2..000000000000 --- a/swarm/network/simulations/discovery/snapshot.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes":[{"node":{"config":null,"up":false}},{"node":{"config":null,"up":false}},{"node":{"config":null,"up":false}},{"node":{"config":null,"up":false}},{"node":{"config":null,"up":false}},{"node":{"config":null,"up":false}},{"node":{"config":null,"up":false}},{"node":{"config":null,"up":false}},{"node":{"config":null,"up":false}},{"node":{"config":null,"up":false}}],"conns":[{"one":"c04a0c47cb0c522ecf28d8841e93721e73f58790b30e92382816a4b453be2988","other":"d9283e5247a18d6564b3581217e9f4d9c93a4359944894c00bb2b22c690faadc","up":true},{"one":"dd99c11abe2abae112d64d902b96fe0c75243ea67eca759a2769058a30cc0e77","other":"c04a0c47cb0c522ecf28d8841e93721e73f58790b30e92382816a4b453be2988","up":true},{"one":"4f5dad2aa4f26ac5a23d4fbcc807296b474eab77761db6594debd60ef4287aed","other":"dd99c11abe2abae112d64d902b96fe0c75243ea67eca759a2769058a30cc0e77","up":true},{"one":"4f47f4e176d1c9f78d9a7e19723689ffe2a0603004a3d4506a2349e55a56fc17","other":"4f5dad2aa4f26ac5a23d4fbcc807296b474eab77761db6594debd60ef4287aed","up":true},{"one":"20b6a1be2cb8f966151682350e029d4f8da8ee92de10a2a1cb1727d110acebfa","other":"4f47f4e176d1c9f78d9a7e19723689ffe2a0603004a3d4506a2349e55a56fc17","up":true},{"one":"50cb92e77710582fa9cbee7a54cf25c95fd27d8d54b13ba5520a50139c309a22","other":"20b6a1be2cb8f966151682350e029d4f8da8ee92de10a2a1cb1727d110acebfa","up":true},{"one":"319dc901f99940f1339c540bc36fbabb10a96d326b13b9d7f53e7496980e2996","other":"50cb92e77710582fa9cbee7a54cf25c95fd27d8d54b13ba5520a50139c309a22","up":true},{"one":"dc285b6436a8bfd4d2e586d478b18d3fe7b705ce0b4fb27a651adcf6d27984f1","other":"319dc901f99940f1339c540bc36fbabb10a96d326b13b9d7f53e7496980e2996","up":true},{"one":"974dbe511377280f945a53a194b4bb397875b10b1ecb119a92425bbb16db68f1","other":"dc285b6436a8bfd4d2e586d478b18d3fe7b705ce0b4fb27a651adcf6d27984f1","up":true},{"one":"d9283e5247a18d6564b3581217e9f4d9c93a4359944894c00bb2b22c690faadc","other":"974dbe511377280f945a53a194b4bb397875b10b1ecb119a92425bbb16db68f1","up":true}]} \ No newline at end of file diff --git a/swarm/network/simulations/overlay.go b/swarm/network/simulations/overlay.go deleted file mode 100644 index dd087b449980..000000000000 --- a/swarm/network/simulations/overlay.go +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// You can run this simulation using -// -// go run ./swarm/network/simulations/overlay.go -package main - -import ( - "flag" - "fmt" - "net/http" - "runtime" - "sync" - "time" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/simulations" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" - "github.com/ubiq/go-ubiq/swarm/network" - "github.com/ubiq/go-ubiq/swarm/state" - colorable "github.com/mattn/go-colorable" -) - -var ( - noDiscovery = flag.Bool("no-discovery", false, "disable discovery (useful if you want to load a snapshot)") - vmodule = flag.String("vmodule", "", "log filters for logger via Vmodule") - verbosity = flag.Int("verbosity", 0, "log filters for logger via Vmodule") - httpSimPort = 8888 -) - -func init() { - flag.Parse() - //initialize the logger - //this is a demonstration on how to use Vmodule for filtering logs - //provide -vmodule as param, and comma-separated values, e.g.: - //-vmodule overlay_test.go=4,simulations=3 - //above examples sets overlay_test.go logs to level 4, while packages ending with "simulations" to 3 - if *vmodule != "" { - //only enable the pattern matching handler if the flag has been provided - glogger := log.NewGlogHandler(log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true))) - if *verbosity > 0 { - glogger.Verbosity(log.Lvl(*verbosity)) - } - glogger.Vmodule(*vmodule) - log.Root().SetHandler(glogger) - } -} - -type Simulation struct { - mtx sync.Mutex - stores map[enode.ID]state.Store -} - -func NewSimulation() *Simulation { - return &Simulation{ - stores: make(map[enode.ID]state.Store), - } -} - -func (s *Simulation) NewService(ctx *adapters.ServiceContext) (node.Service, error) { - node := ctx.Config.Node() - s.mtx.Lock() - store, ok := s.stores[node.ID()] - if !ok { - store = state.NewInmemoryStore() - s.stores[node.ID()] = store - } - s.mtx.Unlock() - - addr := network.NewAddr(node) - - kp := network.NewKadParams() - kp.NeighbourhoodSize = 2 - kp.MaxBinSize = 4 - kp.MinBinSize = 1 - kp.MaxRetries = 1000 - kp.RetryExponent = 2 - kp.RetryInterval = 1000000 - kad := network.NewKademlia(addr.Over(), kp) - hp := network.NewHiveParams() - hp.Discovery = !*noDiscovery - hp.KeepAliveInterval = 300 * time.Millisecond - - config := &network.BzzConfig{ - OverlayAddr: addr.Over(), - UnderlayAddr: addr.Under(), - HiveParams: hp, - } - - return network.NewBzz(config, kad, store, nil, nil), nil -} - -//create the simulation network -func newSimulationNetwork() *simulations.Network { - - s := NewSimulation() - services := adapters.Services{ - "overlay": s.NewService, - } - adapter := adapters.NewSimAdapter(services) - simNetwork := simulations.NewNetwork(adapter, &simulations.NetworkConfig{ - DefaultService: "overlay", - }) - return simNetwork -} - -//return a new http server -func newOverlaySim(sim *simulations.Network) *simulations.Server { - return simulations.NewServer(sim) -} - -// var server -func main() { - //cpu optimization - runtime.GOMAXPROCS(runtime.NumCPU()) - //run the sim - runOverlaySim() -} - -func runOverlaySim() { - //create the simulation network - net := newSimulationNetwork() - //create a http server with it - sim := newOverlaySim(net) - log.Info(fmt.Sprintf("starting simulation server on 0.0.0.0:%d...", httpSimPort)) - //start the HTTP server - http.ListenAndServe(fmt.Sprintf(":%d", httpSimPort), sim) -} diff --git a/swarm/network/simulations/overlay_test.go b/swarm/network/simulations/overlay_test.go deleted file mode 100644 index 9cef353e6f09..000000000000 --- a/swarm/network/simulations/overlay_test.go +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . -package main - -import ( - "context" - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "net/url" - "testing" - "time" - - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/simulations" - "github.com/ubiq/go-ubiq/swarm/log" -) - -var ( - nodeCount = 10 -) - -//This test is used to test the overlay simulation. -//As the simulation is executed via a main, it is easily missed on changes -//An automated test will prevent that -//The test just connects to the simulations, starts the network, -//starts the mocker, gets the number of nodes, and stops it again. -//It also provides a documentation on the steps needed by frontends -//to use the simulations -func TestOverlaySim(t *testing.T) { - t.Skip("Test is flaky, see: https://github.com/ethersphere/go-ethereum/issues/592") - //start the simulation - log.Info("Start simulation backend") - //get the simulation networ; needed to subscribe for up events - net := newSimulationNetwork() - //create the overlay simulation - sim := newOverlaySim(net) - //create a http test server with it - srv := httptest.NewServer(sim) - defer srv.Close() - - log.Debug("Http simulation server started. Start simulation network") - //start the simulation network (initialization of simulation) - resp, err := http.Post(srv.URL+"/start", "application/json", nil) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("Expected Status Code %d, got %d", http.StatusOK, resp.StatusCode) - } - - log.Debug("Start mocker") - //start the mocker, needs a node count and an ID - resp, err = http.PostForm(srv.URL+"/mocker/start", - url.Values{ - "node-count": {fmt.Sprintf("%d", nodeCount)}, - "mocker-type": {simulations.GetMockerList()[0]}, - }) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - reason, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - t.Fatalf("Expected Status Code %d, got %d, response body %s", http.StatusOK, resp.StatusCode, string(reason)) - } - - //variables needed to wait for nodes being up - var upCount int - trigger := make(chan enode.ID) - - //wait for all nodes to be up - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - //start watching node up events... - go watchSimEvents(net, ctx, trigger) - - //...and wait until all expected up events (nodeCount) have been received -LOOP: - for { - select { - case <-trigger: - //new node up event received, increase counter - upCount++ - //all expected node up events received - if upCount == nodeCount { - break LOOP - } - case <-ctx.Done(): - t.Fatalf("Timed out waiting for up events") - } - - } - - //at this point we can query the server - log.Info("Get number of nodes") - //get the number of nodes - resp, err = http.Get(srv.URL + "/nodes") - if err != nil { - t.Fatal(err) - } - - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - b, err := ioutil.ReadAll(resp.Body) - if err != nil { - t.Fatal(err) - } - - //unmarshal number of nodes from JSON response - var nodesArr []simulations.Node - err = json.Unmarshal(b, &nodesArr) - if err != nil { - t.Fatal(err) - } - - //check if number of nodes received is same as sent - if len(nodesArr) != nodeCount { - t.Fatal(fmt.Errorf("Expected %d number of nodes, got %d", nodeCount, len(nodesArr))) - } - - //need to let it run for a little while, otherwise stopping it immediately can crash due running nodes - //wanting to connect to already stopped nodes - time.Sleep(1 * time.Second) - - log.Info("Stop the network") - //stop the network - resp, err = http.Post(srv.URL+"/stop", "application/json", nil) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } - - log.Info("Reset the network") - //reset the network (removes all nodes and connections) - resp, err = http.Post(srv.URL+"/reset", "application/json", nil) - if err != nil { - t.Fatal(err) - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - t.Fatalf("err %s", resp.Status) - } -} - -//watch for events so we know when all nodes are up -func watchSimEvents(net *simulations.Network, ctx context.Context, trigger chan enode.ID) { - events := make(chan *simulations.Event) - sub := net.Events().Subscribe(events) - defer sub.Unsubscribe() - - for { - select { - case ev := <-events: - //only catch node up events - if ev.Type == simulations.EventTypeNode { - if ev.Node.Up() { - log.Debug("got node up event", "event", ev, "node", ev.Node.Config.ID) - select { - case trigger <- ev.Node.Config.ID: - case <-ctx.Done(): - return - } - } - } - case <-ctx.Done(): - return - } - } -} diff --git a/swarm/network/stream/common_test.go b/swarm/network/stream/common_test.go deleted file mode 100644 index c11822c3318b..000000000000 --- a/swarm/network/stream/common_test.go +++ /dev/null @@ -1,373 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package stream - -import ( - "context" - "errors" - "flag" - "fmt" - "io" - "io/ioutil" - "math/rand" - "os" - "strings" - "sync" - "sync/atomic" - "time" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" - p2ptest "github.com/ubiq/go-ubiq/p2p/testing" - "github.com/ubiq/go-ubiq/swarm/network" - "github.com/ubiq/go-ubiq/swarm/network/simulation" - "github.com/ubiq/go-ubiq/swarm/state" - "github.com/ubiq/go-ubiq/swarm/storage" - mockmem "github.com/ubiq/go-ubiq/swarm/storage/mock/mem" - "github.com/ubiq/go-ubiq/swarm/testutil" - colorable "github.com/mattn/go-colorable" -) - -var ( - loglevel = flag.Int("loglevel", 2, "verbosity of logs") - nodes = flag.Int("nodes", 0, "number of nodes") - chunks = flag.Int("chunks", 0, "number of chunks") - useMockStore = flag.Bool("mockstore", false, "disabled mock store (default: enabled)") - longrunning = flag.Bool("longrunning", false, "do run long-running tests") - - bucketKeyDB = simulation.BucketKey("db") - bucketKeyStore = simulation.BucketKey("store") - bucketKeyFileStore = simulation.BucketKey("filestore") - bucketKeyNetStore = simulation.BucketKey("netstore") - bucketKeyDelivery = simulation.BucketKey("delivery") - bucketKeyRegistry = simulation.BucketKey("registry") - - chunkSize = 4096 - pof = network.Pof -) - -func init() { - flag.Parse() - rand.Seed(time.Now().UnixNano()) - - log.PrintOrigins(true) - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))) -} - -// newNetStoreAndDelivery is a default constructor for BzzAddr, NetStore and Delivery, used in Simulations -func newNetStoreAndDelivery(ctx *adapters.ServiceContext, bucket *sync.Map) (*network.BzzAddr, *storage.NetStore, *Delivery, func(), error) { - addr := network.NewAddr(ctx.Config.Node()) - - netStore, delivery, cleanup, err := netStoreAndDeliveryWithAddr(ctx, bucket, addr) - if err != nil { - return nil, nil, nil, nil, err - } - - netStore.NewNetFetcherFunc = network.NewFetcherFactory(delivery.RequestFromPeers, true).New - - return addr, netStore, delivery, cleanup, nil -} - -// newNetStoreAndDeliveryWithBzzAddr is a constructor for NetStore and Delivery, used in Simulations, accepting any BzzAddr -func newNetStoreAndDeliveryWithBzzAddr(ctx *adapters.ServiceContext, bucket *sync.Map, addr *network.BzzAddr) (*storage.NetStore, *Delivery, func(), error) { - netStore, delivery, cleanup, err := netStoreAndDeliveryWithAddr(ctx, bucket, addr) - if err != nil { - return nil, nil, nil, err - } - - netStore.NewNetFetcherFunc = network.NewFetcherFactory(delivery.RequestFromPeers, true).New - - return netStore, delivery, cleanup, nil -} - -// newNetStoreAndDeliveryWithRequestFunc is a constructor for NetStore and Delivery, used in Simulations, accepting any NetStore.RequestFunc -func newNetStoreAndDeliveryWithRequestFunc(ctx *adapters.ServiceContext, bucket *sync.Map, rf network.RequestFunc) (*network.BzzAddr, *storage.NetStore, *Delivery, func(), error) { - addr := network.NewAddr(ctx.Config.Node()) - - netStore, delivery, cleanup, err := netStoreAndDeliveryWithAddr(ctx, bucket, addr) - if err != nil { - return nil, nil, nil, nil, err - } - - netStore.NewNetFetcherFunc = network.NewFetcherFactory(rf, true).New - - return addr, netStore, delivery, cleanup, nil -} - -func netStoreAndDeliveryWithAddr(ctx *adapters.ServiceContext, bucket *sync.Map, addr *network.BzzAddr) (*storage.NetStore, *Delivery, func(), error) { - n := ctx.Config.Node() - - store, datadir, err := createTestLocalStorageForID(n.ID(), addr) - if *useMockStore { - store, datadir, err = createMockStore(mockmem.NewGlobalStore(), n.ID(), addr) - } - if err != nil { - return nil, nil, nil, err - } - localStore := store.(*storage.LocalStore) - netStore, err := storage.NewNetStore(localStore, nil) - if err != nil { - return nil, nil, nil, err - } - - fileStore := storage.NewFileStore(netStore, storage.NewFileStoreParams()) - - kad := network.NewKademlia(addr.Over(), network.NewKadParams()) - delivery := NewDelivery(kad, netStore) - - bucket.Store(bucketKeyStore, store) - bucket.Store(bucketKeyDB, netStore) - bucket.Store(bucketKeyDelivery, delivery) - bucket.Store(bucketKeyFileStore, fileStore) - - cleanup := func() { - netStore.Close() - os.RemoveAll(datadir) - } - - return netStore, delivery, cleanup, nil -} - -func newStreamerTester(registryOptions *RegistryOptions) (*p2ptest.ProtocolTester, *Registry, *storage.LocalStore, func(), error) { - // setup - addr := network.RandomAddr() // tested peers peer address - to := network.NewKademlia(addr.OAddr, network.NewKadParams()) - - // temp datadir - datadir, err := ioutil.TempDir("", "streamer") - if err != nil { - return nil, nil, nil, nil, err - } - removeDataDir := func() { - os.RemoveAll(datadir) - } - - params := storage.NewDefaultLocalStoreParams() - params.Init(datadir) - params.BaseKey = addr.Over() - - localStore, err := storage.NewTestLocalStoreForAddr(params) - if err != nil { - removeDataDir() - return nil, nil, nil, nil, err - } - - netStore, err := storage.NewNetStore(localStore, nil) - if err != nil { - removeDataDir() - return nil, nil, nil, nil, err - } - - delivery := NewDelivery(to, netStore) - netStore.NewNetFetcherFunc = network.NewFetcherFactory(delivery.RequestFromPeers, true).New - streamer := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), registryOptions, nil) - teardown := func() { - streamer.Close() - removeDataDir() - } - protocolTester := p2ptest.NewProtocolTester(addr.ID(), 1, streamer.runProtocol) - - err = waitForPeers(streamer, 10*time.Second, 1) - if err != nil { - teardown() - return nil, nil, nil, nil, errors.New("timeout: peer is not created") - } - - return protocolTester, streamer, localStore, teardown, nil -} - -func waitForPeers(streamer *Registry, timeout time.Duration, expectedPeers int) error { - ticker := time.NewTicker(10 * time.Millisecond) - timeoutTimer := time.NewTimer(timeout) - for { - select { - case <-ticker.C: - if streamer.peersCount() >= expectedPeers { - return nil - } - case <-timeoutTimer.C: - return errors.New("timeout") - } - } -} - -type roundRobinStore struct { - index uint32 - stores []storage.ChunkStore -} - -func newRoundRobinStore(stores ...storage.ChunkStore) *roundRobinStore { - return &roundRobinStore{ - stores: stores, - } -} - -// not used in this context, only to fulfill ChunkStore interface -func (rrs *roundRobinStore) Has(ctx context.Context, addr storage.Address) bool { - panic("RoundRobinStor doesn't support HasChunk") -} - -func (rrs *roundRobinStore) Get(ctx context.Context, addr storage.Address) (storage.Chunk, error) { - return nil, errors.New("get not well defined on round robin store") -} - -func (rrs *roundRobinStore) Put(ctx context.Context, chunk storage.Chunk) error { - i := atomic.AddUint32(&rrs.index, 1) - idx := int(i) % len(rrs.stores) - return rrs.stores[idx].Put(ctx, chunk) -} - -func (rrs *roundRobinStore) Close() { - for _, store := range rrs.stores { - store.Close() - } -} - -func readAll(fileStore *storage.FileStore, hash []byte) (int64, error) { - r, _ := fileStore.Retrieve(context.TODO(), hash) - buf := make([]byte, 1024) - var n int - var total int64 - var err error - for (total == 0 || n > 0) && err == nil { - n, err = r.ReadAt(buf, total) - total += int64(n) - } - if err != nil && err != io.EOF { - return total, err - } - return total, nil -} - -func uploadFilesToNodes(sim *simulation.Simulation) ([]storage.Address, []string, error) { - nodes := sim.UpNodeIDs() - nodeCnt := len(nodes) - log.Debug(fmt.Sprintf("Uploading %d files to nodes", nodeCnt)) - //array holding generated files - rfiles := make([]string, nodeCnt) - //array holding the root hashes of the files - rootAddrs := make([]storage.Address, nodeCnt) - - var err error - //for every node, generate a file and upload - for i, id := range nodes { - item, ok := sim.NodeItem(id, bucketKeyFileStore) - if !ok { - return nil, nil, fmt.Errorf("Error accessing localstore") - } - fileStore := item.(*storage.FileStore) - //generate a file - rfiles[i], err = generateRandomFile() - if err != nil { - return nil, nil, err - } - //store it (upload it) on the FileStore - ctx := context.TODO() - rk, wait, err := fileStore.Store(ctx, strings.NewReader(rfiles[i]), int64(len(rfiles[i])), false) - log.Debug("Uploaded random string file to node") - if err != nil { - return nil, nil, err - } - err = wait(ctx) - if err != nil { - return nil, nil, err - } - rootAddrs[i] = rk - } - return rootAddrs, rfiles, nil -} - -//generate a random file (string) -func generateRandomFile() (string, error) { - //generate a random file size between minFileSize and maxFileSize - fileSize := rand.Intn(maxFileSize-minFileSize) + minFileSize - log.Debug(fmt.Sprintf("Generated file with filesize %d kB", fileSize)) - b := testutil.RandomBytes(1, fileSize*1024) - return string(b), nil -} - -//create a local store for the given node -func createTestLocalStorageForID(id enode.ID, addr *network.BzzAddr) (storage.ChunkStore, string, error) { - var datadir string - var err error - datadir, err = ioutil.TempDir("", fmt.Sprintf("syncer-test-%s", id.TerminalString())) - if err != nil { - return nil, "", err - } - var store storage.ChunkStore - params := storage.NewDefaultLocalStoreParams() - params.ChunkDbPath = datadir - params.BaseKey = addr.Over() - store, err = storage.NewTestLocalStoreForAddr(params) - if err != nil { - os.RemoveAll(datadir) - return nil, "", err - } - return store, datadir, nil -} - -// watchDisconnections receives simulation peer events in a new goroutine and sets atomic value -// disconnected to true in case of a disconnect event. -func watchDisconnections(ctx context.Context, sim *simulation.Simulation) (disconnected *boolean) { - log.Debug("Watching for disconnections") - disconnections := sim.PeerEvents( - ctx, - sim.NodeIDs(), - simulation.NewPeerEventsFilter().Drop(), - ) - disconnected = new(boolean) - go func() { - for { - select { - case <-ctx.Done(): - return - case d := <-disconnections: - if d.Error != nil { - log.Error("peer drop event error", "node", d.NodeID, "peer", d.PeerID, "err", d.Error) - } else { - log.Error("peer drop", "node", d.NodeID, "peer", d.PeerID) - } - disconnected.set(true) - } - } - }() - return disconnected -} - -// boolean is used to concurrently set -// and read a boolean value. -type boolean struct { - v bool - mu sync.RWMutex -} - -// set sets the value. -func (b *boolean) set(v bool) { - b.mu.Lock() - defer b.mu.Unlock() - - b.v = v -} - -// bool reads the value. -func (b *boolean) bool() bool { - b.mu.RLock() - defer b.mu.RUnlock() - - return b.v -} diff --git a/swarm/network/stream/delivery.go b/swarm/network/stream/delivery.go deleted file mode 100644 index 127f32f13179..000000000000 --- a/swarm/network/stream/delivery.go +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package stream - -import ( - "context" - "errors" - "fmt" - - "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/network" - "github.com/ubiq/go-ubiq/swarm/spancontext" - "github.com/ubiq/go-ubiq/swarm/storage" - opentracing "github.com/opentracing/opentracing-go" -) - -const ( - swarmChunkServerStreamName = "RETRIEVE_REQUEST" - deliveryCap = 32 -) - -var ( - processReceivedChunksCount = metrics.NewRegisteredCounter("network.stream.received_chunks.count", nil) - handleRetrieveRequestMsgCount = metrics.NewRegisteredCounter("network.stream.handle_retrieve_request_msg.count", nil) - retrieveChunkFail = metrics.NewRegisteredCounter("network.stream.retrieve_chunks_fail.count", nil) - - requestFromPeersCount = metrics.NewRegisteredCounter("network.stream.request_from_peers.count", nil) - requestFromPeersEachCount = metrics.NewRegisteredCounter("network.stream.request_from_peers_each.count", nil) -) - -type Delivery struct { - chunkStore storage.SyncChunkStore - kad *network.Kademlia - getPeer func(enode.ID) *Peer -} - -func NewDelivery(kad *network.Kademlia, chunkStore storage.SyncChunkStore) *Delivery { - return &Delivery{ - chunkStore: chunkStore, - kad: kad, - } -} - -// SwarmChunkServer implements Server -type SwarmChunkServer struct { - deliveryC chan []byte - batchC chan []byte - chunkStore storage.ChunkStore - currentLen uint64 - quit chan struct{} -} - -// NewSwarmChunkServer is SwarmChunkServer constructor -func NewSwarmChunkServer(chunkStore storage.ChunkStore) *SwarmChunkServer { - s := &SwarmChunkServer{ - deliveryC: make(chan []byte, deliveryCap), - batchC: make(chan []byte), - chunkStore: chunkStore, - quit: make(chan struct{}), - } - go s.processDeliveries() - return s -} - -// processDeliveries handles delivered chunk hashes -func (s *SwarmChunkServer) processDeliveries() { - var hashes []byte - var batchC chan []byte - for { - select { - case <-s.quit: - return - case hash := <-s.deliveryC: - hashes = append(hashes, hash...) - batchC = s.batchC - case batchC <- hashes: - hashes = nil - batchC = nil - } - } -} - -// SessionIndex returns zero in all cases for SwarmChunkServer. -func (s *SwarmChunkServer) SessionIndex() (uint64, error) { - return 0, nil -} - -// SetNextBatch -func (s *SwarmChunkServer) SetNextBatch(_, _ uint64) (hashes []byte, from uint64, to uint64, proof *HandoverProof, err error) { - select { - case hashes = <-s.batchC: - case <-s.quit: - return - } - - from = s.currentLen - s.currentLen += uint64(len(hashes)) - to = s.currentLen - return -} - -// Close needs to be called on a stream server -func (s *SwarmChunkServer) Close() { - close(s.quit) -} - -// GetData retrives chunk data from db store -func (s *SwarmChunkServer) GetData(ctx context.Context, key []byte) ([]byte, error) { - chunk, err := s.chunkStore.Get(ctx, storage.Address(key)) - if err != nil { - return nil, err - } - return chunk.Data(), nil -} - -// RetrieveRequestMsg is the protocol msg for chunk retrieve requests -type RetrieveRequestMsg struct { - Addr storage.Address - SkipCheck bool - HopCount uint8 -} - -func (d *Delivery) handleRetrieveRequestMsg(ctx context.Context, sp *Peer, req *RetrieveRequestMsg) error { - log.Trace("received request", "peer", sp.ID(), "hash", req.Addr) - handleRetrieveRequestMsgCount.Inc(1) - - var osp opentracing.Span - ctx, osp = spancontext.StartSpan( - ctx, - "retrieve.request") - - s, err := sp.getServer(NewStream(swarmChunkServerStreamName, "", true)) - if err != nil { - return err - } - streamer := s.Server.(*SwarmChunkServer) - - var cancel func() - // TODO: do something with this hardcoded timeout, maybe use TTL in the future - ctx = context.WithValue(ctx, "peer", sp.ID().String()) - ctx = context.WithValue(ctx, "hopcount", req.HopCount) - ctx, cancel = context.WithTimeout(ctx, network.RequestTimeout) - - go func() { - select { - case <-ctx.Done(): - case <-streamer.quit: - } - cancel() - }() - - go func() { - defer osp.Finish() - chunk, err := d.chunkStore.Get(ctx, req.Addr) - if err != nil { - retrieveChunkFail.Inc(1) - log.Debug("ChunkStore.Get can not retrieve chunk", "peer", sp.ID().String(), "addr", req.Addr, "hopcount", req.HopCount, "err", err) - return - } - if req.SkipCheck { - syncing := false - err = sp.Deliver(ctx, chunk, s.priority, syncing) - if err != nil { - log.Warn("ERROR in handleRetrieveRequestMsg", "err", err) - } - return - } - select { - case streamer.deliveryC <- chunk.Address()[:]: - case <-streamer.quit: - } - - }() - - return nil -} - -//Chunk delivery always uses the same message type.... -type ChunkDeliveryMsg struct { - Addr storage.Address - SData []byte // the stored chunk Data (incl size) - peer *Peer // set in handleChunkDeliveryMsg -} - -//...but swap accounting needs to disambiguate if it is a delivery for syncing or for retrieval -//as it decides based on message type if it needs to account for this message or not - -//defines a chunk delivery for retrieval (with accounting) -type ChunkDeliveryMsgRetrieval ChunkDeliveryMsg - -//defines a chunk delivery for syncing (without accounting) -type ChunkDeliveryMsgSyncing ChunkDeliveryMsg - -// TODO: Fix context SNAFU -func (d *Delivery) handleChunkDeliveryMsg(ctx context.Context, sp *Peer, req *ChunkDeliveryMsg) error { - var osp opentracing.Span - ctx, osp = spancontext.StartSpan( - ctx, - "chunk.delivery") - - processReceivedChunksCount.Inc(1) - - go func() { - defer osp.Finish() - - req.peer = sp - err := d.chunkStore.Put(ctx, storage.NewChunk(req.Addr, req.SData)) - if err != nil { - if err == storage.ErrChunkInvalid { - // we removed this log because it spams the logs - // TODO: Enable this log line - // log.Warn("invalid chunk delivered", "peer", sp.ID(), "chunk", req.Addr, ) - req.peer.Drop(err) - } - } - }() - return nil -} - -// RequestFromPeers sends a chunk retrieve request to -func (d *Delivery) RequestFromPeers(ctx context.Context, req *network.Request) (*enode.ID, chan struct{}, error) { - requestFromPeersCount.Inc(1) - var sp *Peer - spID := req.Source - - if spID != nil { - sp = d.getPeer(*spID) - if sp == nil { - return nil, nil, fmt.Errorf("source peer %v not found", spID.String()) - } - } else { - d.kad.EachConn(req.Addr[:], 255, func(p *network.Peer, po int) bool { - id := p.ID() - if p.LightNode { - // skip light nodes - return true - } - if req.SkipPeer(id.String()) { - log.Trace("Delivery.RequestFromPeers: skip peer", "peer id", id) - return true - } - sp = d.getPeer(id) - // sp is nil, when we encounter a peer that is not registered for delivery, i.e. doesn't support the `stream` protocol - if sp == nil { - return true - } - spID = &id - return false - }) - if sp == nil { - return nil, nil, errors.New("no peer found") - } - } - - err := sp.SendPriority(ctx, &RetrieveRequestMsg{ - Addr: req.Addr, - SkipCheck: req.SkipCheck, - HopCount: req.HopCount, - }, Top, "request.from.peers") - if err != nil { - return nil, nil, err - } - requestFromPeersEachCount.Inc(1) - - return spID, sp.quit, nil -} diff --git a/swarm/network/stream/delivery_test.go b/swarm/network/stream/delivery_test.go deleted file mode 100644 index 04ac1a0abdde..000000000000 --- a/swarm/network/stream/delivery_test.go +++ /dev/null @@ -1,734 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package stream - -import ( - "bytes" - "context" - "errors" - "fmt" - "sync" - "testing" - "time" - - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/protocols" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" - p2ptest "github.com/ubiq/go-ubiq/p2p/testing" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/network" - pq "github.com/ubiq/go-ubiq/swarm/network/priorityqueue" - "github.com/ubiq/go-ubiq/swarm/network/simulation" - "github.com/ubiq/go-ubiq/swarm/state" - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/ubiq/go-ubiq/swarm/testutil" -) - -//Tests initializing a retrieve request -func TestStreamerRetrieveRequest(t *testing.T) { - regOpts := &RegistryOptions{ - Retrieval: RetrievalClientOnly, - Syncing: SyncingDisabled, - } - tester, streamer, _, teardown, err := newStreamerTester(regOpts) - if err != nil { - t.Fatal(err) - } - defer teardown() - - node := tester.Nodes[0] - - ctx := context.Background() - req := network.NewRequest( - storage.Address(hash0[:]), - true, - &sync.Map{}, - ) - streamer.delivery.RequestFromPeers(ctx, req) - - stream := NewStream(swarmChunkServerStreamName, "", true) - - err = tester.TestExchanges(p2ptest.Exchange{ - Label: "RetrieveRequestMsg", - Expects: []p2ptest.Expect{ - { //start expecting a subscription for RETRIEVE_REQUEST due to `RetrievalClientOnly` - Code: 4, - Msg: &SubscribeMsg{ - Stream: stream, - History: nil, - Priority: Top, - }, - Peer: node.ID(), - }, - { //expect a retrieve request message for the given hash - Code: 5, - Msg: &RetrieveRequestMsg{ - Addr: hash0[:], - SkipCheck: true, - }, - Peer: node.ID(), - }, - }, - }) - - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } -} - -//Test requesting a chunk from a peer then issuing a "empty" OfferedHashesMsg (no hashes available yet) -//Should time out as the peer does not have the chunk (no syncing happened previously) -func TestStreamerUpstreamRetrieveRequestMsgExchangeWithoutStore(t *testing.T) { - tester, streamer, _, teardown, err := newStreamerTester(&RegistryOptions{ - Retrieval: RetrievalEnabled, - Syncing: SyncingDisabled, //do no syncing - }) - if err != nil { - t.Fatal(err) - } - defer teardown() - - node := tester.Nodes[0] - - chunk := storage.NewChunk(storage.Address(hash0[:]), nil) - - peer := streamer.getPeer(node.ID()) - - stream := NewStream(swarmChunkServerStreamName, "", true) - //simulate pre-subscription to RETRIEVE_REQUEST stream on peer - peer.handleSubscribeMsg(context.TODO(), &SubscribeMsg{ - Stream: stream, - History: nil, - Priority: Top, - }) - - //test the exchange - err = tester.TestExchanges(p2ptest.Exchange{ - Expects: []p2ptest.Expect{ - { //first expect a subscription to the RETRIEVE_REQUEST stream - Code: 4, - Msg: &SubscribeMsg{ - Stream: stream, - History: nil, - Priority: Top, - }, - Peer: node.ID(), - }, - }, - }, p2ptest.Exchange{ - Label: "RetrieveRequestMsg", - Triggers: []p2ptest.Trigger{ - { //then the actual RETRIEVE_REQUEST.... - Code: 5, - Msg: &RetrieveRequestMsg{ - Addr: chunk.Address()[:], - }, - Peer: node.ID(), - }, - }, - Expects: []p2ptest.Expect{ - { //to which the peer responds with offered hashes - Code: 1, - Msg: &OfferedHashesMsg{ - HandoverProof: nil, - Hashes: nil, - From: 0, - To: 0, - }, - Peer: node.ID(), - }, - }, - }) - - //should fail with a timeout as the peer we are requesting - //the chunk from does not have the chunk - expectedError := `exchange #1 "RetrieveRequestMsg": timed out` - if err == nil || err.Error() != expectedError { - t.Fatalf("Expected error %v, got %v", expectedError, err) - } -} - -// upstream request server receives a retrieve Request and responds with -// offered hashes or delivery if skipHash is set to true -func TestStreamerUpstreamRetrieveRequestMsgExchange(t *testing.T) { - tester, streamer, localStore, teardown, err := newStreamerTester(&RegistryOptions{ - Retrieval: RetrievalEnabled, - Syncing: SyncingDisabled, - }) - if err != nil { - t.Fatal(err) - } - defer teardown() - - node := tester.Nodes[0] - - peer := streamer.getPeer(node.ID()) - - stream := NewStream(swarmChunkServerStreamName, "", true) - - peer.handleSubscribeMsg(context.TODO(), &SubscribeMsg{ - Stream: stream, - History: nil, - Priority: Top, - }) - - hash := storage.Address(hash0[:]) - chunk := storage.NewChunk(hash, hash) - err = localStore.Put(context.TODO(), chunk) - if err != nil { - t.Fatalf("Expected no err got %v", err) - } - - err = tester.TestExchanges(p2ptest.Exchange{ - Expects: []p2ptest.Expect{ - { - Code: 4, - Msg: &SubscribeMsg{ - Stream: stream, - History: nil, - Priority: Top, - }, - Peer: node.ID(), - }, - }, - }, p2ptest.Exchange{ - Label: "RetrieveRequestMsg", - Triggers: []p2ptest.Trigger{ - { - Code: 5, - Msg: &RetrieveRequestMsg{ - Addr: hash, - }, - Peer: node.ID(), - }, - }, - Expects: []p2ptest.Expect{ - { - Code: 1, - Msg: &OfferedHashesMsg{ - HandoverProof: &HandoverProof{ - Handover: &Handover{}, - }, - Hashes: hash, - From: 0, - // TODO: why is this 32??? - To: 32, - Stream: stream, - }, - Peer: node.ID(), - }, - }, - }) - - if err != nil { - t.Fatal(err) - } - - hash = storage.Address(hash1[:]) - chunk = storage.NewChunk(hash, hash1[:]) - err = localStore.Put(context.TODO(), chunk) - if err != nil { - t.Fatalf("Expected no err got %v", err) - } - - err = tester.TestExchanges(p2ptest.Exchange{ - Label: "RetrieveRequestMsg", - Triggers: []p2ptest.Trigger{ - { - Code: 5, - Msg: &RetrieveRequestMsg{ - Addr: hash, - SkipCheck: true, - }, - Peer: node.ID(), - }, - }, - Expects: []p2ptest.Expect{ - { - Code: 6, - Msg: &ChunkDeliveryMsg{ - Addr: hash, - SData: hash, - }, - Peer: node.ID(), - }, - }, - }) - - if err != nil { - t.Fatal(err) - } -} - -// if there is one peer in the Kademlia, RequestFromPeers should return it -func TestRequestFromPeers(t *testing.T) { - dummyPeerID := enode.HexID("3431c3939e1ee2a6345e976a8234f9870152d64879f30bc272a074f6859e75e8") - - addr := network.RandomAddr() - to := network.NewKademlia(addr.OAddr, network.NewKadParams()) - delivery := NewDelivery(to, nil) - protocolsPeer := protocols.NewPeer(p2p.NewPeer(dummyPeerID, "dummy", nil), nil, nil) - peer := network.NewPeer(&network.BzzPeer{ - BzzAddr: network.RandomAddr(), - LightNode: false, - Peer: protocolsPeer, - }, to) - to.On(peer) - r := NewRegistry(addr.ID(), delivery, nil, nil, nil, nil) - - // an empty priorityQueue has to be created to prevent a goroutine being called after the test has finished - sp := &Peer{ - Peer: protocolsPeer, - pq: pq.New(int(PriorityQueue), PriorityQueueCap), - streamer: r, - } - r.setPeer(sp) - req := network.NewRequest( - storage.Address(hash0[:]), - true, - &sync.Map{}, - ) - ctx := context.Background() - id, _, err := delivery.RequestFromPeers(ctx, req) - - if err != nil { - t.Fatal(err) - } - if *id != dummyPeerID { - t.Fatalf("Expected an id, got %v", id) - } -} - -// RequestFromPeers should not return light nodes -func TestRequestFromPeersWithLightNode(t *testing.T) { - dummyPeerID := enode.HexID("3431c3939e1ee2a6345e976a8234f9870152d64879f30bc272a074f6859e75e8") - - addr := network.RandomAddr() - to := network.NewKademlia(addr.OAddr, network.NewKadParams()) - delivery := NewDelivery(to, nil) - - protocolsPeer := protocols.NewPeer(p2p.NewPeer(dummyPeerID, "dummy", nil), nil, nil) - // setting up a lightnode - peer := network.NewPeer(&network.BzzPeer{ - BzzAddr: network.RandomAddr(), - LightNode: true, - Peer: protocolsPeer, - }, to) - to.On(peer) - r := NewRegistry(addr.ID(), delivery, nil, nil, nil, nil) - // an empty priorityQueue has to be created to prevent a goroutine being called after the test has finished - sp := &Peer{ - Peer: protocolsPeer, - pq: pq.New(int(PriorityQueue), PriorityQueueCap), - streamer: r, - } - r.setPeer(sp) - - req := network.NewRequest( - storage.Address(hash0[:]), - true, - &sync.Map{}, - ) - - ctx := context.Background() - // making a request which should return with "no peer found" - _, _, err := delivery.RequestFromPeers(ctx, req) - - expectedError := "no peer found" - if err.Error() != expectedError { - t.Fatalf("expected '%v', got %v", expectedError, err) - } -} - -func TestStreamerDownstreamChunkDeliveryMsgExchange(t *testing.T) { - tester, streamer, localStore, teardown, err := newStreamerTester(&RegistryOptions{ - Retrieval: RetrievalDisabled, - Syncing: SyncingDisabled, - }) - if err != nil { - t.Fatal(err) - } - defer teardown() - - streamer.RegisterClientFunc("foo", func(p *Peer, t string, live bool) (Client, error) { - return &testClient{ - t: t, - }, nil - }) - - node := tester.Nodes[0] - - //subscribe to custom stream - stream := NewStream("foo", "", true) - err = streamer.Subscribe(node.ID(), stream, NewRange(5, 8), Top) - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - - chunkKey := hash0[:] - chunkData := hash1[:] - - err = tester.TestExchanges(p2ptest.Exchange{ - Label: "Subscribe message", - Expects: []p2ptest.Expect{ - { //first expect subscription to the custom stream... - Code: 4, - Msg: &SubscribeMsg{ - Stream: stream, - History: NewRange(5, 8), - Priority: Top, - }, - Peer: node.ID(), - }, - }, - }, - p2ptest.Exchange{ - Label: "ChunkDelivery message", - Triggers: []p2ptest.Trigger{ - { //...then trigger a chunk delivery for the given chunk from peer in order for - //local node to get the chunk delivered - Code: 6, - Msg: &ChunkDeliveryMsg{ - Addr: chunkKey, - SData: chunkData, - }, - Peer: node.ID(), - }, - }, - }) - - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) - defer cancel() - - // wait for the chunk to get stored - storedChunk, err := localStore.Get(ctx, chunkKey) - for err != nil { - select { - case <-ctx.Done(): - t.Fatalf("Chunk is not in localstore after timeout, err: %v", err) - default: - } - storedChunk, err = localStore.Get(ctx, chunkKey) - time.Sleep(50 * time.Millisecond) - } - - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - - if !bytes.Equal(storedChunk.Data(), chunkData) { - t.Fatal("Retrieved chunk has different data than original") - } - -} - -func TestDeliveryFromNodes(t *testing.T) { - testDeliveryFromNodes(t, 2, dataChunkCount, true) - testDeliveryFromNodes(t, 2, dataChunkCount, false) - testDeliveryFromNodes(t, 4, dataChunkCount, true) - testDeliveryFromNodes(t, 4, dataChunkCount, false) - testDeliveryFromNodes(t, 8, dataChunkCount, true) - testDeliveryFromNodes(t, 8, dataChunkCount, false) - testDeliveryFromNodes(t, 16, dataChunkCount, true) - testDeliveryFromNodes(t, 16, dataChunkCount, false) -} - -func testDeliveryFromNodes(t *testing.T, nodes, chunkCount int, skipCheck bool) { - t.Helper() - t.Run(fmt.Sprintf("testDeliveryFromNodes_%d_%d_skipCheck_%t", nodes, chunkCount, skipCheck), func(t *testing.T) { - sim := simulation.New(map[string]simulation.ServiceFunc{ - "streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) { - addr, netStore, delivery, clean, err := newNetStoreAndDelivery(ctx, bucket) - if err != nil { - return nil, nil, err - } - - r := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), &RegistryOptions{ - SkipCheck: skipCheck, - Syncing: SyncingDisabled, - Retrieval: RetrievalEnabled, - }, nil) - bucket.Store(bucketKeyRegistry, r) - - cleanup = func() { - r.Close() - clean() - } - - return r, cleanup, nil - }, - }) - defer sim.Close() - - log.Info("Adding nodes to simulation") - _, err := sim.AddNodesAndConnectChain(nodes) - if err != nil { - t.Fatal(err) - } - - log.Info("Starting simulation") - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) (err error) { - nodeIDs := sim.UpNodeIDs() - //determine the pivot node to be the first node of the simulation - pivot := nodeIDs[0] - - //distribute chunks of a random file into Stores of nodes 1 to nodes - //we will do this by creating a file store with an underlying round-robin store: - //the file store will create a hash for the uploaded file, but every chunk will be - //distributed to different nodes via round-robin scheduling - log.Debug("Writing file to round-robin file store") - //to do this, we create an array for chunkstores (length minus one, the pivot node) - stores := make([]storage.ChunkStore, len(nodeIDs)-1) - //we then need to get all stores from the sim.... - lStores := sim.NodesItems(bucketKeyStore) - i := 0 - //...iterate the buckets... - for id, bucketVal := range lStores { - //...and remove the one which is the pivot node - if id == pivot { - continue - } - //the other ones are added to the array... - stores[i] = bucketVal.(storage.ChunkStore) - i++ - } - //...which then gets passed to the round-robin file store - roundRobinFileStore := storage.NewFileStore(newRoundRobinStore(stores...), storage.NewFileStoreParams()) - //now we can actually upload a (random) file to the round-robin store - size := chunkCount * chunkSize - log.Debug("Storing data to file store") - fileHash, wait, err := roundRobinFileStore.Store(ctx, testutil.RandomReader(1, size), int64(size), false) - // wait until all chunks stored - if err != nil { - return err - } - err = wait(ctx) - if err != nil { - return err - } - - log.Debug("Waiting for kademlia") - // TODO this does not seem to be correct usage of the function, as the simulation may have no kademlias - if _, err := sim.WaitTillHealthy(ctx); err != nil { - return err - } - - //get the pivot node's filestore - item, ok := sim.NodeItem(pivot, bucketKeyFileStore) - if !ok { - return fmt.Errorf("No filestore") - } - pivotFileStore := item.(*storage.FileStore) - log.Debug("Starting retrieval routine") - retErrC := make(chan error) - go func() { - // start the retrieval on the pivot node - this will spawn retrieve requests for missing chunks - // we must wait for the peer connections to have started before requesting - n, err := readAll(pivotFileStore, fileHash) - log.Info(fmt.Sprintf("retrieved %v", fileHash), "read", n, "err", err) - retErrC <- err - }() - - disconnected := watchDisconnections(ctx, sim) - defer func() { - if err != nil && disconnected.bool() { - err = errors.New("disconnect events received") - } - }() - - //finally check that the pivot node gets all chunks via the root hash - log.Debug("Check retrieval") - success := true - var total int64 - total, err = readAll(pivotFileStore, fileHash) - if err != nil { - return err - } - log.Info(fmt.Sprintf("check if %08x is available locally: number of bytes read %v/%v (error: %v)", fileHash, total, size, err)) - if err != nil || total != int64(size) { - success = false - } - - if !success { - return fmt.Errorf("Test failed, chunks not available on all nodes") - } - if err := <-retErrC; err != nil { - return fmt.Errorf("requesting chunks: %v", err) - } - log.Debug("Test terminated successfully") - return nil - }) - if result.Error != nil { - t.Fatal(result.Error) - } - }) -} - -func BenchmarkDeliveryFromNodesWithoutCheck(b *testing.B) { - for chunks := 32; chunks <= 128; chunks *= 2 { - for i := 2; i < 32; i *= 2 { - b.Run( - fmt.Sprintf("nodes=%v,chunks=%v", i, chunks), - func(b *testing.B) { - benchmarkDeliveryFromNodes(b, i, chunks, true) - }, - ) - } - } -} - -func BenchmarkDeliveryFromNodesWithCheck(b *testing.B) { - for chunks := 32; chunks <= 128; chunks *= 2 { - for i := 2; i < 32; i *= 2 { - b.Run( - fmt.Sprintf("nodes=%v,chunks=%v", i, chunks), - func(b *testing.B) { - benchmarkDeliveryFromNodes(b, i, chunks, false) - }, - ) - } - } -} - -func benchmarkDeliveryFromNodes(b *testing.B, nodes, chunkCount int, skipCheck bool) { - sim := simulation.New(map[string]simulation.ServiceFunc{ - "streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) { - addr, netStore, delivery, clean, err := newNetStoreAndDelivery(ctx, bucket) - if err != nil { - return nil, nil, err - } - - r := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), &RegistryOptions{ - SkipCheck: skipCheck, - Syncing: SyncingDisabled, - Retrieval: RetrievalDisabled, - SyncUpdateDelay: 0, - }, nil) - bucket.Store(bucketKeyRegistry, r) - - cleanup = func() { - r.Close() - clean() - } - - return r, cleanup, nil - }, - }) - defer sim.Close() - - log.Info("Initializing test config") - _, err := sim.AddNodesAndConnectChain(nodes) - if err != nil { - b.Fatal(err) - } - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) (err error) { - nodeIDs := sim.UpNodeIDs() - node := nodeIDs[len(nodeIDs)-1] - - item, ok := sim.NodeItem(node, bucketKeyFileStore) - if !ok { - return errors.New("No filestore") - } - remoteFileStore := item.(*storage.FileStore) - - pivotNode := nodeIDs[0] - item, ok = sim.NodeItem(pivotNode, bucketKeyNetStore) - if !ok { - return errors.New("No filestore") - } - netStore := item.(*storage.NetStore) - - if _, err := sim.WaitTillHealthy(ctx); err != nil { - return err - } - - disconnected := watchDisconnections(ctx, sim) - defer func() { - if err != nil && disconnected.bool() { - err = errors.New("disconnect events received") - } - }() - // benchmark loop - b.ResetTimer() - b.StopTimer() - Loop: - for i := 0; i < b.N; i++ { - // uploading chunkCount random chunks to the last node - hashes := make([]storage.Address, chunkCount) - for i := 0; i < chunkCount; i++ { - // create actual size real chunks - ctx := context.TODO() - hash, wait, err := remoteFileStore.Store(ctx, testutil.RandomReader(i, chunkSize), int64(chunkSize), false) - if err != nil { - return fmt.Errorf("store: %v", err) - } - // wait until all chunks stored - err = wait(ctx) - if err != nil { - return fmt.Errorf("wait store: %v", err) - } - // collect the hashes - hashes[i] = hash - } - // now benchmark the actual retrieval - // netstore.Get is called for each hash in a go routine and errors are collected - b.StartTimer() - errs := make(chan error) - for _, hash := range hashes { - go func(h storage.Address) { - _, err := netStore.Get(ctx, h) - log.Warn("test check netstore get", "hash", h, "err", err) - errs <- err - }(hash) - } - // count and report retrieval errors - // if there are misses then chunk timeout is too low for the distance and volume (?) - var total, misses int - for err := range errs { - if err != nil { - log.Warn(err.Error()) - misses++ - } - total++ - if total == chunkCount { - break - } - } - b.StopTimer() - - if misses > 0 { - err = fmt.Errorf("%v chunk not found out of %v", misses, total) - break Loop - } - } - return err - }) - if result.Error != nil { - b.Fatal(result.Error) - } - -} diff --git a/swarm/network/stream/intervals/dbstore_test.go b/swarm/network/stream/intervals/dbstore_test.go deleted file mode 100644 index 4aef5c846545..000000000000 --- a/swarm/network/stream/intervals/dbstore_test.go +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package intervals - -import ( - "io/ioutil" - "os" - "testing" - - "github.com/ubiq/go-ubiq/swarm/state" -) - -// TestDBStore tests basic functionality of DBStore. -func TestDBStore(t *testing.T) { - dir, err := ioutil.TempDir("", "intervals_test_db_store") - if err != nil { - panic(err) - } - defer os.RemoveAll(dir) - - store, err := state.NewDBStore(dir) - if err != nil { - t.Fatal(err) - } - defer store.Close() - - testStore(t, store) -} diff --git a/swarm/network/stream/intervals/intervals.go b/swarm/network/stream/intervals/intervals.go deleted file mode 100644 index 562c3df9ae49..000000000000 --- a/swarm/network/stream/intervals/intervals.go +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package intervals - -import ( - "bytes" - "fmt" - "strconv" - "sync" -) - -// Intervals store a list of intervals. Its purpose is to provide -// methods to add new intervals and retrieve missing intervals that -// need to be added. -// It may be used in synchronization of streaming data to persist -// retrieved data ranges between sessions. -type Intervals struct { - start uint64 - ranges [][2]uint64 - mu sync.RWMutex -} - -// New creates a new instance of Intervals. -// Start argument limits the lower bound of intervals. -// No range bellow start bound will be added by Add method or -// returned by Next method. This limit may be used for -// tracking "live" synchronization, where the sync session -// starts from a specific value, and if "live" sync intervals -// need to be merged with historical ones, it can be safely done. -func NewIntervals(start uint64) *Intervals { - return &Intervals{ - start: start, - } -} - -// Add adds a new range to intervals. Range start and end are values -// are both inclusive. -func (i *Intervals) Add(start, end uint64) { - i.mu.Lock() - defer i.mu.Unlock() - - i.add(start, end) -} - -func (i *Intervals) add(start, end uint64) { - if start < i.start { - start = i.start - } - if end < i.start { - return - } - minStartJ := -1 - maxEndJ := -1 - j := 0 - for ; j < len(i.ranges); j++ { - if minStartJ < 0 { - if (start <= i.ranges[j][0] && end+1 >= i.ranges[j][0]) || (start <= i.ranges[j][1]+1 && end+1 >= i.ranges[j][1]) { - if i.ranges[j][0] < start { - start = i.ranges[j][0] - } - minStartJ = j - } - } - if (start <= i.ranges[j][1] && end+1 >= i.ranges[j][1]) || (start <= i.ranges[j][0] && end+1 >= i.ranges[j][0]) { - if i.ranges[j][1] > end { - end = i.ranges[j][1] - } - maxEndJ = j - } - if end+1 <= i.ranges[j][0] { - break - } - } - if minStartJ < 0 && maxEndJ < 0 { - i.ranges = append(i.ranges[:j], append([][2]uint64{{start, end}}, i.ranges[j:]...)...) - return - } - if minStartJ >= 0 { - i.ranges[minStartJ][0] = start - } - if maxEndJ >= 0 { - i.ranges[maxEndJ][1] = end - } - if minStartJ >= 0 && maxEndJ >= 0 && minStartJ != maxEndJ { - i.ranges[maxEndJ][0] = start - i.ranges = append(i.ranges[:minStartJ], i.ranges[maxEndJ:]...) - } -} - -// Merge adds all the intervals from the m Interval to current one. -func (i *Intervals) Merge(m *Intervals) { - m.mu.RLock() - defer m.mu.RUnlock() - i.mu.Lock() - defer i.mu.Unlock() - - for _, r := range m.ranges { - i.add(r[0], r[1]) - } -} - -// Next returns the first range interval that is not fulfilled. Returned -// start and end values are both inclusive, meaning that the whole range -// including start and end need to be added in order to full the gap -// in intervals. -// Returned value for end is 0 if the next interval is after the whole -// range that is stored in Intervals. Zero end value represents no limit -// on the next interval length. -func (i *Intervals) Next() (start, end uint64) { - i.mu.RLock() - defer i.mu.RUnlock() - - l := len(i.ranges) - if l == 0 { - return i.start, 0 - } - if i.ranges[0][0] != i.start { - return i.start, i.ranges[0][0] - 1 - } - if l == 1 { - return i.ranges[0][1] + 1, 0 - } - return i.ranges[0][1] + 1, i.ranges[1][0] - 1 -} - -// Last returns the value that is at the end of the last interval. -func (i *Intervals) Last() (end uint64) { - i.mu.RLock() - defer i.mu.RUnlock() - - l := len(i.ranges) - if l == 0 { - return 0 - } - return i.ranges[l-1][1] -} - -// String returns a descriptive representation of range intervals -// in [] notation, as a list of two element vectors. -func (i *Intervals) String() string { - return fmt.Sprint(i.ranges) -} - -// MarshalBinary encodes Intervals parameters into a semicolon separated list. -// The first element in the list is base36-encoded start value. The following -// elements are two base36-encoded value ranges separated by comma. -func (i *Intervals) MarshalBinary() (data []byte, err error) { - d := make([][]byte, len(i.ranges)+1) - d[0] = []byte(strconv.FormatUint(i.start, 36)) - for j := range i.ranges { - r := i.ranges[j] - d[j+1] = []byte(strconv.FormatUint(r[0], 36) + "," + strconv.FormatUint(r[1], 36)) - } - return bytes.Join(d, []byte(";")), nil -} - -// UnmarshalBinary decodes data according to the Intervals.MarshalBinary format. -func (i *Intervals) UnmarshalBinary(data []byte) (err error) { - d := bytes.Split(data, []byte(";")) - l := len(d) - if l == 0 { - return nil - } - if l >= 1 { - i.start, err = strconv.ParseUint(string(d[0]), 36, 64) - if err != nil { - return err - } - } - if l == 1 { - return nil - } - - i.ranges = make([][2]uint64, 0, l-1) - for j := 1; j < l; j++ { - r := bytes.SplitN(d[j], []byte(","), 2) - if len(r) < 2 { - return fmt.Errorf("range %d has less then 2 elements", j) - } - start, err := strconv.ParseUint(string(r[0]), 36, 64) - if err != nil { - return fmt.Errorf("parsing the first element in range %d: %v", j, err) - } - end, err := strconv.ParseUint(string(r[1]), 36, 64) - if err != nil { - return fmt.Errorf("parsing the second element in range %d: %v", j, err) - } - i.ranges = append(i.ranges, [2]uint64{start, end}) - } - - return nil -} diff --git a/swarm/network/stream/intervals/intervals_test.go b/swarm/network/stream/intervals/intervals_test.go deleted file mode 100644 index b5212f0d917f..000000000000 --- a/swarm/network/stream/intervals/intervals_test.go +++ /dev/null @@ -1,395 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package intervals - -import "testing" - -// Test tests Interval methods Add, Next and Last for various -// initial state. -func Test(t *testing.T) { - for i, tc := range []struct { - startLimit uint64 - initial [][2]uint64 - start uint64 - end uint64 - expected string - nextStart uint64 - nextEnd uint64 - last uint64 - }{ - { - initial: nil, - start: 0, - end: 0, - expected: "[[0 0]]", - nextStart: 1, - nextEnd: 0, - last: 0, - }, - { - initial: nil, - start: 0, - end: 10, - expected: "[[0 10]]", - nextStart: 11, - nextEnd: 0, - last: 10, - }, - { - initial: nil, - start: 5, - end: 15, - expected: "[[5 15]]", - nextStart: 0, - nextEnd: 4, - last: 15, - }, - { - initial: [][2]uint64{{0, 0}}, - start: 0, - end: 0, - expected: "[[0 0]]", - nextStart: 1, - nextEnd: 0, - last: 0, - }, - { - initial: [][2]uint64{{0, 0}}, - start: 5, - end: 15, - expected: "[[0 0] [5 15]]", - nextStart: 1, - nextEnd: 4, - last: 15, - }, - { - initial: [][2]uint64{{5, 15}}, - start: 5, - end: 15, - expected: "[[5 15]]", - nextStart: 0, - nextEnd: 4, - last: 15, - }, - { - initial: [][2]uint64{{5, 15}}, - start: 5, - end: 20, - expected: "[[5 20]]", - nextStart: 0, - nextEnd: 4, - last: 20, - }, - { - initial: [][2]uint64{{5, 15}}, - start: 10, - end: 20, - expected: "[[5 20]]", - nextStart: 0, - nextEnd: 4, - last: 20, - }, - { - initial: [][2]uint64{{5, 15}}, - start: 0, - end: 20, - expected: "[[0 20]]", - nextStart: 21, - nextEnd: 0, - last: 20, - }, - { - initial: [][2]uint64{{5, 15}}, - start: 2, - end: 10, - expected: "[[2 15]]", - nextStart: 0, - nextEnd: 1, - last: 15, - }, - { - initial: [][2]uint64{{5, 15}}, - start: 2, - end: 4, - expected: "[[2 15]]", - nextStart: 0, - nextEnd: 1, - last: 15, - }, - { - initial: [][2]uint64{{5, 15}}, - start: 2, - end: 5, - expected: "[[2 15]]", - nextStart: 0, - nextEnd: 1, - last: 15, - }, - { - initial: [][2]uint64{{5, 15}}, - start: 2, - end: 3, - expected: "[[2 3] [5 15]]", - nextStart: 0, - nextEnd: 1, - last: 15, - }, - { - initial: [][2]uint64{{5, 15}}, - start: 2, - end: 4, - expected: "[[2 15]]", - nextStart: 0, - nextEnd: 1, - last: 15, - }, - { - initial: [][2]uint64{{0, 1}, {5, 15}}, - start: 2, - end: 4, - expected: "[[0 15]]", - nextStart: 16, - nextEnd: 0, - last: 15, - }, - { - initial: [][2]uint64{{0, 5}, {15, 20}}, - start: 2, - end: 10, - expected: "[[0 10] [15 20]]", - nextStart: 11, - nextEnd: 14, - last: 20, - }, - { - initial: [][2]uint64{{0, 5}, {15, 20}}, - start: 8, - end: 18, - expected: "[[0 5] [8 20]]", - nextStart: 6, - nextEnd: 7, - last: 20, - }, - { - initial: [][2]uint64{{0, 5}, {15, 20}}, - start: 2, - end: 17, - expected: "[[0 20]]", - nextStart: 21, - nextEnd: 0, - last: 20, - }, - { - initial: [][2]uint64{{0, 5}, {15, 20}}, - start: 2, - end: 25, - expected: "[[0 25]]", - nextStart: 26, - nextEnd: 0, - last: 25, - }, - { - initial: [][2]uint64{{0, 5}, {15, 20}}, - start: 5, - end: 14, - expected: "[[0 20]]", - nextStart: 21, - nextEnd: 0, - last: 20, - }, - { - initial: [][2]uint64{{0, 5}, {15, 20}}, - start: 6, - end: 14, - expected: "[[0 20]]", - nextStart: 21, - nextEnd: 0, - last: 20, - }, - { - initial: [][2]uint64{{0, 5}, {15, 20}, {30, 40}}, - start: 6, - end: 29, - expected: "[[0 40]]", - nextStart: 41, - nextEnd: 0, - last: 40, - }, - { - initial: [][2]uint64{{0, 5}, {15, 20}, {30, 40}, {50, 60}}, - start: 3, - end: 55, - expected: "[[0 60]]", - nextStart: 61, - nextEnd: 0, - last: 60, - }, - { - initial: [][2]uint64{{0, 5}, {15, 20}, {30, 40}, {50, 60}}, - start: 21, - end: 49, - expected: "[[0 5] [15 60]]", - nextStart: 6, - nextEnd: 14, - last: 60, - }, - { - initial: [][2]uint64{{0, 5}, {15, 20}, {30, 40}, {50, 60}}, - start: 0, - end: 100, - expected: "[[0 100]]", - nextStart: 101, - nextEnd: 0, - last: 100, - }, - { - startLimit: 100, - initial: nil, - start: 0, - end: 0, - expected: "[]", - nextStart: 100, - nextEnd: 0, - last: 0, - }, - { - startLimit: 100, - initial: nil, - start: 20, - end: 30, - expected: "[]", - nextStart: 100, - nextEnd: 0, - last: 0, - }, - { - startLimit: 100, - initial: nil, - start: 50, - end: 100, - expected: "[[100 100]]", - nextStart: 101, - nextEnd: 0, - last: 100, - }, - { - startLimit: 100, - initial: nil, - start: 50, - end: 110, - expected: "[[100 110]]", - nextStart: 111, - nextEnd: 0, - last: 110, - }, - { - startLimit: 100, - initial: nil, - start: 120, - end: 130, - expected: "[[120 130]]", - nextStart: 100, - nextEnd: 119, - last: 130, - }, - { - startLimit: 100, - initial: nil, - start: 120, - end: 130, - expected: "[[120 130]]", - nextStart: 100, - nextEnd: 119, - last: 130, - }, - } { - intervals := NewIntervals(tc.startLimit) - intervals.ranges = tc.initial - intervals.Add(tc.start, tc.end) - got := intervals.String() - if got != tc.expected { - t.Errorf("interval #%d: expected %s, got %s", i, tc.expected, got) - } - nextStart, nextEnd := intervals.Next() - if nextStart != tc.nextStart { - t.Errorf("interval #%d, expected next start %d, got %d", i, tc.nextStart, nextStart) - } - if nextEnd != tc.nextEnd { - t.Errorf("interval #%d, expected next end %d, got %d", i, tc.nextEnd, nextEnd) - } - last := intervals.Last() - if last != tc.last { - t.Errorf("interval #%d, expected last %d, got %d", i, tc.last, last) - } - } -} - -func TestMerge(t *testing.T) { - for i, tc := range []struct { - initial [][2]uint64 - merge [][2]uint64 - expected string - }{ - { - initial: nil, - merge: nil, - expected: "[]", - }, - { - initial: [][2]uint64{{10, 20}}, - merge: nil, - expected: "[[10 20]]", - }, - { - initial: nil, - merge: [][2]uint64{{15, 25}}, - expected: "[[15 25]]", - }, - { - initial: [][2]uint64{{0, 100}}, - merge: [][2]uint64{{150, 250}}, - expected: "[[0 100] [150 250]]", - }, - { - initial: [][2]uint64{{0, 100}}, - merge: [][2]uint64{{101, 250}}, - expected: "[[0 250]]", - }, - { - initial: [][2]uint64{{0, 10}, {30, 40}}, - merge: [][2]uint64{{20, 25}, {41, 50}}, - expected: "[[0 10] [20 25] [30 50]]", - }, - { - initial: [][2]uint64{{0, 5}, {15, 20}, {30, 40}, {50, 60}}, - merge: [][2]uint64{{6, 25}}, - expected: "[[0 25] [30 40] [50 60]]", - }, - } { - intervals := NewIntervals(0) - intervals.ranges = tc.initial - m := NewIntervals(0) - m.ranges = tc.merge - - intervals.Merge(m) - - got := intervals.String() - if got != tc.expected { - t.Errorf("interval #%d: expected %s, got %s", i, tc.expected, got) - } - } -} diff --git a/swarm/network/stream/intervals/store_test.go b/swarm/network/stream/intervals/store_test.go deleted file mode 100644 index 6355c18a18b6..000000000000 --- a/swarm/network/stream/intervals/store_test.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package intervals - -import ( - "testing" - - "github.com/ubiq/go-ubiq/swarm/state" -) - -// TestInmemoryStore tests basic functionality of InmemoryStore. -func TestInmemoryStore(t *testing.T) { - testStore(t, state.NewInmemoryStore()) -} - -// testStore is a helper function to test various Store implementations. -func testStore(t *testing.T, s state.Store) { - key1 := "key1" - i1 := NewIntervals(0) - i1.Add(10, 20) - if err := s.Put(key1, i1); err != nil { - t.Fatal(err) - } - i := &Intervals{} - err := s.Get(key1, i) - if err != nil { - t.Fatal(err) - } - if i.String() != i1.String() { - t.Errorf("expected interval %s, got %s", i1, i) - } - - key2 := "key2" - i2 := NewIntervals(0) - i2.Add(10, 20) - if err := s.Put(key2, i2); err != nil { - t.Fatal(err) - } - err = s.Get(key2, i) - if err != nil { - t.Fatal(err) - } - if i.String() != i2.String() { - t.Errorf("expected interval %s, got %s", i2, i) - } - - if err := s.Delete(key1); err != nil { - t.Fatal(err) - } - if err := s.Get(key1, i); err != state.ErrNotFound { - t.Errorf("expected error %v, got %s", state.ErrNotFound, err) - } - if err := s.Get(key2, i); err != nil { - t.Errorf("expected error %v, got %s", nil, err) - } - - if err := s.Delete(key2); err != nil { - t.Fatal(err) - } - if err := s.Get(key2, i); err != state.ErrNotFound { - t.Errorf("expected error %v, got %s", state.ErrNotFound, err) - } -} diff --git a/swarm/network/stream/intervals_test.go b/swarm/network/stream/intervals_test.go deleted file mode 100644 index 7d896ee8465d..000000000000 --- a/swarm/network/stream/intervals_test.go +++ /dev/null @@ -1,362 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package stream - -import ( - "context" - "encoding/binary" - "errors" - "fmt" - "sync" - "testing" - "time" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" - "github.com/ubiq/go-ubiq/swarm/network/simulation" - "github.com/ubiq/go-ubiq/swarm/state" - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/ubiq/go-ubiq/swarm/testutil" -) - -func TestIntervalsLive(t *testing.T) { - testIntervals(t, true, nil, false) - testIntervals(t, true, nil, true) -} - -func TestIntervalsHistory(t *testing.T) { - testIntervals(t, false, NewRange(9, 26), false) - testIntervals(t, false, NewRange(9, 26), true) -} - -func TestIntervalsLiveAndHistory(t *testing.T) { - testIntervals(t, true, NewRange(9, 26), false) - testIntervals(t, true, NewRange(9, 26), true) -} - -func testIntervals(t *testing.T, live bool, history *Range, skipCheck bool) { - - nodes := 2 - chunkCount := dataChunkCount - externalStreamName := "externalStream" - externalStreamSessionAt := uint64(50) - externalStreamMaxKeys := uint64(100) - - sim := simulation.New(map[string]simulation.ServiceFunc{ - "intervalsStreamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (node.Service, func(), error) { - addr, netStore, delivery, clean, err := newNetStoreAndDelivery(ctx, bucket) - if err != nil { - return nil, nil, err - } - - r := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), &RegistryOptions{ - Retrieval: RetrievalDisabled, - Syncing: SyncingRegisterOnly, - SkipCheck: skipCheck, - }, nil) - bucket.Store(bucketKeyRegistry, r) - - r.RegisterClientFunc(externalStreamName, func(p *Peer, t string, live bool) (Client, error) { - return newTestExternalClient(netStore), nil - }) - r.RegisterServerFunc(externalStreamName, func(p *Peer, t string, live bool) (Server, error) { - return newTestExternalServer(t, externalStreamSessionAt, externalStreamMaxKeys, nil), nil - }) - - cleanup := func() { - r.Close() - clean() - } - - return r, cleanup, nil - }, - }) - defer sim.Close() - - log.Info("Adding nodes to simulation") - _, err := sim.AddNodesAndConnectChain(nodes) - if err != nil { - t.Fatal(err) - } - - ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second) - defer cancel() - - if _, err := sim.WaitTillHealthy(ctx); err != nil { - t.Fatal(err) - } - - result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) (err error) { - nodeIDs := sim.UpNodeIDs() - storer := nodeIDs[0] - checker := nodeIDs[1] - - item, ok := sim.NodeItem(storer, bucketKeyFileStore) - if !ok { - return fmt.Errorf("No filestore") - } - fileStore := item.(*storage.FileStore) - - size := chunkCount * chunkSize - - _, wait, err := fileStore.Store(ctx, testutil.RandomReader(1, size), int64(size), false) - if err != nil { - return fmt.Errorf("store: %v", err) - } - err = wait(ctx) - if err != nil { - return fmt.Errorf("wait store: %v", err) - } - - item, ok = sim.NodeItem(checker, bucketKeyRegistry) - if !ok { - return fmt.Errorf("No registry") - } - registry := item.(*Registry) - - liveErrC := make(chan error) - historyErrC := make(chan error) - - err = registry.Subscribe(storer, NewStream(externalStreamName, "", live), history, Top) - if err != nil { - return err - } - - disconnected := watchDisconnections(ctx, sim) - defer func() { - if err != nil && disconnected.bool() { - err = errors.New("disconnect events received") - } - }() - - go func() { - if !live { - close(liveErrC) - return - } - - var err error - defer func() { - liveErrC <- err - }() - - // live stream - var liveHashesChan chan []byte - liveHashesChan, err = getHashes(ctx, registry, storer, NewStream(externalStreamName, "", true)) - if err != nil { - log.Error("get hashes", "err", err) - return - } - i := externalStreamSessionAt - - // we have subscribed, enable notifications - err = enableNotifications(registry, storer, NewStream(externalStreamName, "", true)) - if err != nil { - return - } - - for { - select { - case hash := <-liveHashesChan: - h := binary.BigEndian.Uint64(hash) - if h != i { - err = fmt.Errorf("expected live hash %d, got %d", i, h) - return - } - i++ - if i > externalStreamMaxKeys { - return - } - case <-ctx.Done(): - return - } - } - }() - - go func() { - if live && history == nil { - close(historyErrC) - return - } - - var err error - defer func() { - historyErrC <- err - }() - - // history stream - var historyHashesChan chan []byte - historyHashesChan, err = getHashes(ctx, registry, storer, NewStream(externalStreamName, "", false)) - if err != nil { - log.Error("get hashes", "err", err) - return - } - - var i uint64 - historyTo := externalStreamMaxKeys - if history != nil { - i = history.From - if history.To != 0 { - historyTo = history.To - } - } - - // we have subscribed, enable notifications - err = enableNotifications(registry, storer, NewStream(externalStreamName, "", false)) - if err != nil { - return - } - - for { - select { - case hash := <-historyHashesChan: - h := binary.BigEndian.Uint64(hash) - if h != i { - err = fmt.Errorf("expected history hash %d, got %d", i, h) - return - } - i++ - if i > historyTo { - return - } - case <-ctx.Done(): - return - } - } - }() - - if err := <-liveErrC; err != nil { - return err - } - if err := <-historyErrC; err != nil { - return err - } - - return nil - }) - - if result.Error != nil { - t.Fatal(result.Error) - } -} - -func getHashes(ctx context.Context, r *Registry, peerID enode.ID, s Stream) (chan []byte, error) { - peer := r.getPeer(peerID) - - client, err := peer.getClient(ctx, s) - if err != nil { - return nil, err - } - - c := client.Client.(*testExternalClient) - - return c.hashes, nil -} - -func enableNotifications(r *Registry, peerID enode.ID, s Stream) error { - peer := r.getPeer(peerID) - - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - client, err := peer.getClient(ctx, s) - if err != nil { - return err - } - - close(client.Client.(*testExternalClient).enableNotificationsC) - - return nil -} - -type testExternalClient struct { - hashes chan []byte - store storage.SyncChunkStore - enableNotificationsC chan struct{} -} - -func newTestExternalClient(store storage.SyncChunkStore) *testExternalClient { - return &testExternalClient{ - hashes: make(chan []byte), - store: store, - enableNotificationsC: make(chan struct{}), - } -} - -func (c *testExternalClient) NeedData(ctx context.Context, hash []byte) func(context.Context) error { - wait := c.store.FetchFunc(ctx, storage.Address(hash)) - if wait == nil { - return nil - } - select { - case c.hashes <- hash: - case <-ctx.Done(): - log.Warn("testExternalClient NeedData context", "err", ctx.Err()) - return func(_ context.Context) error { - return ctx.Err() - } - } - return wait -} - -func (c *testExternalClient) BatchDone(Stream, uint64, []byte, []byte) func() (*TakeoverProof, error) { - return nil -} - -func (c *testExternalClient) Close() {} - -type testExternalServer struct { - t string - keyFunc func(key []byte, index uint64) - sessionAt uint64 - maxKeys uint64 -} - -func newTestExternalServer(t string, sessionAt, maxKeys uint64, keyFunc func(key []byte, index uint64)) *testExternalServer { - if keyFunc == nil { - keyFunc = binary.BigEndian.PutUint64 - } - return &testExternalServer{ - t: t, - keyFunc: keyFunc, - sessionAt: sessionAt, - maxKeys: maxKeys, - } -} - -func (s *testExternalServer) SessionIndex() (uint64, error) { - return s.sessionAt, nil -} - -func (s *testExternalServer) SetNextBatch(from uint64, to uint64) ([]byte, uint64, uint64, *HandoverProof, error) { - if to > s.maxKeys { - to = s.maxKeys - } - b := make([]byte, HashSize*(to-from+1)) - for i := from; i <= to; i++ { - s.keyFunc(b[(i-from)*HashSize:(i-from+1)*HashSize], i) - } - return b, from, to, nil, nil -} - -func (s *testExternalServer) GetData(context.Context, []byte) ([]byte, error) { - return make([]byte, 4096), nil -} - -func (s *testExternalServer) Close() {} diff --git a/swarm/network/stream/lightnode_test.go b/swarm/network/stream/lightnode_test.go deleted file mode 100644 index a45b4322bbc6..000000000000 --- a/swarm/network/stream/lightnode_test.go +++ /dev/null @@ -1,214 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . -package stream - -import ( - "testing" - - p2ptest "github.com/ubiq/go-ubiq/p2p/testing" -) - -// This test checks the default behavior of the server, that is -// when it is serving Retrieve requests. -func TestLigthnodeRetrieveRequestWithRetrieve(t *testing.T) { - registryOptions := &RegistryOptions{ - Retrieval: RetrievalClientOnly, - Syncing: SyncingDisabled, - } - tester, _, _, teardown, err := newStreamerTester(registryOptions) - if err != nil { - t.Fatal(err) - } - defer teardown() - - node := tester.Nodes[0] - - stream := NewStream(swarmChunkServerStreamName, "", false) - - err = tester.TestExchanges(p2ptest.Exchange{ - Label: "SubscribeMsg", - Triggers: []p2ptest.Trigger{ - { - Code: 4, - Msg: &SubscribeMsg{ - Stream: stream, - }, - Peer: node.ID(), - }, - }, - }) - if err != nil { - t.Fatalf("Got %v", err) - } - - err = tester.TestDisconnected(&p2ptest.Disconnect{Peer: node.ID()}) - if err == nil || err.Error() != "timed out waiting for peers to disconnect" { - t.Fatalf("Expected no disconnect, got %v", err) - } -} - -// This test checks the Lightnode behavior of server, when serving Retrieve -// requests are disabled -func TestLigthnodeRetrieveRequestWithoutRetrieve(t *testing.T) { - registryOptions := &RegistryOptions{ - Retrieval: RetrievalDisabled, - Syncing: SyncingDisabled, - } - tester, _, _, teardown, err := newStreamerTester(registryOptions) - if err != nil { - t.Fatal(err) - } - defer teardown() - - node := tester.Nodes[0] - - stream := NewStream(swarmChunkServerStreamName, "", false) - - err = tester.TestExchanges( - p2ptest.Exchange{ - Label: "SubscribeMsg", - Triggers: []p2ptest.Trigger{ - { - Code: 4, - Msg: &SubscribeMsg{ - Stream: stream, - }, - Peer: node.ID(), - }, - }, - Expects: []p2ptest.Expect{ - { - Code: 7, - Msg: &SubscribeErrorMsg{ - Error: "stream RETRIEVE_REQUEST not registered", - }, - Peer: node.ID(), - }, - }, - }) - if err != nil { - t.Fatalf("Got %v", err) - } -} - -// This test checks the default behavior of the server, that is -// when syncing is enabled. -func TestLigthnodeRequestSubscriptionWithSync(t *testing.T) { - registryOptions := &RegistryOptions{ - Retrieval: RetrievalDisabled, - Syncing: SyncingRegisterOnly, - } - tester, _, _, teardown, err := newStreamerTester(registryOptions) - if err != nil { - t.Fatal(err) - } - defer teardown() - - node := tester.Nodes[0] - - syncStream := NewStream("SYNC", FormatSyncBinKey(1), false) - - err = tester.TestExchanges( - p2ptest.Exchange{ - Label: "RequestSubscription", - Triggers: []p2ptest.Trigger{ - { - Code: 8, - Msg: &RequestSubscriptionMsg{ - Stream: syncStream, - }, - Peer: node.ID(), - }, - }, - Expects: []p2ptest.Expect{ - { - Code: 4, - Msg: &SubscribeMsg{ - Stream: syncStream, - }, - Peer: node.ID(), - }, - }, - }) - - if err != nil { - t.Fatalf("Got %v", err) - } -} - -// This test checks the Lightnode behavior of the server, that is -// when syncing is disabled. -func TestLigthnodeRequestSubscriptionWithoutSync(t *testing.T) { - registryOptions := &RegistryOptions{ - Retrieval: RetrievalDisabled, - Syncing: SyncingDisabled, - } - tester, _, _, teardown, err := newStreamerTester(registryOptions) - if err != nil { - t.Fatal(err) - } - defer teardown() - - node := tester.Nodes[0] - - syncStream := NewStream("SYNC", FormatSyncBinKey(1), false) - - err = tester.TestExchanges(p2ptest.Exchange{ - Label: "RequestSubscription", - Triggers: []p2ptest.Trigger{ - { - Code: 8, - Msg: &RequestSubscriptionMsg{ - Stream: syncStream, - }, - Peer: node.ID(), - }, - }, - Expects: []p2ptest.Expect{ - { - Code: 7, - Msg: &SubscribeErrorMsg{ - Error: "stream SYNC not registered", - }, - Peer: node.ID(), - }, - }, - }, p2ptest.Exchange{ - Label: "RequestSubscription", - Triggers: []p2ptest.Trigger{ - { - Code: 4, - Msg: &SubscribeMsg{ - Stream: syncStream, - }, - Peer: node.ID(), - }, - }, - Expects: []p2ptest.Expect{ - { - Code: 7, - Msg: &SubscribeErrorMsg{ - Error: "stream SYNC not registered", - }, - Peer: node.ID(), - }, - }, - }) - - if err != nil { - t.Fatalf("Got %v", err) - } -} diff --git a/swarm/network/stream/messages.go b/swarm/network/stream/messages.go deleted file mode 100644 index 62a0acf0b10b..000000000000 --- a/swarm/network/stream/messages.go +++ /dev/null @@ -1,405 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package stream - -import ( - "context" - "fmt" - "time" - - "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/swarm/log" - bv "github.com/ubiq/go-ubiq/swarm/network/bitvector" - "github.com/ubiq/go-ubiq/swarm/spancontext" - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/opentracing/opentracing-go" -) - -var syncBatchTimeout = 30 * time.Second - -// Stream defines a unique stream identifier. -type Stream struct { - // Name is used for Client and Server functions identification. - Name string - // Key is the name of specific stream data. - Key string - // Live defines whether the stream delivers only new data - // for the specific stream. - Live bool -} - -func NewStream(name string, key string, live bool) Stream { - return Stream{ - Name: name, - Key: key, - Live: live, - } -} - -// String return a stream id based on all Stream fields. -func (s Stream) String() string { - t := "h" - if s.Live { - t = "l" - } - return fmt.Sprintf("%s|%s|%s", s.Name, s.Key, t) -} - -// SubcribeMsg is the protocol msg for requesting a stream(section) -type SubscribeMsg struct { - Stream Stream - History *Range `rlp:"nil"` - Priority uint8 // delivered on priority channel -} - -// RequestSubscriptionMsg is the protocol msg for a node to request subscription to a -// specific stream -type RequestSubscriptionMsg struct { - Stream Stream - History *Range `rlp:"nil"` - Priority uint8 // delivered on priority channel -} - -func (p *Peer) handleRequestSubscription(ctx context.Context, req *RequestSubscriptionMsg) (err error) { - log.Debug(fmt.Sprintf("handleRequestSubscription: streamer %s to subscribe to %s with stream %s", p.streamer.addr, p.ID(), req.Stream)) - if err = p.streamer.Subscribe(p.ID(), req.Stream, req.History, req.Priority); err != nil { - // The error will be sent as a subscribe error message - // and will not be returned as it will prevent any new message - // exchange between peers over p2p. Instead, error will be returned - // only if there is one from sending subscribe error message. - err = p.Send(ctx, SubscribeErrorMsg{ - Error: err.Error(), - }) - } - return err -} - -func (p *Peer) handleSubscribeMsg(ctx context.Context, req *SubscribeMsg) (err error) { - metrics.GetOrRegisterCounter("peer.handlesubscribemsg", nil).Inc(1) - - defer func() { - if err != nil { - // The error will be sent as a subscribe error message - // and will not be returned as it will prevent any new message - // exchange between peers over p2p. Instead, error will be returned - // only if there is one from sending subscribe error message. - err = p.Send(context.TODO(), SubscribeErrorMsg{ - Error: err.Error(), - }) - } - }() - - log.Debug("received subscription", "from", p.streamer.addr, "peer", p.ID(), "stream", req.Stream, "history", req.History) - - f, err := p.streamer.GetServerFunc(req.Stream.Name) - if err != nil { - return err - } - - s, err := f(p, req.Stream.Key, req.Stream.Live) - if err != nil { - return err - } - os, err := p.setServer(req.Stream, s, req.Priority) - if err != nil { - return err - } - - var from uint64 - var to uint64 - if !req.Stream.Live && req.History != nil { - from = req.History.From - to = req.History.To - } - - go func() { - if err := p.SendOfferedHashes(os, from, to); err != nil { - log.Warn("SendOfferedHashes error", "peer", p.ID().TerminalString(), "err", err) - } - }() - - if req.Stream.Live && req.History != nil { - // subscribe to the history stream - s, err := f(p, req.Stream.Key, false) - if err != nil { - return err - } - - os, err := p.setServer(getHistoryStream(req.Stream), s, getHistoryPriority(req.Priority)) - if err != nil { - return err - } - go func() { - if err := p.SendOfferedHashes(os, req.History.From, req.History.To); err != nil { - log.Warn("SendOfferedHashes error", "peer", p.ID().TerminalString(), "err", err) - } - }() - } - - return nil -} - -type SubscribeErrorMsg struct { - Error string -} - -func (p *Peer) handleSubscribeErrorMsg(req *SubscribeErrorMsg) (err error) { - //TODO the error should be channeled to whoever calls the subscribe - return fmt.Errorf("subscribe to peer %s: %v", p.ID(), req.Error) -} - -type UnsubscribeMsg struct { - Stream Stream -} - -func (p *Peer) handleUnsubscribeMsg(req *UnsubscribeMsg) error { - return p.removeServer(req.Stream) -} - -type QuitMsg struct { - Stream Stream -} - -func (p *Peer) handleQuitMsg(req *QuitMsg) error { - return p.removeClient(req.Stream) -} - -// OfferedHashesMsg is the protocol msg for offering to hand over a -// stream section -type OfferedHashesMsg struct { - Stream Stream // name of Stream - From, To uint64 // peer and db-specific entry count - Hashes []byte // stream of hashes (128) - *HandoverProof // HandoverProof -} - -// String pretty prints OfferedHashesMsg -func (m OfferedHashesMsg) String() string { - return fmt.Sprintf("Stream '%v' [%v-%v] (%v)", m.Stream, m.From, m.To, len(m.Hashes)/HashSize) -} - -// handleOfferedHashesMsg protocol msg handler calls the incoming streamer interface -// Filter method -func (p *Peer) handleOfferedHashesMsg(ctx context.Context, req *OfferedHashesMsg) error { - metrics.GetOrRegisterCounter("peer.handleofferedhashes", nil).Inc(1) - - var sp opentracing.Span - ctx, sp = spancontext.StartSpan( - ctx, - "handle.offered.hashes") - defer sp.Finish() - - c, _, err := p.getOrSetClient(req.Stream, req.From, req.To) - if err != nil { - return err - } - - hashes := req.Hashes - lenHashes := len(hashes) - if lenHashes%HashSize != 0 { - return fmt.Errorf("error invalid hashes length (len: %v)", lenHashes) - } - - want, err := bv.New(lenHashes / HashSize) - if err != nil { - return fmt.Errorf("error initiaising bitvector of length %v: %v", lenHashes/HashSize, err) - } - - ctr := 0 - errC := make(chan error) - ctx, cancel := context.WithTimeout(ctx, syncBatchTimeout) - - ctx = context.WithValue(ctx, "source", p.ID().String()) - for i := 0; i < lenHashes; i += HashSize { - hash := hashes[i : i+HashSize] - - if wait := c.NeedData(ctx, hash); wait != nil { - ctr++ - want.Set(i/HashSize, true) - // create request and wait until the chunk data arrives and is stored - go func(w func(context.Context) error) { - select { - case errC <- w(ctx): - case <-ctx.Done(): - } - }(wait) - } - } - - go func() { - defer cancel() - for i := 0; i < ctr; i++ { - select { - case err := <-errC: - if err != nil { - log.Debug("client.handleOfferedHashesMsg() error waiting for chunk, dropping peer", "peer", p.ID(), "err", err) - p.Drop(err) - return - } - case <-ctx.Done(): - log.Debug("client.handleOfferedHashesMsg() context done", "ctx.Err()", ctx.Err()) - return - case <-c.quit: - log.Debug("client.handleOfferedHashesMsg() quit") - return - } - } - select { - case c.next <- c.batchDone(p, req, hashes): - case <-c.quit: - log.Debug("client.handleOfferedHashesMsg() quit") - case <-ctx.Done(): - log.Debug("client.handleOfferedHashesMsg() context done", "ctx.Err()", ctx.Err()) - } - }() - // only send wantedKeysMsg if all missing chunks of the previous batch arrived - // except - if c.stream.Live { - c.sessionAt = req.From - } - from, to := c.nextBatch(req.To + 1) - log.Trace("set next batch", "peer", p.ID(), "stream", req.Stream, "from", req.From, "to", req.To, "addr", p.streamer.addr) - if from == to { - return nil - } - - msg := &WantedHashesMsg{ - Stream: req.Stream, - Want: want.Bytes(), - From: from, - To: to, - } - go func() { - log.Trace("sending want batch", "peer", p.ID(), "stream", msg.Stream, "from", msg.From, "to", msg.To) - select { - case err := <-c.next: - if err != nil { - log.Warn("c.next error dropping peer", "err", err) - p.Drop(err) - return - } - case <-c.quit: - log.Debug("client.handleOfferedHashesMsg() quit") - return - case <-ctx.Done(): - log.Debug("client.handleOfferedHashesMsg() context done", "ctx.Err()", ctx.Err()) - return - } - log.Trace("sending want batch", "peer", p.ID(), "stream", msg.Stream, "from", msg.From, "to", msg.To) - err := p.SendPriority(ctx, msg, c.priority, "") - if err != nil { - log.Warn("SendPriority error", "err", err) - } - }() - return nil -} - -// WantedHashesMsg is the protocol msg data for signaling which hashes -// offered in OfferedHashesMsg downstream peer actually wants sent over -type WantedHashesMsg struct { - Stream Stream - Want []byte // bitvector indicating which keys of the batch needed - From, To uint64 // next interval offset - empty if not to be continued -} - -// String pretty prints WantedHashesMsg -func (m WantedHashesMsg) String() string { - return fmt.Sprintf("Stream '%v', Want: %x, Next: [%v-%v]", m.Stream, m.Want, m.From, m.To) -} - -// handleWantedHashesMsg protocol msg handler -// * sends the next batch of unsynced keys -// * sends the actual data chunks as per WantedHashesMsg -func (p *Peer) handleWantedHashesMsg(ctx context.Context, req *WantedHashesMsg) error { - metrics.GetOrRegisterCounter("peer.handlewantedhashesmsg", nil).Inc(1) - - log.Trace("received wanted batch", "peer", p.ID(), "stream", req.Stream, "from", req.From, "to", req.To) - s, err := p.getServer(req.Stream) - if err != nil { - return err - } - hashes := s.currentBatch - // launch in go routine since GetBatch blocks until new hashes arrive - go func() { - if err := p.SendOfferedHashes(s, req.From, req.To); err != nil { - log.Warn("SendOfferedHashes error", "peer", p.ID().TerminalString(), "err", err) - } - }() - // go p.SendOfferedHashes(s, req.From, req.To) - l := len(hashes) / HashSize - - log.Trace("wanted batch length", "peer", p.ID(), "stream", req.Stream, "from", req.From, "to", req.To, "lenhashes", len(hashes), "l", l) - want, err := bv.NewFromBytes(req.Want, l) - if err != nil { - return fmt.Errorf("error initiaising bitvector of length %v: %v", l, err) - } - for i := 0; i < l; i++ { - if want.Get(i) { - metrics.GetOrRegisterCounter("peer.handlewantedhashesmsg.actualget", nil).Inc(1) - - hash := hashes[i*HashSize : (i+1)*HashSize] - data, err := s.GetData(ctx, hash) - if err != nil { - return fmt.Errorf("handleWantedHashesMsg get data %x: %v", hash, err) - } - chunk := storage.NewChunk(hash, data) - syncing := true - if err := p.Deliver(ctx, chunk, s.priority, syncing); err != nil { - return err - } - } - } - return nil -} - -// Handover represents a statement that the upstream peer hands over the stream section -type Handover struct { - Stream Stream // name of stream - Start, End uint64 // index of hashes - Root []byte // Root hash for indexed segment inclusion proofs -} - -// HandoverProof represents a signed statement that the upstream peer handed over the stream section -type HandoverProof struct { - Sig []byte // Sign(Hash(Serialisation(Handover))) - *Handover -} - -// Takeover represents a statement that downstream peer took over (stored all data) -// handed over -type Takeover Handover - -// TakeoverProof represents a signed statement that the downstream peer took over -// the stream section -type TakeoverProof struct { - Sig []byte // Sign(Hash(Serialisation(Takeover))) - *Takeover -} - -// TakeoverProofMsg is the protocol msg sent by downstream peer -type TakeoverProofMsg TakeoverProof - -// String pretty prints TakeoverProofMsg -func (m TakeoverProofMsg) String() string { - return fmt.Sprintf("Stream: '%v' [%v-%v], Root: %x, Sig: %x", m.Stream, m.Start, m.End, m.Root, m.Sig) -} - -func (p *Peer) handleTakeoverProofMsg(ctx context.Context, req *TakeoverProofMsg) error { - _, err := p.getServer(req.Stream) - // store the strongest takeoverproof for the stream in streamer - return err -} diff --git a/swarm/network/stream/norace_test.go b/swarm/network/stream/norace_test.go deleted file mode 100644 index b324f6939c5f..000000000000 --- a/swarm/network/stream/norace_test.go +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build !race - -package stream - -// Provide a flag to reduce the scope of tests when running them -// with race detector. Some of the tests are doing a lot of allocations -// on the heap, and race detector uses much more memory to track them. -const raceTest = false diff --git a/swarm/network/stream/peer.go b/swarm/network/stream/peer.go deleted file mode 100644 index 23cd1afe4464..000000000000 --- a/swarm/network/stream/peer.go +++ /dev/null @@ -1,430 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package stream - -import ( - "context" - "errors" - "fmt" - "sync" - "time" - - "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/p2p/protocols" - "github.com/ubiq/go-ubiq/swarm/log" - pq "github.com/ubiq/go-ubiq/swarm/network/priorityqueue" - "github.com/ubiq/go-ubiq/swarm/network/stream/intervals" - "github.com/ubiq/go-ubiq/swarm/spancontext" - "github.com/ubiq/go-ubiq/swarm/state" - "github.com/ubiq/go-ubiq/swarm/storage" - opentracing "github.com/opentracing/opentracing-go" -) - -type notFoundError struct { - t string - s Stream -} - -func newNotFoundError(t string, s Stream) *notFoundError { - return ¬FoundError{t: t, s: s} -} - -func (e *notFoundError) Error() string { - return fmt.Sprintf("%s not found for stream %q", e.t, e.s) -} - -// ErrMaxPeerServers will be returned if peer server limit is reached. -// It will be sent in the SubscribeErrorMsg. -var ErrMaxPeerServers = errors.New("max peer servers") - -// Peer is the Peer extension for the streaming protocol -type Peer struct { - *protocols.Peer - streamer *Registry - pq *pq.PriorityQueue - serverMu sync.RWMutex - clientMu sync.RWMutex // protects both clients and clientParams - servers map[Stream]*server - clients map[Stream]*client - // clientParams map keeps required client arguments - // that are set on Registry.Subscribe and used - // on creating a new client in offered hashes handler. - clientParams map[Stream]*clientParams - quit chan struct{} - spans sync.Map -} - -type WrappedPriorityMsg struct { - Context context.Context - Msg interface{} -} - -// NewPeer is the constructor for Peer -func NewPeer(peer *protocols.Peer, streamer *Registry) *Peer { - p := &Peer{ - Peer: peer, - pq: pq.New(int(PriorityQueue), PriorityQueueCap), - streamer: streamer, - servers: make(map[Stream]*server), - clients: make(map[Stream]*client), - clientParams: make(map[Stream]*clientParams), - quit: make(chan struct{}), - spans: sync.Map{}, - } - ctx, cancel := context.WithCancel(context.Background()) - go p.pq.Run(ctx, func(i interface{}) { - wmsg := i.(WrappedPriorityMsg) - defer p.spans.Delete(wmsg.Context) - sp, ok := p.spans.Load(wmsg.Context) - if ok { - defer sp.(opentracing.Span).Finish() - } - err := p.Send(wmsg.Context, wmsg.Msg) - if err != nil { - log.Error("Message send error, dropping peer", "peer", p.ID(), "err", err) - p.Drop(err) - } - }) - - // basic monitoring for pq contention - go func(pq *pq.PriorityQueue) { - ticker := time.NewTicker(5 * time.Second) - defer ticker.Stop() - for { - select { - case <-ticker.C: - var len_maxi int - var cap_maxi int - for k := range pq.Queues { - if len_maxi < len(pq.Queues[k]) { - len_maxi = len(pq.Queues[k]) - } - - if cap_maxi < cap(pq.Queues[k]) { - cap_maxi = cap(pq.Queues[k]) - } - } - - metrics.GetOrRegisterGauge(fmt.Sprintf("pq_len_%s", p.ID().TerminalString()), nil).Update(int64(len_maxi)) - metrics.GetOrRegisterGauge(fmt.Sprintf("pq_cap_%s", p.ID().TerminalString()), nil).Update(int64(cap_maxi)) - case <-p.quit: - return - } - } - }(p.pq) - - go func() { - <-p.quit - cancel() - }() - return p -} - -// Deliver sends a storeRequestMsg protocol message to the peer -// Depending on the `syncing` parameter we send different message types -func (p *Peer) Deliver(ctx context.Context, chunk storage.Chunk, priority uint8, syncing bool) error { - var msg interface{} - - spanName := "send.chunk.delivery" - - //we send different types of messages if delivery is for syncing or retrievals, - //even if handling and content of the message are the same, - //because swap accounting decides which messages need accounting based on the message type - if syncing { - msg = &ChunkDeliveryMsgSyncing{ - Addr: chunk.Address(), - SData: chunk.Data(), - } - spanName += ".syncing" - } else { - msg = &ChunkDeliveryMsgRetrieval{ - Addr: chunk.Address(), - SData: chunk.Data(), - } - spanName += ".retrieval" - } - - return p.SendPriority(ctx, msg, priority, spanName) -} - -// SendPriority sends message to the peer using the outgoing priority queue -func (p *Peer) SendPriority(ctx context.Context, msg interface{}, priority uint8, traceId string) error { - defer metrics.GetOrRegisterResettingTimer(fmt.Sprintf("peer.sendpriority_t.%d", priority), nil).UpdateSince(time.Now()) - metrics.GetOrRegisterCounter(fmt.Sprintf("peer.sendpriority.%d", priority), nil).Inc(1) - if traceId != "" { - var sp opentracing.Span - ctx, sp = spancontext.StartSpan( - ctx, - traceId, - ) - p.spans.Store(ctx, sp) - } - wmsg := WrappedPriorityMsg{ - Context: ctx, - Msg: msg, - } - err := p.pq.Push(wmsg, int(priority)) - if err == pq.ErrContention { - log.Warn("dropping peer on priority queue contention", "peer", p.ID()) - p.Drop(err) - } - return err -} - -// SendOfferedHashes sends OfferedHashesMsg protocol msg -func (p *Peer) SendOfferedHashes(s *server, f, t uint64) error { - var sp opentracing.Span - ctx, sp := spancontext.StartSpan( - context.TODO(), - "send.offered.hashes") - defer sp.Finish() - - hashes, from, to, proof, err := s.setNextBatch(f, t) - if err != nil { - return err - } - // true only when quitting - if len(hashes) == 0 { - return nil - } - if proof == nil { - proof = &HandoverProof{ - Handover: &Handover{}, - } - } - s.currentBatch = hashes - msg := &OfferedHashesMsg{ - HandoverProof: proof, - Hashes: hashes, - From: from, - To: to, - Stream: s.stream, - } - log.Trace("Swarm syncer offer batch", "peer", p.ID(), "stream", s.stream, "len", len(hashes), "from", from, "to", to) - return p.SendPriority(ctx, msg, s.priority, "send.offered.hashes") -} - -func (p *Peer) getServer(s Stream) (*server, error) { - p.serverMu.RLock() - defer p.serverMu.RUnlock() - - server := p.servers[s] - if server == nil { - return nil, newNotFoundError("server", s) - } - return server, nil -} - -func (p *Peer) setServer(s Stream, o Server, priority uint8) (*server, error) { - p.serverMu.Lock() - defer p.serverMu.Unlock() - - if p.servers[s] != nil { - return nil, fmt.Errorf("server %s already registered", s) - } - - if p.streamer.maxPeerServers > 0 && len(p.servers) >= p.streamer.maxPeerServers { - return nil, ErrMaxPeerServers - } - - sessionIndex, err := o.SessionIndex() - if err != nil { - return nil, err - } - os := &server{ - Server: o, - stream: s, - priority: priority, - sessionIndex: sessionIndex, - } - p.servers[s] = os - return os, nil -} - -func (p *Peer) removeServer(s Stream) error { - p.serverMu.Lock() - defer p.serverMu.Unlock() - - server, ok := p.servers[s] - if !ok { - return newNotFoundError("server", s) - } - server.Close() - delete(p.servers, s) - return nil -} - -func (p *Peer) getClient(ctx context.Context, s Stream) (c *client, err error) { - var params *clientParams - func() { - p.clientMu.RLock() - defer p.clientMu.RUnlock() - - c = p.clients[s] - if c != nil { - return - } - params = p.clientParams[s] - }() - if c != nil { - return c, nil - } - - if params != nil { - //debug.PrintStack() - if err := params.waitClient(ctx); err != nil { - return nil, err - } - } - - p.clientMu.RLock() - defer p.clientMu.RUnlock() - - c = p.clients[s] - if c != nil { - return c, nil - } - return nil, newNotFoundError("client", s) -} - -func (p *Peer) getOrSetClient(s Stream, from, to uint64) (c *client, created bool, err error) { - p.clientMu.Lock() - defer p.clientMu.Unlock() - - c = p.clients[s] - if c != nil { - return c, false, nil - } - - f, err := p.streamer.GetClientFunc(s.Name) - if err != nil { - return nil, false, err - } - - is, err := f(p, s.Key, s.Live) - if err != nil { - return nil, false, err - } - - cp, err := p.getClientParams(s) - if err != nil { - return nil, false, err - } - defer func() { - if err == nil { - if err := p.removeClientParams(s); err != nil { - log.Error("stream set client: remove client params", "stream", s, "peer", p, "err", err) - } - } - }() - - intervalsKey := peerStreamIntervalsKey(p, s) - if s.Live { - // try to find previous history and live intervals and merge live into history - historyKey := peerStreamIntervalsKey(p, NewStream(s.Name, s.Key, false)) - historyIntervals := &intervals.Intervals{} - err := p.streamer.intervalsStore.Get(historyKey, historyIntervals) - switch err { - case nil: - liveIntervals := &intervals.Intervals{} - err := p.streamer.intervalsStore.Get(intervalsKey, liveIntervals) - switch err { - case nil: - historyIntervals.Merge(liveIntervals) - if err := p.streamer.intervalsStore.Put(historyKey, historyIntervals); err != nil { - log.Error("stream set client: put history intervals", "stream", s, "peer", p, "err", err) - } - case state.ErrNotFound: - default: - log.Error("stream set client: get live intervals", "stream", s, "peer", p, "err", err) - } - case state.ErrNotFound: - default: - log.Error("stream set client: get history intervals", "stream", s, "peer", p, "err", err) - } - } - - if err := p.streamer.intervalsStore.Put(intervalsKey, intervals.NewIntervals(from)); err != nil { - return nil, false, err - } - - next := make(chan error, 1) - c = &client{ - Client: is, - stream: s, - priority: cp.priority, - to: cp.to, - next: next, - quit: make(chan struct{}), - intervalsStore: p.streamer.intervalsStore, - intervalsKey: intervalsKey, - } - p.clients[s] = c - cp.clientCreated() // unblock all possible getClient calls that are waiting - next <- nil // this is to allow wantedKeysMsg before first batch arrives - return c, true, nil -} - -func (p *Peer) removeClient(s Stream) error { - p.clientMu.Lock() - defer p.clientMu.Unlock() - - client, ok := p.clients[s] - if !ok { - return newNotFoundError("client", s) - } - client.close() - delete(p.clients, s) - return nil -} - -func (p *Peer) setClientParams(s Stream, params *clientParams) error { - p.clientMu.Lock() - defer p.clientMu.Unlock() - - if p.clients[s] != nil { - return fmt.Errorf("client %s already exists", s) - } - if p.clientParams[s] != nil { - return fmt.Errorf("client params %s already set", s) - } - p.clientParams[s] = params - return nil -} - -func (p *Peer) getClientParams(s Stream) (*clientParams, error) { - params := p.clientParams[s] - if params == nil { - return nil, fmt.Errorf("client params '%v' not provided to peer %v", s, p.ID()) - } - return params, nil -} - -func (p *Peer) removeClientParams(s Stream) error { - _, ok := p.clientParams[s] - if !ok { - return newNotFoundError("client params", s) - } - delete(p.clientParams, s) - return nil -} - -func (p *Peer) close() { - for _, s := range p.servers { - s.Close() - } -} diff --git a/swarm/network/stream/race_test.go b/swarm/network/stream/race_test.go deleted file mode 100644 index 8aed3542bf81..000000000000 --- a/swarm/network/stream/race_test.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build race - -package stream - -// Reduce the scope of some tests when running with race detector, -// as it raises the memory consumption significantly. -const raceTest = true diff --git a/swarm/network/stream/snapshot_retrieval_test.go b/swarm/network/stream/snapshot_retrieval_test.go deleted file mode 100644 index c869958cfabd..000000000000 --- a/swarm/network/stream/snapshot_retrieval_test.go +++ /dev/null @@ -1,311 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . -package stream - -import ( - "context" - "fmt" - "sync" - "testing" - "time" - - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/network/simulation" - "github.com/ubiq/go-ubiq/swarm/state" - "github.com/ubiq/go-ubiq/swarm/storage" -) - -//constants for random file generation -const ( - minFileSize = 2 - maxFileSize = 40 -) - -//This test is a retrieval test for nodes. -//A configurable number of nodes can be -//provided to the test. -//Files are uploaded to nodes, other nodes try to retrieve the file -//Number of nodes can be provided via commandline too. -func TestFileRetrieval(t *testing.T) { - if *nodes != 0 { - err := runFileRetrievalTest(*nodes) - if err != nil { - t.Fatal(err) - } - } else { - nodeCnt := []int{16} - //if the `longrunning` flag has been provided - //run more test combinations - if *longrunning { - nodeCnt = append(nodeCnt, 32, 64, 128) - } - for _, n := range nodeCnt { - err := runFileRetrievalTest(n) - if err != nil { - t.Fatal(err) - } - } - } -} - -//This test is a retrieval test for nodes. -//One node is randomly selected to be the pivot node. -//A configurable number of chunks and nodes can be -//provided to the test, the number of chunks is uploaded -//to the pivot node and other nodes try to retrieve the chunk(s). -//Number of chunks and nodes can be provided via commandline too. -func TestRetrieval(t *testing.T) { - //if nodes/chunks have been provided via commandline, - //run the tests with these values - if *nodes != 0 && *chunks != 0 { - err := runRetrievalTest(t, *chunks, *nodes) - if err != nil { - t.Fatal(err) - } - } else { - var nodeCnt []int - var chnkCnt []int - //if the `longrunning` flag has been provided - //run more test combinations - if *longrunning { - nodeCnt = []int{16, 32, 128} - chnkCnt = []int{4, 32, 256} - } else { - //default test - nodeCnt = []int{16} - chnkCnt = []int{32} - } - for _, n := range nodeCnt { - for _, c := range chnkCnt { - t.Run(fmt.Sprintf("TestRetrieval_%d_%d", n, c), func(t *testing.T) { - err := runRetrievalTest(t, c, n) - if err != nil { - t.Fatal(err) - } - }) - } - } - } -} - -var retrievalSimServiceMap = map[string]simulation.ServiceFunc{ - "streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) { - addr, netStore, delivery, clean, err := newNetStoreAndDelivery(ctx, bucket) - if err != nil { - return nil, nil, err - } - - r := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), &RegistryOptions{ - Retrieval: RetrievalEnabled, - Syncing: SyncingAutoSubscribe, - SyncUpdateDelay: 3 * time.Second, - }, nil) - - cleanup = func() { - r.Close() - clean() - } - - return r, cleanup, nil - }, -} - -/* -The test loads a snapshot file to construct the swarm network, -assuming that the snapshot file identifies a healthy -kademlia network. Nevertheless a health check runs in the -simulation's `action` function. - -The snapshot should have 'streamer' in its service list. -*/ -func runFileRetrievalTest(nodeCount int) error { - sim := simulation.New(retrievalSimServiceMap) - defer sim.Close() - - log.Info("Initializing test config") - - conf := &synctestConfig{} - //map of discover ID to indexes of chunks expected at that ID - conf.idToChunksMap = make(map[enode.ID][]int) - //map of overlay address to discover ID - conf.addrToIDMap = make(map[string]enode.ID) - //array where the generated chunk hashes will be stored - conf.hashes = make([]storage.Address, 0) - - err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount)) - if err != nil { - return err - } - - ctx, cancelSimRun := context.WithTimeout(context.Background(), 3*time.Minute) - defer cancelSimRun() - - result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error { - nodeIDs := sim.UpNodeIDs() - for _, n := range nodeIDs { - //get the kademlia overlay address from this ID - a := n.Bytes() - //append it to the array of all overlay addresses - conf.addrs = append(conf.addrs, a) - //the proximity calculation is on overlay addr, - //the p2p/simulations check func triggers on enode.ID, - //so we need to know which overlay addr maps to which nodeID - conf.addrToIDMap[string(a)] = n - } - - //an array for the random files - var randomFiles []string - //channel to signal when the upload has finished - //uploadFinished := make(chan struct{}) - //channel to trigger new node checks - - conf.hashes, randomFiles, err = uploadFilesToNodes(sim) - if err != nil { - return err - } - if _, err := sim.WaitTillHealthy(ctx); err != nil { - return err - } - - // File retrieval check is repeated until all uploaded files are retrieved from all nodes - // or until the timeout is reached. - REPEAT: - for { - for _, id := range nodeIDs { - //for each expected file, check if it is in the local store - item, ok := sim.NodeItem(id, bucketKeyFileStore) - if !ok { - return fmt.Errorf("No filestore") - } - fileStore := item.(*storage.FileStore) - //check all chunks - for i, hash := range conf.hashes { - reader, _ := fileStore.Retrieve(context.TODO(), hash) - //check that we can read the file size and that it corresponds to the generated file size - if s, err := reader.Size(ctx, nil); err != nil || s != int64(len(randomFiles[i])) { - log.Debug("Retrieve error", "err", err, "hash", hash, "nodeId", id) - time.Sleep(500 * time.Millisecond) - continue REPEAT - } - log.Debug(fmt.Sprintf("File with root hash %x successfully retrieved", hash)) - } - } - return nil - } - }) - - if result.Error != nil { - return result.Error - } - - return nil -} - -/* -The test generates the given number of chunks. - -The test loads a snapshot file to construct the swarm network, -assuming that the snapshot file identifies a healthy -kademlia network. Nevertheless a health check runs in the -simulation's `action` function. - -The snapshot should have 'streamer' in its service list. -*/ -func runRetrievalTest(t *testing.T, chunkCount int, nodeCount int) error { - t.Helper() - sim := simulation.New(retrievalSimServiceMap) - defer sim.Close() - - conf := &synctestConfig{} - //map of discover ID to indexes of chunks expected at that ID - conf.idToChunksMap = make(map[enode.ID][]int) - //map of overlay address to discover ID - conf.addrToIDMap = make(map[string]enode.ID) - //array where the generated chunk hashes will be stored - conf.hashes = make([]storage.Address, 0) - - err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount)) - if err != nil { - return err - } - - ctx := context.Background() - result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error { - nodeIDs := sim.UpNodeIDs() - for _, n := range nodeIDs { - //get the kademlia overlay address from this ID - a := n.Bytes() - //append it to the array of all overlay addresses - conf.addrs = append(conf.addrs, a) - //the proximity calculation is on overlay addr, - //the p2p/simulations check func triggers on enode.ID, - //so we need to know which overlay addr maps to which nodeID - conf.addrToIDMap[string(a)] = n - } - - //this is the node selected for upload - node := sim.Net.GetRandomUpNode() - item, ok := sim.NodeItem(node.ID(), bucketKeyStore) - if !ok { - return fmt.Errorf("No localstore") - } - lstore := item.(*storage.LocalStore) - conf.hashes, err = uploadFileToSingleNodeStore(node.ID(), chunkCount, lstore) - if err != nil { - return err - } - if _, err := sim.WaitTillHealthy(ctx); err != nil { - return err - } - - // File retrieval check is repeated until all uploaded files are retrieved from all nodes - // or until the timeout is reached. - REPEAT: - for { - for _, id := range nodeIDs { - //for each expected chunk, check if it is in the local store - //check on the node's FileStore (netstore) - item, ok := sim.NodeItem(id, bucketKeyFileStore) - if !ok { - return fmt.Errorf("No filestore") - } - fileStore := item.(*storage.FileStore) - //check all chunks - for _, hash := range conf.hashes { - reader, _ := fileStore.Retrieve(context.TODO(), hash) - //check that we can read the chunk size and that it corresponds to the generated chunk size - if s, err := reader.Size(ctx, nil); err != nil || s != int64(chunkSize) { - log.Debug("Retrieve error", "err", err, "hash", hash, "nodeId", id, "size", s) - time.Sleep(500 * time.Millisecond) - continue REPEAT - } - log.Debug(fmt.Sprintf("Chunk with root hash %x successfully retrieved", hash)) - } - } - // all nodes and files found, exit loop and return without error - return nil - } - }) - - if result.Error != nil { - return result.Error - } - - return nil -} diff --git a/swarm/network/stream/snapshot_sync_test.go b/swarm/network/stream/snapshot_sync_test.go deleted file mode 100644 index a0cd9073972c..000000000000 --- a/swarm/network/stream/snapshot_sync_test.go +++ /dev/null @@ -1,344 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . -package stream - -import ( - "context" - "errors" - "fmt" - "io/ioutil" - "os" - "runtime" - "sync" - "testing" - "time" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/simulations" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" - "github.com/ubiq/go-ubiq/swarm/network" - "github.com/ubiq/go-ubiq/swarm/network/simulation" - "github.com/ubiq/go-ubiq/swarm/pot" - "github.com/ubiq/go-ubiq/swarm/state" - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/ubiq/go-ubiq/swarm/storage/mock" - mockmem "github.com/ubiq/go-ubiq/swarm/storage/mock/mem" - "github.com/ubiq/go-ubiq/swarm/testutil" -) - -const MaxTimeout = 600 - -type synctestConfig struct { - addrs [][]byte - hashes []storage.Address - idToChunksMap map[enode.ID][]int - //chunksToNodesMap map[string][]int - addrToIDMap map[string]enode.ID -} - -const ( - // EventTypeNode is the type of event emitted when a node is either - // created, started or stopped - EventTypeChunkCreated simulations.EventType = "chunkCreated" - EventTypeChunkOffered simulations.EventType = "chunkOffered" - EventTypeChunkWanted simulations.EventType = "chunkWanted" - EventTypeChunkDelivered simulations.EventType = "chunkDelivered" - EventTypeChunkArrived simulations.EventType = "chunkArrived" - EventTypeSimTerminated simulations.EventType = "simTerminated" -) - -// Tests in this file should not request chunks from peers. -// This function will panic indicating that there is a problem if request has been made. -func dummyRequestFromPeers(_ context.Context, req *network.Request) (*enode.ID, chan struct{}, error) { - panic(fmt.Sprintf("unexpected request: address %s, source %s", req.Addr.String(), req.Source.String())) -} - -//This test is a syncing test for nodes. -//One node is randomly selected to be the pivot node. -//A configurable number of chunks and nodes can be -//provided to the test, the number of chunks is uploaded -//to the pivot node, and we check that nodes get the chunks -//they are expected to store based on the syncing protocol. -//Number of chunks and nodes can be provided via commandline too. -func TestSyncingViaGlobalSync(t *testing.T) { - if runtime.GOOS == "darwin" && os.Getenv("TRAVIS") == "true" { - t.Skip("Flaky on mac on travis") - } - //if nodes/chunks have been provided via commandline, - //run the tests with these values - if *nodes != 0 && *chunks != 0 { - log.Info(fmt.Sprintf("Running test with %d chunks and %d nodes...", *chunks, *nodes)) - testSyncingViaGlobalSync(t, *chunks, *nodes) - } else { - var nodeCnt []int - var chnkCnt []int - //if the `longrunning` flag has been provided - //run more test combinations - if *longrunning { - chnkCnt = []int{1, 8, 32, 256, 1024} - nodeCnt = []int{16, 32, 64, 128, 256} - } else if raceTest { - // TestSyncingViaGlobalSync allocates a lot of memory - // with race detector. By reducing the number of chunks - // and nodes, memory consumption is lower and data races - // are still checked, while correctness of syncing is - // tested with more chunks and nodes in regular (!race) - // tests. - chnkCnt = []int{4} - nodeCnt = []int{16} - } else { - //default test - chnkCnt = []int{4, 32} - nodeCnt = []int{32, 16} - } - for _, chnk := range chnkCnt { - for _, n := range nodeCnt { - log.Info(fmt.Sprintf("Long running test with %d chunks and %d nodes...", chnk, n)) - testSyncingViaGlobalSync(t, chnk, n) - } - } - } -} - -var simServiceMap = map[string]simulation.ServiceFunc{ - "streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) { - addr, netStore, delivery, clean, err := newNetStoreAndDeliveryWithRequestFunc(ctx, bucket, dummyRequestFromPeers) - if err != nil { - return nil, nil, err - } - - var dir string - var store *state.DBStore - if raceTest { - // Use on-disk DBStore to reduce memory consumption in race tests. - dir, err = ioutil.TempDir("", "swarm-stream-") - if err != nil { - return nil, nil, err - } - store, err = state.NewDBStore(dir) - if err != nil { - return nil, nil, err - } - } else { - store = state.NewInmemoryStore() - } - - r := NewRegistry(addr.ID(), delivery, netStore, store, &RegistryOptions{ - Retrieval: RetrievalDisabled, - Syncing: SyncingAutoSubscribe, - SyncUpdateDelay: 3 * time.Second, - }, nil) - - bucket.Store(bucketKeyRegistry, r) - - cleanup = func() { - r.Close() - clean() - } - - return r, cleanup, nil - }, -} - -func testSyncingViaGlobalSync(t *testing.T, chunkCount int, nodeCount int) { - sim := simulation.New(simServiceMap) - defer sim.Close() - - log.Info("Initializing test config") - - conf := &synctestConfig{} - //map of discover ID to indexes of chunks expected at that ID - conf.idToChunksMap = make(map[enode.ID][]int) - //map of overlay address to discover ID - conf.addrToIDMap = make(map[string]enode.ID) - //array where the generated chunk hashes will be stored - conf.hashes = make([]storage.Address, 0) - - err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount)) - if err != nil { - t.Fatal(err) - } - - ctx, cancelSimRun := context.WithTimeout(context.Background(), 2*time.Minute) - defer cancelSimRun() - - if _, err := sim.WaitTillHealthy(ctx); err != nil { - t.Fatal(err) - } - - result := runSim(conf, ctx, sim, chunkCount) - - if result.Error != nil { - t.Fatal(result.Error) - } - log.Info("Simulation ended") -} - -func runSim(conf *synctestConfig, ctx context.Context, sim *simulation.Simulation, chunkCount int) simulation.Result { - - return sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) (err error) { - disconnected := watchDisconnections(ctx, sim) - defer func() { - if err != nil && disconnected.bool() { - err = errors.New("disconnect events received") - } - }() - - nodeIDs := sim.UpNodeIDs() - for _, n := range nodeIDs { - //get the kademlia overlay address from this ID - a := n.Bytes() - //append it to the array of all overlay addresses - conf.addrs = append(conf.addrs, a) - //the proximity calculation is on overlay addr, - //the p2p/simulations check func triggers on enode.ID, - //so we need to know which overlay addr maps to which nodeID - conf.addrToIDMap[string(a)] = n - } - - //get the node at that index - //this is the node selected for upload - node := sim.Net.GetRandomUpNode() - item, ok := sim.NodeItem(node.ID(), bucketKeyStore) - if !ok { - return fmt.Errorf("No localstore") - } - lstore := item.(*storage.LocalStore) - hashes, err := uploadFileToSingleNodeStore(node.ID(), chunkCount, lstore) - if err != nil { - return err - } - for _, h := range hashes { - evt := &simulations.Event{ - Type: EventTypeChunkCreated, - Node: sim.Net.GetNode(node.ID()), - Data: h.String(), - } - sim.Net.Events().Send(evt) - } - conf.hashes = append(conf.hashes, hashes...) - mapKeysToNodes(conf) - - // File retrieval check is repeated until all uploaded files are retrieved from all nodes - // or until the timeout is reached. - var globalStore mock.GlobalStorer - if *useMockStore { - globalStore = mockmem.NewGlobalStore() - } - REPEAT: - for { - for _, id := range nodeIDs { - //for each expected chunk, check if it is in the local store - localChunks := conf.idToChunksMap[id] - for _, ch := range localChunks { - //get the real chunk by the index in the index array - chunk := conf.hashes[ch] - log.Trace(fmt.Sprintf("node has chunk: %s:", chunk)) - //check if the expected chunk is indeed in the localstore - var err error - if *useMockStore { - //use the globalStore if the mockStore should be used; in that case, - //the complete localStore stack is bypassed for getting the chunk - _, err = globalStore.Get(common.BytesToAddress(id.Bytes()), chunk) - } else { - //use the actual localstore - item, ok := sim.NodeItem(id, bucketKeyStore) - if !ok { - return fmt.Errorf("Error accessing localstore") - } - lstore := item.(*storage.LocalStore) - _, err = lstore.Get(ctx, chunk) - } - if err != nil { - log.Debug(fmt.Sprintf("Chunk %s NOT found for id %s", chunk, id)) - // Do not get crazy with logging the warn message - time.Sleep(500 * time.Millisecond) - continue REPEAT - } - evt := &simulations.Event{ - Type: EventTypeChunkArrived, - Node: sim.Net.GetNode(id), - Data: chunk.String(), - } - sim.Net.Events().Send(evt) - log.Debug(fmt.Sprintf("Chunk %s IS FOUND for id %s", chunk, id)) - } - } - return nil - } - }) -} - -//map chunk keys to addresses which are responsible -func mapKeysToNodes(conf *synctestConfig) { - nodemap := make(map[string][]int) - //build a pot for chunk hashes - np := pot.NewPot(nil, 0) - indexmap := make(map[string]int) - for i, a := range conf.addrs { - indexmap[string(a)] = i - np, _, _ = pot.Add(np, a, pof) - } - - ppmap := network.NewPeerPotMap(network.NewKadParams().NeighbourhoodSize, conf.addrs) - - //for each address, run EachNeighbour on the chunk hashes pot to identify closest nodes - log.Trace(fmt.Sprintf("Generated hash chunk(s): %v", conf.hashes)) - for i := 0; i < len(conf.hashes); i++ { - var a []byte - np.EachNeighbour([]byte(conf.hashes[i]), pof, func(val pot.Val, po int) bool { - // take the first address - a = val.([]byte) - return false - }) - - nns := ppmap[common.Bytes2Hex(a)].NNSet - nns = append(nns, a) - - for _, p := range nns { - nodemap[string(p)] = append(nodemap[string(p)], i) - } - } - for addr, chunks := range nodemap { - //this selects which chunks are expected to be found with the given node - conf.idToChunksMap[conf.addrToIDMap[addr]] = chunks - } - log.Debug(fmt.Sprintf("Map of expected chunks by ID: %v", conf.idToChunksMap)) -} - -//upload a file(chunks) to a single local node store -func uploadFileToSingleNodeStore(id enode.ID, chunkCount int, lstore *storage.LocalStore) ([]storage.Address, error) { - log.Debug(fmt.Sprintf("Uploading to node id: %s", id)) - fileStore := storage.NewFileStore(lstore, storage.NewFileStoreParams()) - size := chunkSize - var rootAddrs []storage.Address - for i := 0; i < chunkCount; i++ { - rk, wait, err := fileStore.Store(context.TODO(), testutil.RandomReader(i, size), int64(size), false) - if err != nil { - return nil, err - } - err = wait(context.TODO()) - if err != nil { - return nil, err - } - rootAddrs = append(rootAddrs, (rk)) - } - - return rootAddrs, nil -} diff --git a/swarm/network/stream/stream.go b/swarm/network/stream/stream.go deleted file mode 100644 index 3cd9ce4456fb..000000000000 --- a/swarm/network/stream/stream.go +++ /dev/null @@ -1,967 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package stream - -import ( - "context" - "errors" - "fmt" - "math" - "reflect" - "sync" - "time" - - "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/protocols" - "github.com/ubiq/go-ubiq/rpc" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/network" - "github.com/ubiq/go-ubiq/swarm/network/stream/intervals" - "github.com/ubiq/go-ubiq/swarm/state" - "github.com/ubiq/go-ubiq/swarm/storage" -) - -const ( - Low uint8 = iota - Mid - High - Top - PriorityQueue = 4 // number of priority queues - Low, Mid, High, Top - PriorityQueueCap = 4096 // queue capacity - HashSize = 32 -) - -// Enumerate options for syncing and retrieval -type SyncingOption int -type RetrievalOption int - -// Syncing options -const ( - // Syncing disabled - SyncingDisabled SyncingOption = iota - // Register the client and the server but not subscribe - SyncingRegisterOnly - // Both client and server funcs are registered, subscribe sent automatically - SyncingAutoSubscribe -) - -const ( - // Retrieval disabled. Used mostly for tests to isolate syncing features (i.e. syncing only) - RetrievalDisabled RetrievalOption = iota - // Only the client side of the retrieve request is registered. - // (light nodes do not serve retrieve requests) - // once the client is registered, subscription to retrieve request stream is always sent - RetrievalClientOnly - // Both client and server funcs are registered, subscribe sent automatically - RetrievalEnabled -) - -// subscriptionFunc is used to determine what to do in order to perform subscriptions -// usually we would start to really subscribe to nodes, but for tests other functionality may be needed -// (see TestRequestPeerSubscriptions in streamer_test.go) -var subscriptionFunc func(r *Registry, p *network.Peer, bin uint8, subs map[enode.ID]map[Stream]struct{}) bool = doRequestSubscription - -// Registry registry for outgoing and incoming streamer constructors -type Registry struct { - addr enode.ID - api *API - skipCheck bool - clientMu sync.RWMutex - serverMu sync.RWMutex - peersMu sync.RWMutex - serverFuncs map[string]func(*Peer, string, bool) (Server, error) - clientFuncs map[string]func(*Peer, string, bool) (Client, error) - peers map[enode.ID]*Peer - delivery *Delivery - intervalsStore state.Store - autoRetrieval bool // automatically subscribe to retrieve request stream - maxPeerServers int - spec *protocols.Spec //this protocol's spec - balance protocols.Balance //implements protocols.Balance, for accounting - prices protocols.Prices //implements protocols.Prices, provides prices to accounting -} - -// RegistryOptions holds optional values for NewRegistry constructor. -type RegistryOptions struct { - SkipCheck bool - Syncing SyncingOption // Defines syncing behavior - Retrieval RetrievalOption // Defines retrieval behavior - SyncUpdateDelay time.Duration - MaxPeerServers int // The limit of servers for each peer in registry -} - -// NewRegistry is Streamer constructor -func NewRegistry(localID enode.ID, delivery *Delivery, syncChunkStore storage.SyncChunkStore, intervalsStore state.Store, options *RegistryOptions, balance protocols.Balance) *Registry { - if options == nil { - options = &RegistryOptions{} - } - if options.SyncUpdateDelay <= 0 { - options.SyncUpdateDelay = 15 * time.Second - } - // check if retrieval has been disabled - retrieval := options.Retrieval != RetrievalDisabled - - streamer := &Registry{ - addr: localID, - skipCheck: options.SkipCheck, - serverFuncs: make(map[string]func(*Peer, string, bool) (Server, error)), - clientFuncs: make(map[string]func(*Peer, string, bool) (Client, error)), - peers: make(map[enode.ID]*Peer), - delivery: delivery, - intervalsStore: intervalsStore, - autoRetrieval: retrieval, - maxPeerServers: options.MaxPeerServers, - balance: balance, - } - - streamer.setupSpec() - - streamer.api = NewAPI(streamer) - delivery.getPeer = streamer.getPeer - - // if retrieval is enabled, register the server func, so that retrieve requests will be served (non-light nodes only) - if options.Retrieval == RetrievalEnabled { - streamer.RegisterServerFunc(swarmChunkServerStreamName, func(_ *Peer, _ string, live bool) (Server, error) { - if !live { - return nil, errors.New("only live retrieval requests supported") - } - return NewSwarmChunkServer(delivery.chunkStore), nil - }) - } - - // if retrieval is not disabled, register the client func (both light nodes and normal nodes can issue retrieve requests) - if options.Retrieval != RetrievalDisabled { - streamer.RegisterClientFunc(swarmChunkServerStreamName, func(p *Peer, t string, live bool) (Client, error) { - return NewSwarmSyncerClient(p, syncChunkStore, NewStream(swarmChunkServerStreamName, t, live)) - }) - } - - // If syncing is not disabled, the syncing functions are registered (both client and server) - if options.Syncing != SyncingDisabled { - RegisterSwarmSyncerServer(streamer, syncChunkStore) - RegisterSwarmSyncerClient(streamer, syncChunkStore) - } - - // if syncing is set to automatically subscribe to the syncing stream, start the subscription process - if options.Syncing == SyncingAutoSubscribe { - // latestIntC function ensures that - // - receiving from the in chan is not blocked by processing inside the for loop - // - the latest int value is delivered to the loop after the processing is done - // In context of NeighbourhoodDepthC: - // after the syncing is done updating inside the loop, we do not need to update on the intermediate - // depth changes, only to the latest one - latestIntC := func(in <-chan int) <-chan int { - out := make(chan int, 1) - - go func() { - defer close(out) - - for i := range in { - select { - case <-out: - default: - } - out <- i - } - }() - - return out - } - - go func() { - // wait for kademlia table to be healthy - time.Sleep(options.SyncUpdateDelay) - - kad := streamer.delivery.kad - depthC := latestIntC(kad.NeighbourhoodDepthC()) - addressBookSizeC := latestIntC(kad.AddrCountC()) - - // initial requests for syncing subscription to peers - streamer.updateSyncing() - - for depth := range depthC { - log.Debug("Kademlia neighbourhood depth change", "depth", depth) - - // Prevent too early sync subscriptions by waiting until there are no - // new peers connecting. Sync streams updating will be done after no - // peers are connected for at least SyncUpdateDelay period. - timer := time.NewTimer(options.SyncUpdateDelay) - // Hard limit to sync update delay, preventing long delays - // on a very dynamic network - maxTimer := time.NewTimer(3 * time.Minute) - loop: - for { - select { - case <-maxTimer.C: - // force syncing update when a hard timeout is reached - log.Trace("Sync subscriptions update on hard timeout") - // request for syncing subscription to new peers - streamer.updateSyncing() - break loop - case <-timer.C: - // start syncing as no new peers has been added to kademlia - // for some time - log.Trace("Sync subscriptions update") - // request for syncing subscription to new peers - streamer.updateSyncing() - break loop - case size := <-addressBookSizeC: - log.Trace("Kademlia address book size changed on depth change", "size", size) - // new peers has been added to kademlia, - // reset the timer to prevent early sync subscriptions - if !timer.Stop() { - <-timer.C - } - timer.Reset(options.SyncUpdateDelay) - } - } - timer.Stop() - maxTimer.Stop() - } - }() - } - - return streamer -} - -// This is an accounted protocol, therefore we need to provide a pricing Hook to the spec -// For simulations to be able to run multiple nodes and not override the hook's balance, -// we need to construct a spec instance per node instance -func (r *Registry) setupSpec() { - // first create the "bare" spec - r.createSpec() - // now create the pricing object - r.createPriceOracle() - // if balance is nil, this node has been started without swap support (swapEnabled flag is false) - if r.balance != nil && !reflect.ValueOf(r.balance).IsNil() { - // swap is enabled, so setup the hook - r.spec.Hook = protocols.NewAccounting(r.balance, r.prices) - } -} - -// RegisterClient registers an incoming streamer constructor -func (r *Registry) RegisterClientFunc(stream string, f func(*Peer, string, bool) (Client, error)) { - r.clientMu.Lock() - defer r.clientMu.Unlock() - - r.clientFuncs[stream] = f -} - -// RegisterServer registers an outgoing streamer constructor -func (r *Registry) RegisterServerFunc(stream string, f func(*Peer, string, bool) (Server, error)) { - r.serverMu.Lock() - defer r.serverMu.Unlock() - - r.serverFuncs[stream] = f -} - -// GetClient accessor for incoming streamer constructors -func (r *Registry) GetClientFunc(stream string) (func(*Peer, string, bool) (Client, error), error) { - r.clientMu.RLock() - defer r.clientMu.RUnlock() - - f := r.clientFuncs[stream] - if f == nil { - return nil, fmt.Errorf("stream %v not registered", stream) - } - return f, nil -} - -// GetServer accessor for incoming streamer constructors -func (r *Registry) GetServerFunc(stream string) (func(*Peer, string, bool) (Server, error), error) { - r.serverMu.RLock() - defer r.serverMu.RUnlock() - - f := r.serverFuncs[stream] - if f == nil { - return nil, fmt.Errorf("stream %v not registered", stream) - } - return f, nil -} - -func (r *Registry) RequestSubscription(peerId enode.ID, s Stream, h *Range, prio uint8) error { - // check if the stream is registered - if _, err := r.GetServerFunc(s.Name); err != nil { - return err - } - - peer := r.getPeer(peerId) - if peer == nil { - return fmt.Errorf("peer not found %v", peerId) - } - - if _, err := peer.getServer(s); err != nil { - if e, ok := err.(*notFoundError); ok && e.t == "server" { - // request subscription only if the server for this stream is not created - log.Debug("RequestSubscription ", "peer", peerId, "stream", s, "history", h) - return peer.Send(context.TODO(), &RequestSubscriptionMsg{ - Stream: s, - History: h, - Priority: prio, - }) - } - return err - } - log.Trace("RequestSubscription: already subscribed", "peer", peerId, "stream", s, "history", h) - return nil -} - -// Subscribe initiates the streamer -func (r *Registry) Subscribe(peerId enode.ID, s Stream, h *Range, priority uint8) error { - // check if the stream is registered - if _, err := r.GetClientFunc(s.Name); err != nil { - return err - } - - peer := r.getPeer(peerId) - if peer == nil { - return fmt.Errorf("peer not found %v", peerId) - } - - var to uint64 - if !s.Live && h != nil { - to = h.To - } - - err := peer.setClientParams(s, newClientParams(priority, to)) - if err != nil { - return err - } - if s.Live && h != nil { - if err := peer.setClientParams( - getHistoryStream(s), - newClientParams(getHistoryPriority(priority), h.To), - ); err != nil { - return err - } - } - - msg := &SubscribeMsg{ - Stream: s, - History: h, - Priority: priority, - } - log.Debug("Subscribe ", "peer", peerId, "stream", s, "history", h) - - return peer.SendPriority(context.TODO(), msg, priority, "") -} - -func (r *Registry) Unsubscribe(peerId enode.ID, s Stream) error { - peer := r.getPeer(peerId) - if peer == nil { - return fmt.Errorf("peer not found %v", peerId) - } - - msg := &UnsubscribeMsg{ - Stream: s, - } - log.Debug("Unsubscribe ", "peer", peerId, "stream", s) - - if err := peer.Send(context.TODO(), msg); err != nil { - return err - } - return peer.removeClient(s) -} - -// Quit sends the QuitMsg to the peer to remove the -// stream peer client and terminate the streaming. -func (r *Registry) Quit(peerId enode.ID, s Stream) error { - peer := r.getPeer(peerId) - if peer == nil { - log.Debug("stream quit: peer not found", "peer", peerId, "stream", s) - // if the peer is not found, abort the request - return nil - } - - msg := &QuitMsg{ - Stream: s, - } - log.Debug("Quit ", "peer", peerId, "stream", s) - - return peer.Send(context.TODO(), msg) -} - -func (r *Registry) Close() error { - return r.intervalsStore.Close() -} - -func (r *Registry) getPeer(peerId enode.ID) *Peer { - r.peersMu.RLock() - defer r.peersMu.RUnlock() - - return r.peers[peerId] -} - -func (r *Registry) setPeer(peer *Peer) { - r.peersMu.Lock() - r.peers[peer.ID()] = peer - metrics.GetOrRegisterGauge("registry.peers", nil).Update(int64(len(r.peers))) - r.peersMu.Unlock() -} - -func (r *Registry) deletePeer(peer *Peer) { - r.peersMu.Lock() - delete(r.peers, peer.ID()) - metrics.GetOrRegisterGauge("registry.peers", nil).Update(int64(len(r.peers))) - r.peersMu.Unlock() -} - -func (r *Registry) peersCount() (c int) { - r.peersMu.Lock() - c = len(r.peers) - r.peersMu.Unlock() - return -} - -// Run protocol run function -func (r *Registry) Run(p *network.BzzPeer) error { - sp := NewPeer(p.Peer, r) - r.setPeer(sp) - defer r.deletePeer(sp) - defer close(sp.quit) - defer sp.close() - - if r.autoRetrieval && !p.LightNode { - err := r.Subscribe(p.ID(), NewStream(swarmChunkServerStreamName, "", true), nil, Top) - if err != nil { - return err - } - } - - return sp.Run(sp.HandleMsg) -} - -// updateSyncing subscribes to SYNC streams by iterating over the -// kademlia connections and bins. If there are existing SYNC streams -// and they are no longer required after iteration, request to Quit -// them will be send to appropriate peers. -func (r *Registry) updateSyncing() { - kad := r.delivery.kad - // map of all SYNC streams for all peers - // used at the and of the function to remove servers - // that are not needed anymore - subs := make(map[enode.ID]map[Stream]struct{}) - r.peersMu.RLock() - for id, peer := range r.peers { - peer.serverMu.RLock() - for stream := range peer.servers { - if stream.Name == "SYNC" { - if _, ok := subs[id]; !ok { - subs[id] = make(map[Stream]struct{}) - } - subs[id][stream] = struct{}{} - } - } - peer.serverMu.RUnlock() - } - r.peersMu.RUnlock() - - // start requesting subscriptions from peers - r.requestPeerSubscriptions(kad, subs) - - // remove SYNC servers that do not need to be subscribed - for id, streams := range subs { - if len(streams) == 0 { - continue - } - peer := r.getPeer(id) - if peer == nil { - continue - } - for stream := range streams { - log.Debug("Remove sync server", "peer", id, "stream", stream) - err := r.Quit(peer.ID(), stream) - if err != nil && err != p2p.ErrShuttingDown { - log.Error("quit", "err", err, "peer", peer.ID(), "stream", stream) - } - } - } -} - -// requestPeerSubscriptions calls on each live peer in the kademlia table -// and sends a `RequestSubscription` to peers according to their bin -// and their relationship with kademlia's depth. -// Also check `TestRequestPeerSubscriptions` in order to understand the -// expected behavior. -// The function expects: -// * the kademlia -// * a map of subscriptions -// * the actual function to subscribe -// (in case of the test, it doesn't do real subscriptions) -func (r *Registry) requestPeerSubscriptions(kad *network.Kademlia, subs map[enode.ID]map[Stream]struct{}) { - - var startPo int - var endPo int - var ok bool - - // kademlia's depth - kadDepth := kad.NeighbourhoodDepth() - // request subscriptions for all nodes and bins - // nil as base takes the node's base; we need to pass 255 as `EachConn` runs - // from deepest bins backwards - kad.EachConn(nil, 255, func(p *network.Peer, po int) bool { - // nodes that do not provide stream protocol - // should not be subscribed, e.g. bootnodes - if !p.HasCap("stream") { - return true - } - //if the peer's bin is shallower than the kademlia depth, - //only the peer's bin should be subscribed - if po < kadDepth { - startPo = po - endPo = po - } else { - //if the peer's bin is equal or deeper than the kademlia depth, - //each bin from the depth up to k.MaxProxDisplay should be subscribed - startPo = kadDepth - endPo = kad.MaxProxDisplay - } - - for bin := startPo; bin <= endPo; bin++ { - //do the actual subscription - ok = subscriptionFunc(r, p, uint8(bin), subs) - } - return ok - }) -} - -// doRequestSubscription sends the actual RequestSubscription to the peer -func doRequestSubscription(r *Registry, p *network.Peer, bin uint8, subs map[enode.ID]map[Stream]struct{}) bool { - log.Debug("Requesting subscription by registry:", "registry", r.addr, "peer", p.ID(), "bin", bin) - // bin is always less then 256 and it is safe to convert it to type uint8 - stream := NewStream("SYNC", FormatSyncBinKey(bin), true) - if streams, ok := subs[p.ID()]; ok { - // delete live and history streams from the map, so that it won't be removed with a Quit request - delete(streams, stream) - delete(streams, getHistoryStream(stream)) - } - err := r.RequestSubscription(p.ID(), stream, NewRange(0, 0), High) - if err != nil { - log.Debug("Request subscription", "err", err, "peer", p.ID(), "stream", stream) - return false - } - return true -} - -func (r *Registry) runProtocol(p *p2p.Peer, rw p2p.MsgReadWriter) error { - peer := protocols.NewPeer(p, rw, r.spec) - bp := network.NewBzzPeer(peer) - np := network.NewPeer(bp, r.delivery.kad) - r.delivery.kad.On(np) - defer r.delivery.kad.Off(np) - return r.Run(bp) -} - -// HandleMsg is the message handler that delegates incoming messages -func (p *Peer) HandleMsg(ctx context.Context, msg interface{}) error { - switch msg := msg.(type) { - - case *SubscribeMsg: - return p.handleSubscribeMsg(ctx, msg) - - case *SubscribeErrorMsg: - return p.handleSubscribeErrorMsg(msg) - - case *UnsubscribeMsg: - return p.handleUnsubscribeMsg(msg) - - case *OfferedHashesMsg: - return p.handleOfferedHashesMsg(ctx, msg) - - case *TakeoverProofMsg: - return p.handleTakeoverProofMsg(ctx, msg) - - case *WantedHashesMsg: - return p.handleWantedHashesMsg(ctx, msg) - - case *ChunkDeliveryMsgRetrieval: - // handling chunk delivery is the same for retrieval and syncing, so let's cast the msg - return p.streamer.delivery.handleChunkDeliveryMsg(ctx, p, ((*ChunkDeliveryMsg)(msg))) - - case *ChunkDeliveryMsgSyncing: - // handling chunk delivery is the same for retrieval and syncing, so let's cast the msg - return p.streamer.delivery.handleChunkDeliveryMsg(ctx, p, ((*ChunkDeliveryMsg)(msg))) - - case *RetrieveRequestMsg: - return p.streamer.delivery.handleRetrieveRequestMsg(ctx, p, msg) - - case *RequestSubscriptionMsg: - return p.handleRequestSubscription(ctx, msg) - - case *QuitMsg: - return p.handleQuitMsg(msg) - - default: - return fmt.Errorf("unknown message type: %T", msg) - } -} - -type server struct { - Server - stream Stream - priority uint8 - currentBatch []byte - sessionIndex uint64 -} - -// setNextBatch adjusts passed interval based on session index and whether -// stream is live or history. It calls Server SetNextBatch with adjusted -// interval and returns batch hashes and their interval. -func (s *server) setNextBatch(from, to uint64) ([]byte, uint64, uint64, *HandoverProof, error) { - if s.stream.Live { - if from == 0 { - from = s.sessionIndex - } - if to <= from || from >= s.sessionIndex { - to = math.MaxUint64 - } - } else { - if (to < from && to != 0) || from > s.sessionIndex { - return nil, 0, 0, nil, nil - } - if to == 0 || to > s.sessionIndex { - to = s.sessionIndex - } - } - return s.SetNextBatch(from, to) -} - -// Server interface for outgoing peer Streamer -type Server interface { - // SessionIndex is called when a server is initialized - // to get the current cursor state of the stream data. - // Based on this index, live and history stream intervals - // will be adjusted before calling SetNextBatch. - SessionIndex() (uint64, error) - SetNextBatch(uint64, uint64) (hashes []byte, from uint64, to uint64, proof *HandoverProof, err error) - GetData(context.Context, []byte) ([]byte, error) - Close() -} - -type client struct { - Client - stream Stream - priority uint8 - sessionAt uint64 - to uint64 - next chan error - quit chan struct{} - - intervalsKey string - intervalsStore state.Store -} - -func peerStreamIntervalsKey(p *Peer, s Stream) string { - return p.ID().String() + s.String() -} - -func (c client) AddInterval(start, end uint64) (err error) { - i := &intervals.Intervals{} - err = c.intervalsStore.Get(c.intervalsKey, i) - if err != nil { - return err - } - i.Add(start, end) - return c.intervalsStore.Put(c.intervalsKey, i) -} - -func (c client) NextInterval() (start, end uint64, err error) { - i := &intervals.Intervals{} - err = c.intervalsStore.Get(c.intervalsKey, i) - if err != nil { - return 0, 0, err - } - start, end = i.Next() - return start, end, nil -} - -// Client interface for incoming peer Streamer -type Client interface { - NeedData(context.Context, []byte) func(context.Context) error - BatchDone(Stream, uint64, []byte, []byte) func() (*TakeoverProof, error) - Close() -} - -func (c *client) nextBatch(from uint64) (nextFrom uint64, nextTo uint64) { - if c.to > 0 && from >= c.to { - return 0, 0 - } - if c.stream.Live { - return from, 0 - } else if from >= c.sessionAt { - if c.to > 0 { - return from, c.to - } - return from, math.MaxUint64 - } - nextFrom, nextTo, err := c.NextInterval() - if err != nil { - log.Error("next intervals", "stream", c.stream) - return - } - if nextTo > c.to { - nextTo = c.to - } - if nextTo == 0 { - nextTo = c.sessionAt - } - return -} - -func (c *client) batchDone(p *Peer, req *OfferedHashesMsg, hashes []byte) error { - if tf := c.BatchDone(req.Stream, req.From, hashes, req.Root); tf != nil { - tp, err := tf() - if err != nil { - return err - } - - if err := p.SendPriority(context.TODO(), tp, c.priority, ""); err != nil { - return err - } - if c.to > 0 && tp.Takeover.End >= c.to { - return p.streamer.Unsubscribe(p.Peer.ID(), req.Stream) - } - return nil - } - // TODO: make a test case for testing if the interval is added when the batch is done - if err := c.AddInterval(req.From, req.To); err != nil { - return err - } - return nil -} - -func (c *client) close() { - select { - case <-c.quit: - default: - close(c.quit) - } - c.Close() -} - -// clientParams store parameters for the new client -// between a subscription and initial offered hashes request handling. -type clientParams struct { - priority uint8 - to uint64 - // signal when the client is created - clientCreatedC chan struct{} -} - -func newClientParams(priority uint8, to uint64) *clientParams { - return &clientParams{ - priority: priority, - to: to, - clientCreatedC: make(chan struct{}), - } -} - -func (c *clientParams) waitClient(ctx context.Context) error { - select { - case <-ctx.Done(): - return ctx.Err() - case <-c.clientCreatedC: - return nil - } -} - -func (c *clientParams) clientCreated() { - close(c.clientCreatedC) -} - -// GetSpec returns the streamer spec to callers -// This used to be a global variable but for simulations with -// multiple nodes its fields (notably the Hook) would be overwritten -func (r *Registry) GetSpec() *protocols.Spec { - return r.spec -} - -func (r *Registry) createSpec() { - // Spec is the spec of the streamer protocol - var spec = &protocols.Spec{ - Name: "stream", - Version: 8, - MaxMsgSize: 10 * 1024 * 1024, - Messages: []interface{}{ - UnsubscribeMsg{}, - OfferedHashesMsg{}, - WantedHashesMsg{}, - TakeoverProofMsg{}, - SubscribeMsg{}, - RetrieveRequestMsg{}, - ChunkDeliveryMsgRetrieval{}, - SubscribeErrorMsg{}, - RequestSubscriptionMsg{}, - QuitMsg{}, - ChunkDeliveryMsgSyncing{}, - }, - } - r.spec = spec -} - -// An accountable message needs some meta information attached to it -// in order to evaluate the correct price -type StreamerPrices struct { - priceMatrix map[reflect.Type]*protocols.Price - registry *Registry -} - -// Price implements the accounting interface and returns the price for a specific message -func (sp *StreamerPrices) Price(msg interface{}) *protocols.Price { - t := reflect.TypeOf(msg).Elem() - return sp.priceMatrix[t] -} - -// Instead of hardcoding the price, get it -// through a function - it could be quite complex in the future -func (sp *StreamerPrices) getRetrieveRequestMsgPrice() uint64 { - return uint64(1) -} - -// Instead of hardcoding the price, get it -// through a function - it could be quite complex in the future -func (sp *StreamerPrices) getChunkDeliveryMsgRetrievalPrice() uint64 { - return uint64(1) -} - -// createPriceOracle sets up a matrix which can be queried to get -// the price for a message via the Price method -func (r *Registry) createPriceOracle() { - sp := &StreamerPrices{ - registry: r, - } - sp.priceMatrix = map[reflect.Type]*protocols.Price{ - reflect.TypeOf(ChunkDeliveryMsgRetrieval{}): { - Value: sp.getChunkDeliveryMsgRetrievalPrice(), // arbitrary price for now - PerByte: true, - Payer: protocols.Receiver, - }, - reflect.TypeOf(RetrieveRequestMsg{}): { - Value: sp.getRetrieveRequestMsgPrice(), // arbitrary price for now - PerByte: false, - Payer: protocols.Sender, - }, - } - r.prices = sp -} - -func (r *Registry) Protocols() []p2p.Protocol { - return []p2p.Protocol{ - { - Name: r.spec.Name, - Version: r.spec.Version, - Length: r.spec.Length(), - Run: r.runProtocol, - }, - } -} - -func (r *Registry) APIs() []rpc.API { - return []rpc.API{ - { - Namespace: "stream", - Version: "3.0", - Service: r.api, - Public: true, - }, - } -} - -func (r *Registry) Start(server *p2p.Server) error { - log.Info("Streamer started") - return nil -} - -func (r *Registry) Stop() error { - return nil -} - -type Range struct { - From, To uint64 -} - -func NewRange(from, to uint64) *Range { - return &Range{ - From: from, - To: to, - } -} - -func (r *Range) String() string { - return fmt.Sprintf("%v-%v", r.From, r.To) -} - -func getHistoryPriority(priority uint8) uint8 { - if priority == 0 { - return 0 - } - return priority - 1 -} - -func getHistoryStream(s Stream) Stream { - return NewStream(s.Name, s.Key, false) -} - -type API struct { - streamer *Registry -} - -func NewAPI(r *Registry) *API { - return &API{ - streamer: r, - } -} - -func (api *API) SubscribeStream(peerId enode.ID, s Stream, history *Range, priority uint8) error { - return api.streamer.Subscribe(peerId, s, history, priority) -} - -func (api *API) UnsubscribeStream(peerId enode.ID, s Stream) error { - return api.streamer.Unsubscribe(peerId, s) -} - -/* -GetPeerSubscriptions is a API function which allows to query a peer for stream subscriptions it has. -It can be called via RPC. -It returns a map of node IDs with an array of string representations of Stream objects. -*/ -func (api *API) GetPeerSubscriptions() map[string][]string { - //create the empty map - pstreams := make(map[string][]string) - - //iterate all streamer peers - api.streamer.peersMu.RLock() - defer api.streamer.peersMu.RUnlock() - - for id, p := range api.streamer.peers { - var streams []string - //every peer has a map of stream servers - //every stream server represents a subscription - p.serverMu.RLock() - for s := range p.servers { - //append the string representation of the stream - //to the list for this peer - streams = append(streams, s.String()) - } - p.serverMu.RUnlock() - //set the array of stream servers to the map - pstreams[id.String()] = streams - } - return pstreams -} diff --git a/swarm/network/stream/streamer_test.go b/swarm/network/stream/streamer_test.go deleted file mode 100644 index a216145eabf4..000000000000 --- a/swarm/network/stream/streamer_test.go +++ /dev/null @@ -1,1357 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package stream - -import ( - "bytes" - "context" - "errors" - "fmt" - "strconv" - "strings" - "sync" - "testing" - "time" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" - p2ptest "github.com/ubiq/go-ubiq/p2p/testing" - "github.com/ubiq/go-ubiq/swarm/network" - "github.com/ubiq/go-ubiq/swarm/network/simulation" - "github.com/ubiq/go-ubiq/swarm/state" - "golang.org/x/crypto/sha3" -) - -func TestStreamerSubscribe(t *testing.T) { - tester, streamer, _, teardown, err := newStreamerTester(nil) - if err != nil { - t.Fatal(err) - } - defer teardown() - - stream := NewStream("foo", "", true) - err = streamer.Subscribe(tester.Nodes[0].ID(), stream, NewRange(0, 0), Top) - if err == nil || err.Error() != "stream foo not registered" { - t.Fatalf("Expected error %v, got %v", "stream foo not registered", err) - } -} - -func TestStreamerRequestSubscription(t *testing.T) { - tester, streamer, _, teardown, err := newStreamerTester(nil) - if err != nil { - t.Fatal(err) - } - defer teardown() - - stream := NewStream("foo", "", false) - err = streamer.RequestSubscription(tester.Nodes[0].ID(), stream, &Range{}, Top) - if err == nil || err.Error() != "stream foo not registered" { - t.Fatalf("Expected error %v, got %v", "stream foo not registered", err) - } -} - -var ( - hash0 = sha3.Sum256([]byte{0}) - hash1 = sha3.Sum256([]byte{1}) - hash2 = sha3.Sum256([]byte{2}) - hashesTmp = append(hash0[:], hash1[:]...) - hashes = append(hashesTmp, hash2[:]...) - corruptHashes = append(hashes[:40]) -) - -type testClient struct { - t string - wait0 chan bool - wait2 chan bool - batchDone chan bool - receivedHashes map[string][]byte -} - -func newTestClient(t string) *testClient { - return &testClient{ - t: t, - wait0: make(chan bool), - wait2: make(chan bool), - batchDone: make(chan bool), - receivedHashes: make(map[string][]byte), - } -} - -func (self *testClient) NeedData(ctx context.Context, hash []byte) func(context.Context) error { - self.receivedHashes[string(hash)] = hash - if bytes.Equal(hash, hash0[:]) { - return func(context.Context) error { - <-self.wait0 - return nil - } - } else if bytes.Equal(hash, hash2[:]) { - return func(context.Context) error { - <-self.wait2 - return nil - } - } - return nil -} - -func (self *testClient) BatchDone(Stream, uint64, []byte, []byte) func() (*TakeoverProof, error) { - close(self.batchDone) - return nil -} - -func (self *testClient) Close() {} - -type testServer struct { - t string - sessionIndex uint64 -} - -func newTestServer(t string, sessionIndex uint64) *testServer { - return &testServer{ - t: t, - sessionIndex: sessionIndex, - } -} - -func (s *testServer) SessionIndex() (uint64, error) { - return s.sessionIndex, nil -} - -func (self *testServer) SetNextBatch(from uint64, to uint64) ([]byte, uint64, uint64, *HandoverProof, error) { - return make([]byte, HashSize), from + 1, to + 1, nil, nil -} - -func (self *testServer) GetData(context.Context, []byte) ([]byte, error) { - return nil, nil -} - -func (self *testServer) Close() { -} - -func TestStreamerDownstreamSubscribeUnsubscribeMsgExchange(t *testing.T) { - tester, streamer, _, teardown, err := newStreamerTester(nil) - if err != nil { - t.Fatal(err) - } - defer teardown() - - streamer.RegisterClientFunc("foo", func(p *Peer, t string, live bool) (Client, error) { - return newTestClient(t), nil - }) - - node := tester.Nodes[0] - - stream := NewStream("foo", "", true) - err = streamer.Subscribe(node.ID(), stream, NewRange(5, 8), Top) - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - - err = tester.TestExchanges( - p2ptest.Exchange{ - Label: "Subscribe message", - Expects: []p2ptest.Expect{ - { - Code: 4, - Msg: &SubscribeMsg{ - Stream: stream, - History: NewRange(5, 8), - Priority: Top, - }, - Peer: node.ID(), - }, - }, - }, - // trigger OfferedHashesMsg to actually create the client - p2ptest.Exchange{ - Label: "OfferedHashes message", - Triggers: []p2ptest.Trigger{ - { - Code: 1, - Msg: &OfferedHashesMsg{ - HandoverProof: &HandoverProof{ - Handover: &Handover{}, - }, - Hashes: hashes, - From: 5, - To: 8, - Stream: stream, - }, - Peer: node.ID(), - }, - }, - Expects: []p2ptest.Expect{ - { - Code: 2, - Msg: &WantedHashesMsg{ - Stream: stream, - Want: []byte{5}, - From: 9, - To: 0, - }, - Peer: node.ID(), - }, - }, - }, - ) - if err != nil { - t.Fatal(err) - } - - err = streamer.Unsubscribe(node.ID(), stream) - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - - err = tester.TestExchanges(p2ptest.Exchange{ - Label: "Unsubscribe message", - Expects: []p2ptest.Expect{ - { - Code: 0, - Msg: &UnsubscribeMsg{ - Stream: stream, - }, - Peer: node.ID(), - }, - }, - }) - - if err != nil { - t.Fatal(err) - } -} - -func TestStreamerUpstreamSubscribeUnsubscribeMsgExchange(t *testing.T) { - tester, streamer, _, teardown, err := newStreamerTester(nil) - if err != nil { - t.Fatal(err) - } - defer teardown() - - stream := NewStream("foo", "", false) - - streamer.RegisterServerFunc("foo", func(p *Peer, t string, live bool) (Server, error) { - return newTestServer(t, 10), nil - }) - - node := tester.Nodes[0] - - err = tester.TestExchanges(p2ptest.Exchange{ - Label: "Subscribe message", - Triggers: []p2ptest.Trigger{ - { - Code: 4, - Msg: &SubscribeMsg{ - Stream: stream, - History: NewRange(5, 8), - Priority: Top, - }, - Peer: node.ID(), - }, - }, - Expects: []p2ptest.Expect{ - { - Code: 1, - Msg: &OfferedHashesMsg{ - Stream: stream, - HandoverProof: &HandoverProof{ - Handover: &Handover{}, - }, - Hashes: make([]byte, HashSize), - From: 6, - To: 9, - }, - Peer: node.ID(), - }, - }, - }) - - if err != nil { - t.Fatal(err) - } - - err = tester.TestExchanges(p2ptest.Exchange{ - Label: "unsubscribe message", - Triggers: []p2ptest.Trigger{ - { - Code: 0, - Msg: &UnsubscribeMsg{ - Stream: stream, - }, - Peer: node.ID(), - }, - }, - }) - - if err != nil { - t.Fatal(err) - } -} - -func TestStreamerUpstreamSubscribeUnsubscribeMsgExchangeLive(t *testing.T) { - tester, streamer, _, teardown, err := newStreamerTester(nil) - if err != nil { - t.Fatal(err) - } - defer teardown() - - stream := NewStream("foo", "", true) - - streamer.RegisterServerFunc("foo", func(p *Peer, t string, live bool) (Server, error) { - return newTestServer(t, 0), nil - }) - - node := tester.Nodes[0] - - err = tester.TestExchanges(p2ptest.Exchange{ - Label: "Subscribe message", - Triggers: []p2ptest.Trigger{ - { - Code: 4, - Msg: &SubscribeMsg{ - Stream: stream, - Priority: Top, - }, - Peer: node.ID(), - }, - }, - Expects: []p2ptest.Expect{ - { - Code: 1, - Msg: &OfferedHashesMsg{ - Stream: stream, - HandoverProof: &HandoverProof{ - Handover: &Handover{}, - }, - Hashes: make([]byte, HashSize), - From: 1, - To: 0, - }, - Peer: node.ID(), - }, - }, - }) - - if err != nil { - t.Fatal(err) - } - - err = tester.TestExchanges(p2ptest.Exchange{ - Label: "unsubscribe message", - Triggers: []p2ptest.Trigger{ - { - Code: 0, - Msg: &UnsubscribeMsg{ - Stream: stream, - }, - Peer: node.ID(), - }, - }, - }) - - if err != nil { - t.Fatal(err) - } -} - -func TestStreamerUpstreamSubscribeErrorMsgExchange(t *testing.T) { - tester, streamer, _, teardown, err := newStreamerTester(nil) - if err != nil { - t.Fatal(err) - } - defer teardown() - - streamer.RegisterServerFunc("foo", func(p *Peer, t string, live bool) (Server, error) { - return newTestServer(t, 0), nil - }) - - stream := NewStream("bar", "", true) - - node := tester.Nodes[0] - - err = tester.TestExchanges(p2ptest.Exchange{ - Label: "Subscribe message", - Triggers: []p2ptest.Trigger{ - { - Code: 4, - Msg: &SubscribeMsg{ - Stream: stream, - History: NewRange(5, 8), - Priority: Top, - }, - Peer: node.ID(), - }, - }, - Expects: []p2ptest.Expect{ - { - Code: 7, - Msg: &SubscribeErrorMsg{ - Error: "stream bar not registered", - }, - Peer: node.ID(), - }, - }, - }) - - if err != nil { - t.Fatal(err) - } -} - -func TestStreamerUpstreamSubscribeLiveAndHistory(t *testing.T) { - tester, streamer, _, teardown, err := newStreamerTester(nil) - if err != nil { - t.Fatal(err) - } - defer teardown() - - stream := NewStream("foo", "", true) - - streamer.RegisterServerFunc("foo", func(p *Peer, t string, live bool) (Server, error) { - return newTestServer(t, 10), nil - }) - - node := tester.Nodes[0] - - err = tester.TestExchanges(p2ptest.Exchange{ - Label: "Subscribe message", - Triggers: []p2ptest.Trigger{ - { - Code: 4, - Msg: &SubscribeMsg{ - Stream: stream, - History: NewRange(5, 8), - Priority: Top, - }, - Peer: node.ID(), - }, - }, - Expects: []p2ptest.Expect{ - { - Code: 1, - Msg: &OfferedHashesMsg{ - Stream: NewStream("foo", "", false), - HandoverProof: &HandoverProof{ - Handover: &Handover{}, - }, - Hashes: make([]byte, HashSize), - From: 6, - To: 9, - }, - Peer: node.ID(), - }, - { - Code: 1, - Msg: &OfferedHashesMsg{ - Stream: stream, - HandoverProof: &HandoverProof{ - Handover: &Handover{}, - }, - From: 11, - To: 0, - Hashes: make([]byte, HashSize), - }, - Peer: node.ID(), - }, - }, - }) - - if err != nil { - t.Fatal(err) - } -} - -func TestStreamerDownstreamCorruptHashesMsgExchange(t *testing.T) { - tester, streamer, _, teardown, err := newStreamerTester(nil) - if err != nil { - t.Fatal(err) - } - defer teardown() - - stream := NewStream("foo", "", true) - - var tc *testClient - - streamer.RegisterClientFunc("foo", func(p *Peer, t string, live bool) (Client, error) { - tc = newTestClient(t) - return tc, nil - }) - - node := tester.Nodes[0] - - err = streamer.Subscribe(node.ID(), stream, NewRange(5, 8), Top) - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - - err = tester.TestExchanges(p2ptest.Exchange{ - Label: "Subscribe message", - Expects: []p2ptest.Expect{ - { - Code: 4, - Msg: &SubscribeMsg{ - Stream: stream, - History: NewRange(5, 8), - Priority: Top, - }, - Peer: node.ID(), - }, - }, - }, - p2ptest.Exchange{ - Label: "Corrupt offered hash message", - Triggers: []p2ptest.Trigger{ - { - Code: 1, - Msg: &OfferedHashesMsg{ - HandoverProof: &HandoverProof{ - Handover: &Handover{}, - }, - Hashes: corruptHashes, - From: 5, - To: 8, - Stream: stream, - }, - Peer: node.ID(), - }, - }, - }) - if err != nil { - t.Fatal(err) - } - - expectedError := errors.New("Message handler error: (msg code 1): error invalid hashes length (len: 40)") - if err := tester.TestDisconnected(&p2ptest.Disconnect{Peer: node.ID(), Error: expectedError}); err != nil { - t.Fatal(err) - } -} - -func TestStreamerDownstreamOfferedHashesMsgExchange(t *testing.T) { - tester, streamer, _, teardown, err := newStreamerTester(nil) - if err != nil { - t.Fatal(err) - } - defer teardown() - - stream := NewStream("foo", "", true) - - var tc *testClient - - streamer.RegisterClientFunc("foo", func(p *Peer, t string, live bool) (Client, error) { - tc = newTestClient(t) - return tc, nil - }) - - node := tester.Nodes[0] - - err = streamer.Subscribe(node.ID(), stream, NewRange(5, 8), Top) - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - - err = tester.TestExchanges(p2ptest.Exchange{ - Label: "Subscribe message", - Expects: []p2ptest.Expect{ - { - Code: 4, - Msg: &SubscribeMsg{ - Stream: stream, - History: NewRange(5, 8), - Priority: Top, - }, - Peer: node.ID(), - }, - }, - }, - p2ptest.Exchange{ - Label: "WantedHashes message", - Triggers: []p2ptest.Trigger{ - { - Code: 1, - Msg: &OfferedHashesMsg{ - HandoverProof: &HandoverProof{ - Handover: &Handover{}, - }, - Hashes: hashes, - From: 5, - To: 8, - Stream: stream, - }, - Peer: node.ID(), - }, - }, - Expects: []p2ptest.Expect{ - { - Code: 2, - Msg: &WantedHashesMsg{ - Stream: stream, - Want: []byte{5}, - From: 9, - To: 0, - }, - Peer: node.ID(), - }, - }, - }) - if err != nil { - t.Fatal(err) - } - - if len(tc.receivedHashes) != 3 { - t.Fatalf("Expected number of received hashes %v, got %v", 3, len(tc.receivedHashes)) - } - - close(tc.wait0) - - timeout := time.NewTimer(100 * time.Millisecond) - defer timeout.Stop() - - select { - case <-tc.batchDone: - t.Fatal("batch done early") - case <-timeout.C: - } - - close(tc.wait2) - - timeout2 := time.NewTimer(10000 * time.Millisecond) - defer timeout2.Stop() - - select { - case <-tc.batchDone: - case <-timeout2.C: - t.Fatal("timeout waiting batchdone call") - } - -} - -func TestStreamerRequestSubscriptionQuitMsgExchange(t *testing.T) { - tester, streamer, _, teardown, err := newStreamerTester(nil) - if err != nil { - t.Fatal(err) - } - defer teardown() - - streamer.RegisterServerFunc("foo", func(p *Peer, t string, live bool) (Server, error) { - return newTestServer(t, 10), nil - }) - - node := tester.Nodes[0] - - stream := NewStream("foo", "", true) - err = streamer.RequestSubscription(node.ID(), stream, NewRange(5, 8), Top) - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - - err = tester.TestExchanges( - p2ptest.Exchange{ - Label: "RequestSubscription message", - Expects: []p2ptest.Expect{ - { - Code: 8, - Msg: &RequestSubscriptionMsg{ - Stream: stream, - History: NewRange(5, 8), - Priority: Top, - }, - Peer: node.ID(), - }, - }, - }, - p2ptest.Exchange{ - Label: "Subscribe message", - Triggers: []p2ptest.Trigger{ - { - Code: 4, - Msg: &SubscribeMsg{ - Stream: stream, - History: NewRange(5, 8), - Priority: Top, - }, - Peer: node.ID(), - }, - }, - Expects: []p2ptest.Expect{ - { - Code: 1, - Msg: &OfferedHashesMsg{ - Stream: NewStream("foo", "", false), - HandoverProof: &HandoverProof{ - Handover: &Handover{}, - }, - Hashes: make([]byte, HashSize), - From: 6, - To: 9, - }, - Peer: node.ID(), - }, - { - Code: 1, - Msg: &OfferedHashesMsg{ - Stream: stream, - HandoverProof: &HandoverProof{ - Handover: &Handover{}, - }, - From: 11, - To: 0, - Hashes: make([]byte, HashSize), - }, - Peer: node.ID(), - }, - }, - }, - ) - if err != nil { - t.Fatal(err) - } - - err = streamer.Quit(node.ID(), stream) - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - - err = tester.TestExchanges(p2ptest.Exchange{ - Label: "Quit message", - Expects: []p2ptest.Expect{ - { - Code: 9, - Msg: &QuitMsg{ - Stream: stream, - }, - Peer: node.ID(), - }, - }, - }) - - if err != nil { - t.Fatal(err) - } - - historyStream := getHistoryStream(stream) - - err = streamer.Quit(node.ID(), historyStream) - if err != nil { - t.Fatalf("Expected no error, got %v", err) - } - - err = tester.TestExchanges(p2ptest.Exchange{ - Label: "Quit message", - Expects: []p2ptest.Expect{ - { - Code: 9, - Msg: &QuitMsg{ - Stream: historyStream, - }, - Peer: node.ID(), - }, - }, - }) - - if err != nil { - t.Fatal(err) - } -} - -// TestMaxPeerServersWithUnsubscribe creates a registry with a limited -// number of stream servers, and performs a test with subscriptions and -// unsubscriptions, checking if unsubscriptions will remove streams, -// leaving place for new streams. -func TestMaxPeerServersWithUnsubscribe(t *testing.T) { - var maxPeerServers = 6 - tester, streamer, _, teardown, err := newStreamerTester(&RegistryOptions{ - Retrieval: RetrievalDisabled, - Syncing: SyncingDisabled, - MaxPeerServers: maxPeerServers, - }) - if err != nil { - t.Fatal(err) - } - defer teardown() - - streamer.RegisterServerFunc("foo", func(p *Peer, t string, live bool) (Server, error) { - return newTestServer(t, 0), nil - }) - - node := tester.Nodes[0] - - for i := 0; i < maxPeerServers+10; i++ { - stream := NewStream("foo", strconv.Itoa(i), true) - - err = tester.TestExchanges(p2ptest.Exchange{ - Label: "Subscribe message", - Triggers: []p2ptest.Trigger{ - { - Code: 4, - Msg: &SubscribeMsg{ - Stream: stream, - Priority: Top, - }, - Peer: node.ID(), - }, - }, - Expects: []p2ptest.Expect{ - { - Code: 1, - Msg: &OfferedHashesMsg{ - Stream: stream, - HandoverProof: &HandoverProof{ - Handover: &Handover{}, - }, - Hashes: make([]byte, HashSize), - From: 1, - To: 0, - }, - Peer: node.ID(), - }, - }, - }) - - if err != nil { - t.Fatal(err) - } - - err = tester.TestExchanges(p2ptest.Exchange{ - Label: "unsubscribe message", - Triggers: []p2ptest.Trigger{ - { - Code: 0, - Msg: &UnsubscribeMsg{ - Stream: stream, - }, - Peer: node.ID(), - }, - }, - }) - - if err != nil { - t.Fatal(err) - } - } -} - -// TestMaxPeerServersWithoutUnsubscribe creates a registry with a limited -// number of stream servers, and performs subscriptions to detect subscriptions -// error message exchange. -func TestMaxPeerServersWithoutUnsubscribe(t *testing.T) { - var maxPeerServers = 6 - tester, streamer, _, teardown, err := newStreamerTester(&RegistryOptions{ - MaxPeerServers: maxPeerServers, - }) - if err != nil { - t.Fatal(err) - } - defer teardown() - - streamer.RegisterServerFunc("foo", func(p *Peer, t string, live bool) (Server, error) { - return newTestServer(t, 0), nil - }) - - node := tester.Nodes[0] - - for i := 0; i < maxPeerServers+10; i++ { - stream := NewStream("foo", strconv.Itoa(i), true) - - if i >= maxPeerServers { - err = tester.TestExchanges(p2ptest.Exchange{ - Label: "Subscribe message", - Triggers: []p2ptest.Trigger{ - { - Code: 4, - Msg: &SubscribeMsg{ - Stream: stream, - Priority: Top, - }, - Peer: node.ID(), - }, - }, - Expects: []p2ptest.Expect{ - { - Code: 7, - Msg: &SubscribeErrorMsg{ - Error: ErrMaxPeerServers.Error(), - }, - Peer: node.ID(), - }, - }, - }) - - if err != nil { - t.Fatal(err) - } - continue - } - - err = tester.TestExchanges(p2ptest.Exchange{ - Label: "Subscribe message", - Triggers: []p2ptest.Trigger{ - { - Code: 4, - Msg: &SubscribeMsg{ - Stream: stream, - Priority: Top, - }, - Peer: node.ID(), - }, - }, - Expects: []p2ptest.Expect{ - { - Code: 1, - Msg: &OfferedHashesMsg{ - Stream: stream, - HandoverProof: &HandoverProof{ - Handover: &Handover{}, - }, - Hashes: make([]byte, HashSize), - From: 1, - To: 0, - }, - Peer: node.ID(), - }, - }, - }) - - if err != nil { - t.Fatal(err) - } - } -} - -//TestHasPriceImplementation is to check that the Registry has a -//`Price` interface implementation -func TestHasPriceImplementation(t *testing.T) { - _, r, _, teardown, err := newStreamerTester(&RegistryOptions{ - Retrieval: RetrievalDisabled, - Syncing: SyncingDisabled, - }) - if err != nil { - t.Fatal(err) - } - defer teardown() - - if r.prices == nil { - t.Fatal("No prices implementation available for the stream protocol") - } - - pricesInstance, ok := r.prices.(*StreamerPrices) - if !ok { - t.Fatal("`Registry` does not have the expected Prices instance") - } - price := pricesInstance.Price(&ChunkDeliveryMsgRetrieval{}) - if price == nil || price.Value == 0 || price.Value != pricesInstance.getChunkDeliveryMsgRetrievalPrice() { - t.Fatal("No prices set for chunk delivery msg") - } - - price = pricesInstance.Price(&RetrieveRequestMsg{}) - if price == nil || price.Value == 0 || price.Value != pricesInstance.getRetrieveRequestMsgPrice() { - t.Fatal("No prices set for chunk delivery msg") - } -} - -/* -TestRequestPeerSubscriptions is a unit test for stream's pull sync subscriptions. - -The test does: - * assign each connected peer to a bin map - * build up a known kademlia in advance - * run the EachConn function, which returns supposed subscription bins - * store all supposed bins per peer in a map - * check that all peers have the expected subscriptions - -This kad table and its peers are copied from network.TestKademliaCase1, -it represents an edge case but for the purpose of testing the -syncing subscriptions it is just fine. - -Addresses used in this test are discovered as part of the simulation network -in higher level tests for streaming. They were generated randomly. - -The resulting kademlia looks like this: -========================================================================= -Fri Dec 21 20:02:39 UTC 2018 KΛÐΞMLIΛ hive: queen's address: 7efef1 -population: 12 (12), MinProxBinSize: 2, MinBinSize: 2, MaxBinSize: 4 -000 2 8196 835f | 2 8196 (0) 835f (0) -001 2 2690 28f0 | 2 2690 (0) 28f0 (0) -002 2 4d72 4a45 | 2 4d72 (0) 4a45 (0) -003 1 646e | 1 646e (0) -004 3 769c 76d1 7656 | 3 769c (0) 76d1 (0) 7656 (0) -============ DEPTH: 5 ========================================== -005 1 7a48 | 1 7a48 (0) -006 1 7cbd | 1 7cbd (0) -007 0 | 0 -008 0 | 0 -009 0 | 0 -010 0 | 0 -011 0 | 0 -012 0 | 0 -013 0 | 0 -014 0 | 0 -015 0 | 0 -========================================================================= -*/ -func TestRequestPeerSubscriptions(t *testing.T) { - // the pivot address; this is the actual kademlia node - pivotAddr := "7efef1c41d77f843ad167be95f6660567eb8a4a59f39240000cce2e0d65baf8e" - - // a map of bin number to addresses from the given kademlia - binMap := make(map[int][]string) - binMap[0] = []string{ - "835fbbf1d16ba7347b6e2fc552d6e982148d29c624ea20383850df3c810fa8fc", - "81968a2d8fb39114342ee1da85254ec51e0608d7f0f6997c2a8354c260a71009", - } - binMap[1] = []string{ - "28f0bc1b44658548d6e05dd16d4c2fe77f1da5d48b6774bc4263b045725d0c19", - "2690a910c33ee37b91eb6c4e0731d1d345e2dc3b46d308503a6e85bbc242c69e", - } - binMap[2] = []string{ - "4a45f1fc63e1a9cb9dfa44c98da2f3d20c2923e5d75ff60b2db9d1bdb0c54d51", - "4d72a04ddeb851a68cd197ef9a92a3e2ff01fbbff638e64929dd1a9c2e150112", - } - binMap[3] = []string{ - "646e9540c84f6a2f9cf6585d45a4c219573b4fd1b64a3c9a1386fc5cf98c0d4d", - } - binMap[4] = []string{ - "7656caccdc79cd8d7ce66d415cc96a718e8271c62fb35746bfc2b49faf3eebf3", - "76d1e83c71ca246d042e37ff1db181f2776265fbcfdc890ce230bfa617c9c2f0", - "769ce86aa90b518b7ed382f9fdacfbed93574e18dc98fe6c342e4f9f409c2d5a", - } - binMap[5] = []string{ - "7a48f75f8ca60487ae42d6f92b785581b40b91f2da551ae73d5eae46640e02e8", - } - binMap[6] = []string{ - "7cbd42350bde8e18ae5b955b5450f8e2cef3419f92fbf5598160c60fd78619f0", - } - - // create the pivot's kademlia - addr := common.FromHex(pivotAddr) - k := network.NewKademlia(addr, network.NewKadParams()) - - // construct the peers and the kademlia - for _, binaddrs := range binMap { - for _, a := range binaddrs { - addr := common.FromHex(a) - k.On(network.NewPeer(&network.BzzPeer{BzzAddr: &network.BzzAddr{OAddr: addr}}, k)) - } - } - - // TODO: check kad table is same - // currently k.String() prints date so it will never be the same :) - // --> implement JSON representation of kad table - log.Debug(k.String()) - - // simulate that we would do subscriptions: just store the bin numbers - fakeSubscriptions := make(map[string][]int) - //after the test, we need to reset the subscriptionFunc to the default - defer func() { subscriptionFunc = doRequestSubscription }() - // define the function which should run for each connection - // instead of doing real subscriptions, we just store the bin numbers - subscriptionFunc = func(r *Registry, p *network.Peer, bin uint8, subs map[enode.ID]map[Stream]struct{}) bool { - // get the peer ID - peerstr := fmt.Sprintf("%x", p.Over()) - // create the array of bins per peer - if _, ok := fakeSubscriptions[peerstr]; !ok { - fakeSubscriptions[peerstr] = make([]int, 0) - } - // store the (fake) bin subscription - log.Debug(fmt.Sprintf("Adding fake subscription for peer %s with bin %d", peerstr, bin)) - fakeSubscriptions[peerstr] = append(fakeSubscriptions[peerstr], int(bin)) - return true - } - // create just a simple Registry object in order to be able to call... - r := &Registry{} - r.requestPeerSubscriptions(k, nil) - // calculate the kademlia depth - kdepth := k.NeighbourhoodDepth() - - // now, check that all peers have the expected (fake) subscriptions - // iterate the bin map - for bin, peers := range binMap { - // for every peer... - for _, peer := range peers { - // ...get its (fake) subscriptions - fakeSubsForPeer := fakeSubscriptions[peer] - // if the peer's bin is shallower than the kademlia depth... - if bin < kdepth { - // (iterate all (fake) subscriptions) - for _, subbin := range fakeSubsForPeer { - // ...only the peer's bin should be "subscribed" - // (and thus have only one subscription) - if subbin != bin || len(fakeSubsForPeer) != 1 { - t.Fatalf("Did not get expected subscription for bin < depth; bin of peer %s: %d, subscription: %d", peer, bin, subbin) - } - } - } else { //if the peer's bin is equal or higher than the kademlia depth... - // (iterate all (fake) subscriptions) - for i, subbin := range fakeSubsForPeer { - // ...each bin from the peer's bin number up to k.MaxProxDisplay should be "subscribed" - // as we start from depth we can use the iteration index to check - if subbin != i+kdepth { - t.Fatalf("Did not get expected subscription for bin > depth; bin of peer %s: %d, subscription: %d", peer, bin, subbin) - } - // the last "subscription" should be k.MaxProxDisplay - if i == len(fakeSubsForPeer)-1 && subbin != k.MaxProxDisplay { - t.Fatalf("Expected last subscription to be: %d, but is: %d", k.MaxProxDisplay, subbin) - } - } - } - } - } - // print some output - for p, subs := range fakeSubscriptions { - log.Debug(fmt.Sprintf("Peer %s has the following fake subscriptions: ", p)) - for _, bin := range subs { - log.Debug(fmt.Sprintf("%d,", bin)) - } - } -} - -// TestGetSubscriptions is a unit test for the api.GetPeerSubscriptions() function -func TestGetSubscriptions(t *testing.T) { - // create an amount of dummy peers - testPeerCount := 8 - // every peer will have this amount of dummy servers - testServerCount := 4 - // the peerMap which will store this data for the registry - peerMap := make(map[enode.ID]*Peer) - // create the registry - r := &Registry{} - api := NewAPI(r) - // call once, at this point should be empty - regs := api.GetPeerSubscriptions() - if len(regs) != 0 { - t.Fatal("Expected subscription count to be 0, but it is not") - } - - // now create a number of dummy servers for each node - for i := 0; i < testPeerCount; i++ { - addr := network.RandomAddr() - id := addr.ID() - p := &Peer{} - p.servers = make(map[Stream]*server) - for k := 0; k < testServerCount; k++ { - s := Stream{ - Name: strconv.Itoa(k), - Key: "", - Live: false, - } - p.servers[s] = &server{} - } - peerMap[id] = p - } - r.peers = peerMap - - // call the subscriptions again - regs = api.GetPeerSubscriptions() - // count how many (fake) subscriptions there are - cnt := 0 - for _, reg := range regs { - for range reg { - cnt++ - } - } - // check expected value - expectedCount := testPeerCount * testServerCount - if cnt != expectedCount { - t.Fatalf("Expected %d subscriptions, but got %d", expectedCount, cnt) - } -} - -/* -TestGetSubscriptionsRPC sets up a simulation network of `nodeCount` nodes, -starts the simulation, waits for SyncUpdateDelay in order to kick off -stream registration, then tests that there are subscriptions. -*/ -func TestGetSubscriptionsRPC(t *testing.T) { - - // arbitrarily set to 4 - nodeCount := 4 - // run with more nodes if `longrunning` flag is set - if *longrunning { - nodeCount = 64 - } - // set the syncUpdateDelay for sync registrations to start - syncUpdateDelay := 200 * time.Millisecond - // holds the msg code for SubscribeMsg - var subscribeMsgCode uint64 - var ok bool - var expectedMsgCount counter - - // this channel signalizes that the expected amount of subscriptiosn is done - allSubscriptionsDone := make(chan struct{}) - // after the test, we need to reset the subscriptionFunc to the default - defer func() { subscriptionFunc = doRequestSubscription }() - - // we use this subscriptionFunc for this test: just increases count and calls the actual subscription - subscriptionFunc = func(r *Registry, p *network.Peer, bin uint8, subs map[enode.ID]map[Stream]struct{}) bool { - expectedMsgCount.inc() - doRequestSubscription(r, p, bin, subs) - return true - } - // create a standard sim - sim := simulation.New(map[string]simulation.ServiceFunc{ - "streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) { - addr, netStore, delivery, clean, err := newNetStoreAndDeliveryWithRequestFunc(ctx, bucket, dummyRequestFromPeers) - if err != nil { - return nil, nil, err - } - - // configure so that sync registrations actually happen - r := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), &RegistryOptions{ - Retrieval: RetrievalEnabled, - Syncing: SyncingAutoSubscribe, //enable sync registrations - SyncUpdateDelay: syncUpdateDelay, - }, nil) - - // get the SubscribeMsg code - subscribeMsgCode, ok = r.GetSpec().GetCode(SubscribeMsg{}) - if !ok { - t.Fatal("Message code for SubscribeMsg not found") - } - - cleanup = func() { - r.Close() - clean() - } - - return r, cleanup, nil - }, - }) - defer sim.Close() - - ctx, cancelSimRun := context.WithTimeout(context.Background(), 1*time.Minute) - defer cancelSimRun() - - // upload a snapshot - err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount)) - if err != nil { - t.Fatal(err) - } - - // setup the filter for SubscribeMsg - msgs := sim.PeerEvents( - context.Background(), - sim.NodeIDs(), - simulation.NewPeerEventsFilter().ReceivedMessages().Protocol("stream").MsgCode(subscribeMsgCode), - ) - - // strategy: listen to all SubscribeMsg events; after every event we wait - // if after `waitDuration` no more messages are being received, we assume the - // subscription phase has terminated! - - // the loop in this go routine will either wait for new message events - // or times out after 1 second, which signals that we are not receiving - // any new subscriptions any more - go func() { - //for long running sims, waiting 1 sec will not be enough - waitDuration := time.Duration(nodeCount/16) * time.Second - for { - select { - case <-ctx.Done(): - return - case m := <-msgs: // just reset the loop - if m.Error != nil { - log.Error("stream message", "err", m.Error) - continue - } - log.Trace("stream message", "node", m.NodeID, "peer", m.PeerID) - case <-time.After(waitDuration): - // one second passed, don't assume more subscriptions - allSubscriptionsDone <- struct{}{} - log.Info("All subscriptions received") - return - - } - } - }() - - //run the simulation - result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error { - log.Info("Simulation running") - nodes := sim.Net.Nodes - - //wait until all subscriptions are done - select { - case <-allSubscriptionsDone: - case <-ctx.Done(): - return errors.New("Context timed out") - } - - log.Debug("Expected message count: ", "expectedMsgCount", expectedMsgCount.count()) - //now iterate again, this time we call each node via RPC to get its subscriptions - realCount := 0 - for _, node := range nodes { - //create rpc client - client, err := node.Client() - if err != nil { - return fmt.Errorf("create node 1 rpc client fail: %v", err) - } - - //ask it for subscriptions - pstreams := make(map[string][]string) - err = client.Call(&pstreams, "stream_getPeerSubscriptions") - if err != nil { - return fmt.Errorf("client call stream_getPeerSubscriptions: %v", err) - } - //length of the subscriptions can not be smaller than number of peers - log.Debug("node subscriptions", "node", node.String()) - for p, ps := range pstreams { - log.Debug("... with", "peer", p) - for _, s := range ps { - log.Debug(".......", "stream", s) - // each node also has subscriptions to RETRIEVE_REQUEST streams, - // we need to ignore those, we are only counting SYNC streams - if !strings.HasPrefix(s, "RETRIEVE_REQUEST") { - realCount++ - } - } - } - } - // every node is mutually subscribed to each other, so the actual count is half of it - emc := expectedMsgCount.count() - if realCount/2 != emc { - return fmt.Errorf("Real subscriptions and expected amount don't match; real: %d, expected: %d", realCount/2, emc) - } - return nil - }) - if result.Error != nil { - t.Fatal(result.Error) - } -} - -// counter is used to concurrently increment -// and read an integer value. -type counter struct { - v int - mu sync.RWMutex -} - -// Increment the counter. -func (c *counter) inc() { - c.mu.Lock() - defer c.mu.Unlock() - - c.v++ -} - -// Read the counter value. -func (c *counter) count() int { - c.mu.RLock() - defer c.mu.RUnlock() - - return c.v -} diff --git a/swarm/network/stream/syncer.go b/swarm/network/stream/syncer.go deleted file mode 100644 index 84d8f9ea7906..000000000000 --- a/swarm/network/stream/syncer.go +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package stream - -import ( - "context" - "strconv" - "time" - - "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/storage" -) - -const ( - BatchSize = 128 -) - -// SwarmSyncerServer implements an Server for history syncing on bins -// offered streams: -// * live request delivery with or without checkback -// * (live/non-live historical) chunk syncing per proximity bin -type SwarmSyncerServer struct { - po uint8 - store storage.SyncChunkStore - quit chan struct{} -} - -// NewSwarmSyncerServer is constructor for SwarmSyncerServer -func NewSwarmSyncerServer(po uint8, syncChunkStore storage.SyncChunkStore) (*SwarmSyncerServer, error) { - return &SwarmSyncerServer{ - po: po, - store: syncChunkStore, - quit: make(chan struct{}), - }, nil -} - -func RegisterSwarmSyncerServer(streamer *Registry, syncChunkStore storage.SyncChunkStore) { - streamer.RegisterServerFunc("SYNC", func(_ *Peer, t string, _ bool) (Server, error) { - po, err := ParseSyncBinKey(t) - if err != nil { - return nil, err - } - return NewSwarmSyncerServer(po, syncChunkStore) - }) - // streamer.RegisterServerFunc(stream, func(p *Peer) (Server, error) { - // return NewOutgoingProvableSwarmSyncer(po, db) - // }) -} - -// Close needs to be called on a stream server -func (s *SwarmSyncerServer) Close() { - close(s.quit) -} - -// GetData retrieves the actual chunk from netstore -func (s *SwarmSyncerServer) GetData(ctx context.Context, key []byte) ([]byte, error) { - chunk, err := s.store.Get(ctx, storage.Address(key)) - if err != nil { - return nil, err - } - return chunk.Data(), nil -} - -// SessionIndex returns current storage bin (po) index. -func (s *SwarmSyncerServer) SessionIndex() (uint64, error) { - return s.store.BinIndex(s.po), nil -} - -// GetBatch retrieves the next batch of hashes from the dbstore -func (s *SwarmSyncerServer) SetNextBatch(from, to uint64) ([]byte, uint64, uint64, *HandoverProof, error) { - var batch []byte - i := 0 - - var ticker *time.Ticker - defer func() { - if ticker != nil { - ticker.Stop() - } - }() - var wait bool - for { - if wait { - if ticker == nil { - ticker = time.NewTicker(1000 * time.Millisecond) - } - select { - case <-ticker.C: - case <-s.quit: - return nil, 0, 0, nil, nil - } - } - - metrics.GetOrRegisterCounter("syncer.setnextbatch.iterator", nil).Inc(1) - err := s.store.Iterator(from, to, s.po, func(key storage.Address, idx uint64) bool { - batch = append(batch, key[:]...) - i++ - to = idx - return i < BatchSize - }) - if err != nil { - return nil, 0, 0, nil, err - } - if len(batch) > 0 { - break - } - wait = true - } - - log.Trace("Swarm syncer offer batch", "po", s.po, "len", i, "from", from, "to", to, "current store count", s.store.BinIndex(s.po)) - return batch, from, to, nil, nil -} - -// SwarmSyncerClient -type SwarmSyncerClient struct { - store storage.SyncChunkStore - peer *Peer - stream Stream -} - -// NewSwarmSyncerClient is a contructor for provable data exchange syncer -func NewSwarmSyncerClient(p *Peer, store storage.SyncChunkStore, stream Stream) (*SwarmSyncerClient, error) { - return &SwarmSyncerClient{ - store: store, - peer: p, - stream: stream, - }, nil -} - -// // NewIncomingProvableSwarmSyncer is a contructor for provable data exchange syncer -// func NewIncomingProvableSwarmSyncer(po int, priority int, index uint64, sessionAt uint64, intervals []uint64, sessionRoot storage.Address, chunker *storage.PyramidChunker, store storage.ChunkStore, p Peer) *SwarmSyncerClient { -// retrieveC := make(storage.Chunk, chunksCap) -// RunChunkRequestor(p, retrieveC) -// storeC := make(storage.Chunk, chunksCap) -// RunChunkStorer(store, storeC) -// s := &SwarmSyncerClient{ -// po: po, -// priority: priority, -// sessionAt: sessionAt, -// start: index, -// end: index, -// nextC: make(chan struct{}, 1), -// intervals: intervals, -// sessionRoot: sessionRoot, -// sessionReader: chunker.Join(sessionRoot, retrieveC), -// retrieveC: retrieveC, -// storeC: storeC, -// } -// return s -// } - -// // StartSyncing is called on the Peer to start the syncing process -// // the idea is that it is called only after kademlia is close to healthy -// func StartSyncing(s *Streamer, peerId enode.ID, po uint8, nn bool) { -// lastPO := po -// if nn { -// lastPO = maxPO -// } -// -// for i := po; i <= lastPO; i++ { -// s.Subscribe(peerId, "SYNC", newSyncLabel("LIVE", po), 0, 0, High, true) -// s.Subscribe(peerId, "SYNC", newSyncLabel("HISTORY", po), 0, 0, Mid, false) -// } -// } - -// RegisterSwarmSyncerClient registers the client constructor function for -// to handle incoming sync streams -func RegisterSwarmSyncerClient(streamer *Registry, store storage.SyncChunkStore) { - streamer.RegisterClientFunc("SYNC", func(p *Peer, t string, live bool) (Client, error) { - return NewSwarmSyncerClient(p, store, NewStream("SYNC", t, live)) - }) -} - -// NeedData -func (s *SwarmSyncerClient) NeedData(ctx context.Context, key []byte) (wait func(context.Context) error) { - return s.store.FetchFunc(ctx, key) -} - -// BatchDone -func (s *SwarmSyncerClient) BatchDone(stream Stream, from uint64, hashes []byte, root []byte) func() (*TakeoverProof, error) { - // TODO: reenable this with putter/getter refactored code - // if s.chunker != nil { - // return func() (*TakeoverProof, error) { return s.TakeoverProof(stream, from, hashes, root) } - // } - return nil -} - -func (s *SwarmSyncerClient) Close() {} - -// base for parsing and formating sync bin key -// it must be 2 <= base <= 36 -const syncBinKeyBase = 36 - -// FormatSyncBinKey returns a string representation of -// Kademlia bin number to be used as key for SYNC stream. -func FormatSyncBinKey(bin uint8) string { - return strconv.FormatUint(uint64(bin), syncBinKeyBase) -} - -// ParseSyncBinKey parses the string representation -// and returns the Kademlia bin number. -func ParseSyncBinKey(s string) (uint8, error) { - bin, err := strconv.ParseUint(s, syncBinKeyBase, 8) - if err != nil { - return 0, err - } - return uint8(bin), nil -} diff --git a/swarm/network/stream/syncer_test.go b/swarm/network/stream/syncer_test.go deleted file mode 100644 index ad409116c1cf..000000000000 --- a/swarm/network/stream/syncer_test.go +++ /dev/null @@ -1,359 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package stream - -import ( - "context" - "errors" - "fmt" - "io/ioutil" - "math" - "os" - "sync" - "testing" - "time" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/network" - "github.com/ubiq/go-ubiq/swarm/network/simulation" - "github.com/ubiq/go-ubiq/swarm/state" - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/ubiq/go-ubiq/swarm/storage/mock" - "github.com/ubiq/go-ubiq/swarm/testutil" -) - -const dataChunkCount = 200 - -func TestSyncerSimulation(t *testing.T) { - testSyncBetweenNodes(t, 2, dataChunkCount, true, 1) - // This test uses much more memory when running with - // race detector. Allow it to finish successfully by - // reducing its scope, and still check for data races - // with the smallest number of nodes. - if !raceTest { - testSyncBetweenNodes(t, 4, dataChunkCount, true, 1) - testSyncBetweenNodes(t, 8, dataChunkCount, true, 1) - testSyncBetweenNodes(t, 16, dataChunkCount, true, 1) - } -} - -func createMockStore(globalStore mock.GlobalStorer, id enode.ID, addr *network.BzzAddr) (lstore storage.ChunkStore, datadir string, err error) { - address := common.BytesToAddress(id.Bytes()) - mockStore := globalStore.NewNodeStore(address) - params := storage.NewDefaultLocalStoreParams() - - datadir, err = ioutil.TempDir("", "localMockStore-"+id.TerminalString()) - if err != nil { - return nil, "", err - } - params.Init(datadir) - params.BaseKey = addr.Over() - lstore, err = storage.NewLocalStore(params, mockStore) - if err != nil { - return nil, "", err - } - return lstore, datadir, nil -} - -func testSyncBetweenNodes(t *testing.T, nodes, chunkCount int, skipCheck bool, po uint8) { - - sim := simulation.New(map[string]simulation.ServiceFunc{ - "streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) { - addr := network.NewAddr(ctx.Config.Node()) - //hack to put addresses in same space - addr.OAddr[0] = byte(0) - - netStore, delivery, clean, err := newNetStoreAndDeliveryWithBzzAddr(ctx, bucket, addr) - if err != nil { - return nil, nil, err - } - - var dir string - var store *state.DBStore - if raceTest { - // Use on-disk DBStore to reduce memory consumption in race tests. - dir, err = ioutil.TempDir("", "swarm-stream-") - if err != nil { - return nil, nil, err - } - store, err = state.NewDBStore(dir) - if err != nil { - return nil, nil, err - } - } else { - store = state.NewInmemoryStore() - } - - r := NewRegistry(addr.ID(), delivery, netStore, store, &RegistryOptions{ - Retrieval: RetrievalDisabled, - Syncing: SyncingAutoSubscribe, - SkipCheck: skipCheck, - }, nil) - - cleanup = func() { - r.Close() - clean() - if dir != "" { - os.RemoveAll(dir) - } - } - - return r, cleanup, nil - }, - }) - defer sim.Close() - - // create context for simulation run - timeout := 30 * time.Second - ctx, cancel := context.WithTimeout(context.Background(), timeout) - // defer cancel should come before defer simulation teardown - defer cancel() - - _, err := sim.AddNodesAndConnectChain(nodes) - if err != nil { - t.Fatal(err) - } - result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) (err error) { - nodeIDs := sim.UpNodeIDs() - - nodeIndex := make(map[enode.ID]int) - for i, id := range nodeIDs { - nodeIndex[id] = i - } - - disconnected := watchDisconnections(ctx, sim) - defer func() { - if err != nil && disconnected.bool() { - err = errors.New("disconnect events received") - } - }() - - // each node Subscribes to each other's swarmChunkServerStreamName - for j := 0; j < nodes-1; j++ { - id := nodeIDs[j] - client, err := sim.Net.GetNode(id).Client() - if err != nil { - return fmt.Errorf("node %s client: %v", id, err) - } - sid := nodeIDs[j+1] - client.CallContext(ctx, nil, "stream_subscribeStream", sid, NewStream("SYNC", FormatSyncBinKey(1), false), NewRange(0, 0), Top) - if err != nil { - return err - } - if j > 0 || nodes == 2 { - item, ok := sim.NodeItem(nodeIDs[j], bucketKeyFileStore) - if !ok { - return fmt.Errorf("No filestore") - } - fileStore := item.(*storage.FileStore) - size := chunkCount * chunkSize - _, wait, err := fileStore.Store(ctx, testutil.RandomReader(j, size), int64(size), false) - if err != nil { - return fmt.Errorf("fileStore.Store: %v", err) - } - wait(ctx) - } - } - // here we distribute chunks of a random file into stores 1...nodes - if _, err := sim.WaitTillHealthy(ctx); err != nil { - return err - } - - // collect hashes in po 1 bin for each node - hashes := make([][]storage.Address, nodes) - totalHashes := 0 - hashCounts := make([]int, nodes) - for i := nodes - 1; i >= 0; i-- { - if i < nodes-1 { - hashCounts[i] = hashCounts[i+1] - } - item, ok := sim.NodeItem(nodeIDs[i], bucketKeyDB) - if !ok { - return fmt.Errorf("No DB") - } - netStore := item.(*storage.NetStore) - netStore.Iterator(0, math.MaxUint64, po, func(addr storage.Address, index uint64) bool { - hashes[i] = append(hashes[i], addr) - totalHashes++ - hashCounts[i]++ - return true - }) - } - var total, found int - for _, node := range nodeIDs { - i := nodeIndex[node] - - for j := i; j < nodes; j++ { - total += len(hashes[j]) - for _, key := range hashes[j] { - item, ok := sim.NodeItem(nodeIDs[j], bucketKeyDB) - if !ok { - return fmt.Errorf("No DB") - } - db := item.(*storage.NetStore) - _, err := db.Get(ctx, key) - if err == nil { - found++ - } - } - } - log.Debug("sync check", "node", node, "index", i, "bin", po, "found", found, "total", total) - } - if total == found && total > 0 { - return nil - } - return fmt.Errorf("Total not equallying found: total is %d", total) - }) - - if result.Error != nil { - t.Fatal(result.Error) - } -} - -//TestSameVersionID just checks that if the version is not changed, -//then streamer peers see each other -func TestSameVersionID(t *testing.T) { - //test version ID - v := uint(1) - sim := simulation.New(map[string]simulation.ServiceFunc{ - "streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) { - addr, netStore, delivery, clean, err := newNetStoreAndDelivery(ctx, bucket) - if err != nil { - return nil, nil, err - } - - r := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), &RegistryOptions{ - Retrieval: RetrievalDisabled, - Syncing: SyncingAutoSubscribe, - }, nil) - bucket.Store(bucketKeyRegistry, r) - - //assign to each node the same version ID - r.spec.Version = v - - cleanup = func() { - r.Close() - clean() - } - - return r, cleanup, nil - }, - }) - defer sim.Close() - - //connect just two nodes - log.Info("Adding nodes to simulation") - _, err := sim.AddNodesAndConnectChain(2) - if err != nil { - t.Fatal(err) - } - - log.Info("Starting simulation") - ctx := context.Background() - //make sure they have time to connect - time.Sleep(200 * time.Millisecond) - result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error { - //get the pivot node's filestore - nodes := sim.UpNodeIDs() - - item, ok := sim.NodeItem(nodes[0], bucketKeyRegistry) - if !ok { - return fmt.Errorf("No filestore") - } - registry := item.(*Registry) - - //the peers should connect, thus getting the peer should not return nil - if registry.getPeer(nodes[1]) == nil { - return errors.New("Expected the peer to not be nil, but it is") - } - return nil - }) - if result.Error != nil { - t.Fatal(result.Error) - } - log.Info("Simulation ended") -} - -//TestDifferentVersionID proves that if the streamer protocol version doesn't match, -//then the peers are not connected at streamer level -func TestDifferentVersionID(t *testing.T) { - //create a variable to hold the version ID - v := uint(0) - sim := simulation.New(map[string]simulation.ServiceFunc{ - "streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) { - addr, netStore, delivery, clean, err := newNetStoreAndDelivery(ctx, bucket) - if err != nil { - return nil, nil, err - } - - r := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), &RegistryOptions{ - Retrieval: RetrievalDisabled, - Syncing: SyncingAutoSubscribe, - }, nil) - bucket.Store(bucketKeyRegistry, r) - - //increase the version ID for each node - v++ - r.spec.Version = v - - cleanup = func() { - r.Close() - clean() - } - - return r, cleanup, nil - }, - }) - defer sim.Close() - - //connect the nodes - log.Info("Adding nodes to simulation") - _, err := sim.AddNodesAndConnectChain(2) - if err != nil { - t.Fatal(err) - } - - log.Info("Starting simulation") - ctx := context.Background() - //make sure they have time to connect - time.Sleep(200 * time.Millisecond) - result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error { - //get the pivot node's filestore - nodes := sim.UpNodeIDs() - - item, ok := sim.NodeItem(nodes[0], bucketKeyRegistry) - if !ok { - return fmt.Errorf("No filestore") - } - registry := item.(*Registry) - - //getting the other peer should fail due to the different version numbers - if registry.getPeer(nodes[1]) != nil { - return errors.New("Expected the peer to be nil, but it is not") - } - return nil - }) - if result.Error != nil { - t.Fatal(result.Error) - } - log.Info("Simulation ended") - -} diff --git a/swarm/network/stream/testing/snapshot_128.json b/swarm/network/stream/testing/snapshot_128.json deleted file mode 100644 index 9b5eb501d3ac..000000000000 --- a/swarm/network/stream/testing/snapshot_128.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes":[{"node":{"config":{"id":"18f6cc40773a42ddad3e3e6c5fd865a652cbb42e9054f0e46dfaed17a41d4f4f","private_key":"73015943fd2c673001da6bf6658a12a22e057fc545ac0ebc78421f90f1370093","name":"node_a9e0b763852706722dc904b494293f9399c0fa32255890aa720285b8160335bb618f36b68a81b875a805384179f600defef474d486b4ea04b003ef6477ab7907","services":["streamer"],"enable_msg_events":true,"port":63042},"up":true}},{"node":{"config":{"id":"f07c1ee0f9cadf2971a0cb0022d1c67fa82c79f63cb98769b31ccfb04d05c0a8","private_key":"89cbfe6d806f2aeaee6a59667df3c3059ff7531bb33d64661586b004fcb6b831","name":"node_87e696a354d249493217dc4e0190082f30e09616873803efa376871d4b34f86f0eeb4643d4822d8a0fbcdedb27cd6ba5438e98943d358d960c4835e82261c93e","services":["streamer"],"enable_msg_events":true,"port":63043},"up":true}},{"node":{"config":{"id":"53eaa90913134a5bdaa780d18bcd33e85b1932d9b15bcc1096fc4658ea5ea7f8","private_key":"ff2ac479a33dc7fff5f87e4bb3078dfbcbb1567b76e35792faf104a383ebf896","name":"node_18bb6572965f4263c5a4d59a73027abc57a46122125ee58d871e95c993e6a1e8230438ce696a5f8880a08749837268b54319f7b0aa254c1a5bd453a8e9bcf84f","services":["streamer"],"enable_msg_events":true,"port":63044},"up":true}},{"node":{"config":{"id":"314e1e8a8d9ae18d0b5510341a6fab0f8654a7263a5db895480aca606b6a9859","private_key":"4800e21ac6431c61873444c525e207b48bb7a09ba2793b482ba6cf8cce81e353","name":"node_3103510e00a3f49a5e715719049fc8c9c67a2373a548f326458aeb6d9c75ed92b94373638fd075def0209113b4e85d972c23f064539f7b041596184e40d7f9a2","services":["streamer"],"enable_msg_events":true,"port":63045},"up":true}},{"node":{"config":{"id":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","private_key":"7e26b011ae2eabac951145e7840169b1f279577c06c40b4ba3a62da3ddb58de5","name":"node_077d2d032874e5ce70e9b928b7fe72c0326ba92394e16245e31d48b5731d3d32bfd86acf40825decff54bcd735e9ebfd94eba677c418ea7007baec9db4af676d","services":["streamer"],"enable_msg_events":true,"port":63046},"up":true}},{"node":{"config":{"id":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","private_key":"1fbf6b44eeb20ef012046cf8b7d3400ef3e586586aaf1cf6a2e5115ff5e3d868","name":"node_d90a81a583c82d626b92f27244f027da4a0ae76e6d3bdc1de0af7be01798f1fb04b34ce60c6d8651a39d7a70915438a4aa63e5adf844a9f7e38dbf0b1dba754d","services":["streamer"],"enable_msg_events":true,"port":63047},"up":true}},{"node":{"config":{"id":"3f1ee2912d5446e4cdf7a42582f2e30a24bcddd6422a9b6a61f287da256d0bde","private_key":"f32eafbb366e4b7655d302a06aac2e62ff8f4b9c07bb18175e58e534193b8554","name":"node_31ac37862416c0e229c4a088ec179f23bdd1bf12dd464eb11c630ad531d7c3438671862e5458e2f6fbb32b857f857e6b8c17e5d93eb29a0e78bb5a65d7eb648c","services":["streamer"],"enable_msg_events":true,"port":63048},"up":true}},{"node":{"config":{"id":"8bf59dc9ce97e6e2f2be364380aca38f98ddec248e730422f581f5bb56171e34","private_key":"6a809d9de0380db0b8bae8769cf41f9b05576137d0e2eefa69b7ddd921c6ac77","name":"node_1e3c83cabbe6852c987ae521b7fcb33185cf855c59b6235ebb8e57a6f860ccc159ddc01b4a21d251c8faca4559ceb271e046a51493ba148c0d3aed97ad208969","services":["streamer"],"enable_msg_events":true,"port":63049},"up":true}},{"node":{"config":{"id":"87e0232acc7127ea47604a5bfe84d80e38292902502c2a714e0f96d8090eb4fe","private_key":"f23b80b698ec97210ddaa65807a07cee7b411018ddd96c9d700e92a83120cf9e","name":"node_2fdb4382c4bc2950948a8cff13a7df65627bc0b20661cae20fd29acab166c97701594ee3151d75c006ba86d1d68b624b1d1f78e3d1e2fc297844956ef82208a8","services":["streamer"],"enable_msg_events":true,"port":63050},"up":true}},{"node":{"config":{"id":"4ae614490d8739205773d16c8027c46b67d93bd336c33071d8707fea6ac1acf3","private_key":"d2d74e454118a6e150810c74080ee7707b92b4575e7fe13c8887caf521cc734d","name":"node_87fce129511a1be2777052d24b606acdfe7067f4e874ab04674a68664b378ea208975f7269a72af889d3d23cd930b6a181afa2cdef3f9f9491f715bd96ad8dc0","services":["streamer"],"enable_msg_events":true,"port":63051},"up":true}},{"node":{"config":{"id":"004710b2e2194a8cd11292efe8b6f81bec4c1b673a9c8a45b7b5391f5f049895","private_key":"cdc72a1d2e475117e77abccdee1816e4d84fb059d712b717e8bd063239b6fd58","name":"node_c773af3af01ee8ab9fa8d06987baf4f10c394fdd386d69b7a7362f4b68fd6fc082337d3b33a19d584c5801a3e9198225d7b61f6629e34ce823be70908339f4c7","services":["streamer"],"enable_msg_events":true,"port":63052},"up":true}},{"node":{"config":{"id":"a2504bc38c94b7912bfafc42c7a4531cd27775473071e7a26173c4db932bf9ec","private_key":"b1b2452fe8ea070ff3b181fdc538144e1231f0c6f467713712662375dc6c4bb1","name":"node_56b25623ac935f3a8aac1e2603a6bd15ace1e5714671954b47f2cab960cf47922828f415105602408d0a732a893ebeea6f9afab7f889bb235c81589548d09391","services":["streamer"],"enable_msg_events":true,"port":63053},"up":true}},{"node":{"config":{"id":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","private_key":"e87535b0ec914ff93ea21f722eff61dd6cfea4f5542f68aab0af93c58e2afc25","name":"node_09b60de1e85bb6f7d2caf5b1ab58204d7d04531ece300dcd7bcc9157b8b3ef2a182758808a0eec6056034f29f52caadb7c690f498c1c8832ff7a6a9ecff308bd","services":["streamer"],"enable_msg_events":true,"port":63054},"up":true}},{"node":{"config":{"id":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","private_key":"3ae9a961f597c04b695a6d25fd0e6e47b131854f55f89d8ac25cce7411aa4107","name":"node_f2ea93f43be9d0c3fa21f1496dc13c778977a6afdcba24c8b146de7dca2cdde62a5b792aab969e5b4b6c56f63066b336580d911f206049cc24cac32a25fc4306","services":["streamer"],"enable_msg_events":true,"port":63055},"up":true}},{"node":{"config":{"id":"500f2135db1adafe22d8a62af63f1659d6f4538f490ee8151e9db970fa24389b","private_key":"1676fda16b41e3ec275f0d30ad691055248be71252ad15422b9c0260671aaf4c","name":"node_872cdbb6d74fb55fd2d51877ef7804bdd2eeb6de0297eb2ce18b67e52379b147d54a46d2385ec9293eb21736bed4191d92c5c75e8e81fc5a6c691970e019f570","services":["streamer"],"enable_msg_events":true,"port":63056},"up":true}},{"node":{"config":{"id":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","private_key":"6c989b24f2387e5a639effc8cd15b6d60c587fd14615496c9463d1f1a7ff6ad5","name":"node_da3e0fd71eb96ba2cab7f920d32f5425d1aad41d00765fdffb0b215d9dd5b60e2bc5929eafebee5b2b5a11aec164e141beff19d828aac7d1fabd3ffb0bfb8450","services":["streamer"],"enable_msg_events":true,"port":63057},"up":true}},{"node":{"config":{"id":"e0ac6b34d04bc036026767288a8f0f96720f4a3efa5b52b3b86439fba562bcf4","private_key":"a1befe78e67ca8b4972ba564c3bd03ad2ca6b996ded22166468d7a268a4c77d3","name":"node_489660042a8867d90a16ebb013968db26ca3edfc70f53320f511e35b3703eed09fbd787be5c06726a570a54aa15d129cd10db741523adf297929f909be4a0c71","services":["streamer"],"enable_msg_events":true,"port":63058},"up":true}},{"node":{"config":{"id":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","private_key":"6b9ad7d1da45cff60c3bdcac68f0af30e0a6e0e30e4ad73731c00368e9b0254a","name":"node_4448a59bde9edb93edcdb4a77f3e2277b9c01ffea45496ee0533eb5192955a08a0f982e25cf772e0dae68735a55b7acd221f6ba7b134f1e999087bee182330e8","services":["streamer"],"enable_msg_events":true,"port":63059},"up":true}},{"node":{"config":{"id":"fa62b685ae8a9aac669d245102ce68b030f0f270d95d7c88969bfdc9978f9070","private_key":"157e46312708757a331443dd95e1a0c012502430f4a8f8756f0aeaf35bde1f6d","name":"node_73319a301ad3cf0ec09549d817c9523d61b74abb0cad87b737d41d900321e52722a84355f6f87bc7a5f848818c89a021bb0f3e5994f67c9a7b5bbfc98188a376","services":["streamer"],"enable_msg_events":true,"port":63060},"up":true}},{"node":{"config":{"id":"2aef8c4da4e38b6a885785e406912cef6087344b4be9e135e2c597468b003315","private_key":"11148e1d9812b7bb8870b7960332ba4b32ea6aa43a57f9a27c30c2fafb609570","name":"node_caad8529d498a4a1e1ba7573689a913500bd409345ef8e3656abca748269eb73b919282f7f2eb0087f81f7bc52c367657ac8be0cdac65d2490c7c50c874444b7","services":["streamer"],"enable_msg_events":true,"port":63061},"up":true}},{"node":{"config":{"id":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","private_key":"896c3ee65d71ddcbf6030b920c0bf7748971170583e45fa2e33c19b3222e3945","name":"node_44462055ba68fdef337dd19d8123aace9a12284c13bc97687416b6b4ca0c94234ba7db6823651fc021d6ac1539e0c5321e763a7a12c9e7c8a583aac5369817d8","services":["streamer"],"enable_msg_events":true,"port":63062},"up":true}},{"node":{"config":{"id":"7692399f01177f2a8976d4c393a6813c53820e5f4edf54934414d1bcdfb8392c","private_key":"043c2adc5bdb3449b2f770e1207eac21128e77e89c9e1fe8876cecf1792f8b24","name":"node_ecbdca037cd7892752345b48b4219478b1b334f83ce7140fcc72eb71784436b690ce7a41b03e013cefc19d64a34a20cfef1b9e2b535d938bcdcb39fe63645a42","services":["streamer"],"enable_msg_events":true,"port":63063},"up":true}},{"node":{"config":{"id":"f3a1438cfc8b09a3a9c1fc8455ead9041e9532ef3e9a77ffee8e5ef62af2670c","private_key":"88f2cc06ba260e7c09cdd93e48c55c000d7a988ef65ccfc5331d1eac3c66d7b1","name":"node_51ba4faf0988717a37dcddc0a60a70ead33bde310184fc450f9ca73c67f931e6767ad5930bcf409e5aeb613a9ff7a03e47de6fc13b33d8af0b87b38822ae6888","services":["streamer"],"enable_msg_events":true,"port":63064},"up":true}},{"node":{"config":{"id":"40871cbc7eff896f15e558e2c638ac980fd89c718881cd110a086c3397b564c0","private_key":"8c64fc376a830b9237a1d1609a24e18396eb82cd6cd64b8ff572c9f946aaab2c","name":"node_750ca601f07d65f426f6ea5f34e06238f9d7a931f022f9b0ebc811943d3725500cb3c6f00c8d05eb8d5b353c6534136dff38b9a8d3d4dc5bf49cd96875704d07","services":["streamer"],"enable_msg_events":true,"port":63065},"up":true}},{"node":{"config":{"id":"57dfed651ba7f0ab6f9e1d5d2d4ae18fee51a200f50925a88c861982bb4769cb","private_key":"545e42fc4dfd38b62f1481fe46895a3cb9c6632930c8df8358d66a3988e6fe72","name":"node_41994605708232b4ca7448c3bc0760a7b86bf26d442091e5ce6e92eac94925721d7e0eca04bdd03bb1bba1ef92deeccd4bf1b7c6c3318b7e8d031965c6646761","services":["streamer"],"enable_msg_events":true,"port":63066},"up":true}},{"node":{"config":{"id":"d80b59b187b7765439b0e4c940f766ae71a903f599ac41a84d972e8703f7c9ff","private_key":"3e5b24d432307724a3e619c479ad5c87a93c6ee96d2b6ab0dad17da7e84eea55","name":"node_28afc20d8f4779b285bb14870062dbb54796ec77623302e51bc1bafed9e2c35751c8469ffc482718e059875dfa2226195ed10999e251498cba3a444896cb67db","services":["streamer"],"enable_msg_events":true,"port":63067},"up":true}},{"node":{"config":{"id":"ca9721b51497c5d570738eadc37c79fc8d5525749defb98c252371793c2a6f5c","private_key":"cab0eaf666548a84a7ceb4a34a29b7079c66b0df29e7fd315e851e02a8c9a5ed","name":"node_805d784527be4e32e84ddf045baee1ccf348cdf8288de3aae1a5379f762576b957525ef358d9b42c68a93394017880adc09bb2b1b5e01102dc7e4240baf2af95","services":["streamer"],"enable_msg_events":true,"port":63068},"up":true}},{"node":{"config":{"id":"e5cd769dbcc429587e3a92b192575965c0ac18e88c9a54b9f75936d929c43c62","private_key":"8c48ba58bc5ac8c0f3f0295c73df1572e80465f15a97f1c5537735474b11da89","name":"node_9c6dcfef0e128dbfeca58b8ac625b08fb447b0d579bd3f18bc0e2be60d1ec2274595d0554ddba0ca7eb660099d3ea146d8076792b46c93841d2ecf8cf608f5ba","services":["streamer"],"enable_msg_events":true,"port":63069},"up":true}},{"node":{"config":{"id":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","private_key":"0939af4a1ac3398bd818e5ac35e1003530a80a0abba5bf4c586664ab0b15a391","name":"node_6ce3a68cb1e2924ad97f22006094c709a3dd8b4ca1546fbb037f841a9e5ac62def242015dbf6221bda610153db064edcbb58a78a35a06077b8c02bf5b2a33c04","services":["streamer"],"enable_msg_events":true,"port":63070},"up":true}},{"node":{"config":{"id":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","private_key":"bfdc13ed4844405928ae3f67e209353779af203143a9f43fd8d1fc899ddd56a7","name":"node_178d5ce398a7114a63a0c953a59932e769891420f6b1544f08c082cd37b531b66757652d279b3036b03b04f8d46eceb0b46fb95646c6668e2921af75c06c3d97","services":["streamer"],"enable_msg_events":true,"port":63071},"up":true}},{"node":{"config":{"id":"d87f983aefd8f732c29718a95f4dfc54a10640d5c9fdd73b772c3c30df269d88","private_key":"d52f5bca67f434e20d72348971b791cb18def6182b002a3342c721ed06e9ad84","name":"node_59730132a01ba848a3c050bb6234bca9a72deba33716960fc3ec89b186bfcd313b9bbf049939d5805ff98db2c53a9421ce6ec97d8b4cdbaea53ba264d80d0734","services":["streamer"],"enable_msg_events":true,"port":63072},"up":true}},{"node":{"config":{"id":"a12e73ee9cf7369825c1423608ce079cf70df75d0f0bbc56d07d484169fca26d","private_key":"7d5751c36a856dbc2403a058a432e1f2bd142ae438444db9febdaa22480dd404","name":"node_93987e431a0058f2e942ccf8d3486d249cd2734d6494131b343e2c3a8fdd86cfcb12d0aaaf8fcb911ad3cdd682cc82118198195a7fdc915ab7853223f012eab6","services":["streamer"],"enable_msg_events":true,"port":63073},"up":true}},{"node":{"config":{"id":"3d3acee3657d98168e2f4e6463947646560b5ad1ca24b4a8a97e4284c39b02e1","private_key":"5cb8cc7f27d0f0e28e9ca55b592a38839058155cfce8528b5a464f98025eb54d","name":"node_665c3288c14dc1c17d85d17d634910f42183482c7e77c47e68f9b4f475b93e8c152246b9e34781606315ff6ef0f8360342500d15e4a2d67d9df6b72f30af64a7","services":["streamer"],"enable_msg_events":true,"port":63074},"up":true}},{"node":{"config":{"id":"d192c64e09879b5107507e193fea23b0244a574e0d66e444f7a325de32c123fd","private_key":"3901f39cd02354a635723259be3a5e7c28de3f7406c889fc9353d3adb22b9d82","name":"node_562119edffe8270f6f7a5ad9b13d4c65d643e52a2331d1fee16f7e9b5567f44cfea62df3e8965a22cf08db8fa918f0a0bcae8da2936677e6d25bc88ed85ed2f1","services":["streamer"],"enable_msg_events":true,"port":63075},"up":true}},{"node":{"config":{"id":"f836b3ca82f75bc0901d83b2c02e24174bee78c5eab9b55af9c492406942637a","private_key":"4e740dfca715720a19f56d32d6b9783810e1d6da09425e01dfbe3b55714416c0","name":"node_2502fc8ee0ccda79ad1dbd9c7cec625da85980b9116bdd56dc367d508039e25e5f65183293006e5b0df72fa9037a48bd2b133a757940d3587ff77ae2392e3eaf","services":["streamer"],"enable_msg_events":true,"port":63076},"up":true}},{"node":{"config":{"id":"1943c1442da29872e77e4cfd282e9a81490c613e99cbe4503e470b7f23ce75fb","private_key":"765219c4fea7ac80a0c5d26a096829226933310ad22d889ee19eb23915363fda","name":"node_e9f7e58fda4b504275441d51929fbc98214abdc9ed552c7aa94c600a85d4e791d60c032304b29ae028adccec94984fc9a3d705a81462632f50ef45024eb0f64e","services":["streamer"],"enable_msg_events":true,"port":63077},"up":true}},{"node":{"config":{"id":"954a22c6fd7d63fce47d8685d32bb892bcc0feab4209d5dc7c3601efc28e7d6d","private_key":"edf1f1608ee4b7c320472a071f2d60d53c7b74e58fba190b5353e92056f30751","name":"node_9e6c1c6e871638182c4b54ac89352ef5c2bca0a99424a1369013e7c182883da0e8d7ab96b3c8254c31fa315b941d8ddee153157626821fc78c2cae951c1c9053","services":["streamer"],"enable_msg_events":true,"port":63078},"up":true}},{"node":{"config":{"id":"5d6da3260504f8063af24960ac901bdc0c54a126b82d415656a083c496c8b6b8","private_key":"16e405179555f907062a85c408713f0fa46a5f1f6714c99272bb705ae226b2a5","name":"node_bfef26733f5196a11484bcc28d88776e747189ae7cec883ff39a27cfeee6d9d1efb34560c9f8e75eec43fc069a2ce5f0c78107a36cc8a569d37bd5306aecf23a","services":["streamer"],"enable_msg_events":true,"port":63079},"up":true}},{"node":{"config":{"id":"8e317152297d5d048ca4512f733e63e48c3dc66910bad541d33f3cfc76828cb9","private_key":"8337241aad3fa93ccc78bab1ff15a2aa218fbd7d026bffe74b7dadd8e6aa787f","name":"node_f6a07a1d361a4671e997d5eaadae61736291aff3896cb69f06bbc19bf7536dd152e0b15422b2fd9f8a9d2f9a251c9a07d0140319900e2cc9f25a59025c7dc91f","services":["streamer"],"enable_msg_events":true,"port":63080},"up":true}},{"node":{"config":{"id":"d79838c419be793292a24feaf1b9d26c67f0c5afe15bae85689420de3436ad95","private_key":"af0eb33b99b22cff74cc8137b4bb9477f8f47c895a61807279436daefaf9cac8","name":"node_08900197e74e285a5a8a9cc53fbd420bb35043ab27eb2d9eab22615e736a093abfa235c17f667dcc791cb50b9022082eafd9fba030194cc84f0358b769adc85b","services":["streamer"],"enable_msg_events":true,"port":63081},"up":true}},{"node":{"config":{"id":"7c766b57273777cc67f74870560449c9542cfec734b58b167aef99e09aeae962","private_key":"3c9a377e5ae9212adee118e04b3cd9cead5b4830ee51492dffbec8f015b5b757","name":"node_afb5754b4748b7ac5628e32b242c90aab0e2fc7da58d8d5c8c775d13d8a6fd32240f1b89021587858168cbe7b3ea7ad07807728655eae0a9907060494a7c99ca","services":["streamer"],"enable_msg_events":true,"port":63082},"up":true}},{"node":{"config":{"id":"2a2bcb40404a427a50538ad9d1bfff1c5b1a7673990cbd20d830787fd96a00e5","private_key":"5ccc64d0c951c9f50f0f7053504f54f965e22a22fb06b0bd14f206d72d822fa6","name":"node_8f625a4e4f4fd980c796d3aa535e58a39722492480cf6982d43fab02de63a5b4c1b5c7cd8402171dd792bb5d9c3e2bdd38176c061dbe3e5b0592af1339e3fd82","services":["streamer"],"enable_msg_events":true,"port":63083},"up":true}},{"node":{"config":{"id":"30a0ef4bf1f66ab4d02a027a24ba1f9b288ea93e86a5ceab64e1985aa4efcbd0","private_key":"f44a9e23990fd65de4da61711cb5a720a4474421ea0a653c7ce5ed76149b335d","name":"node_ab3814579882e9fd8d464f4f3c8a421be37822ba7f42c5f5e787e81125ec032f9ccf90a138c0b57f0a813b55b583f80d66284f795e2c82d80d2869e1fc770be5","services":["streamer"],"enable_msg_events":true,"port":63084},"up":true}},{"node":{"config":{"id":"61207f4fc13c899b88e9b727eb202a8bab00868bd07d945100b2e56dbcd67683","private_key":"4fd7ee5ba8003bb9b914612e6283cf5738550412c71e4475e07eb8bef32e96a4","name":"node_670949178a5fad22da03af1e206c814a797c0e9eb4e3371f83612f121e5b56e767706a3ae36628cd0c86dd7bd1586ca4df57e2de27b28a31284ecf9176d330e5","services":["streamer"],"enable_msg_events":true,"port":63085},"up":true}},{"node":{"config":{"id":"f99503dc6a4ace3317320d8454b3ac1a5977f508ae810c4d506001ed0126838b","private_key":"d0df7b482eb60b2f1ed4c2562768f60fe5ee8997e542bc9c7310db55391553d4","name":"node_fd12c5c96df6ee76dd7beb9e4e4768dda4d2c498851b6435f13ca0175980d0e4a4ff1c002e4daf41a995f118500604764f88496fb9b2c22e28308d8649b525ce","services":["streamer"],"enable_msg_events":true,"port":63086},"up":true}},{"node":{"config":{"id":"ac23190ee9ee30dd793bf3edef355450522d3f56973ab6b1acbb6dfd9d64137a","private_key":"7771ce12670dba4c28198f6df284ad58c9ca877a25ceb912ec3af5ac83f6e143","name":"node_ed1ab28230ed533abf25633508d54f32bbff78a10c7a15fad5c2320cda9d9669c6d0e15689d8885400e64cbcd81730456f8f4bd5c98681d2cd8a8d4e1daec553","services":["streamer"],"enable_msg_events":true,"port":63087},"up":true}},{"node":{"config":{"id":"e3c3f763b77a0bb72932d5d3dfca016e28d27b1c377690d2b074881b1a2a3259","private_key":"97cc28baaa8c94c905d348295eb19ef607599d22742989105d03eff39a5d6d51","name":"node_a05a18161c2d01a0f122548c66509dd1fcf3ee52975035bc79fb059e9613b743a16cd6f5ac54090e68f51bcf76ff21fb3805ba3197a96b4236afb80f791df802","services":["streamer"],"enable_msg_events":true,"port":63088},"up":true}},{"node":{"config":{"id":"feb3455336b26365b81dfc8cba2a390690ce996e07fcb7f1226720d263205adf","private_key":"9f191333ec4b20a2380317b819e71ffc234e63e85ec57548c407e1740c07f41f","name":"node_c89dbada7195464e732671ac6fff014cacfb4c879b63b6b84e7c1bce367522f759bd06e504b15f43a1735c6322356747fa5c4951882d4fd6cdf6f7cf13726719","services":["streamer"],"enable_msg_events":true,"port":63089},"up":true}},{"node":{"config":{"id":"4309c2d963d91e65f7e8e8ab42df52e75f8d377d4eb07d1a048c0082af4f501e","private_key":"9b597a8aa9ae03a20ec963fd3eeaca13823d7efd28a70c4c44ebfa6e147bb24a","name":"node_51302ef7b50922398ad802e917390bb4a3c24c877f2c2bcb7fcb34de9feca22673a2c594639914fe46a28837ffadfd03bb673afbc856aff5e59caf8e76082482","services":["streamer"],"enable_msg_events":true,"port":63090},"up":true}},{"node":{"config":{"id":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","private_key":"342dbb181da6045ae41eacef680b93f58765c7c5d65713f25f8b0627863c7983","name":"node_a40391285d1a97f1fb368b20c8e4d02be1bdebc0db41f00f99aac2f01893dc12256cd5a711117a981fbb3f9dfea18c19cf3603e925dbd67c4032b22b41eab8d5","services":["streamer"],"enable_msg_events":true,"port":63091},"up":true}},{"node":{"config":{"id":"6e8315dc552c8112dcb01e9302b5e47fef7fbdc81fbe7a1d0b27e0cf80a65ca4","private_key":"af173c26c979843230716c1a7f4aade4f9a19a23e2ee665d0a0ecae0f793188e","name":"node_e0420ba2a293315d810928a0e09a507c6aaf93977ba2c7598e9b83723b4a66682398ea17542c26767c7ff0f4ca09c537d3cc10dad283f079f1de73e25e87cb5c","services":["streamer"],"enable_msg_events":true,"port":63092},"up":true}},{"node":{"config":{"id":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","private_key":"4598804bbccfa26a362afa9773f283bfbc0ab7660240791e38f7ba858e45280d","name":"node_33e3ab7108ced43102003c3b3192b194100f32b6bcb67bb772ea9696e35721196699cf01e443f7cdf8076ad83aaf7468b75c71d03efb95f4c07cf0742ea9af81","services":["streamer"],"enable_msg_events":true,"port":63093},"up":true}},{"node":{"config":{"id":"4124e83515480818c250a3ca2d8a7b159c890983c35165ee531d2e15066c7203","private_key":"253935f834190e90b6bf0646618d2992233bf386ea31a58770e1926cb063050c","name":"node_cbed12dcab0aa04a96ec7e25bc2ee03b337c7b2006391baa7d2aff042c17ec70b82c5fb2dc916d085a7948541719d329f9b528b67ec6adb1ac2cc594d4ef1e42","services":["streamer"],"enable_msg_events":true,"port":63094},"up":true}},{"node":{"config":{"id":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","private_key":"f6d280904f13397798c441bffafc78ab98461c9d84f01f4b9b73de50d5595fd0","name":"node_03d2d77c008b5fa1063b1abc3152b879a529fe4fdb2dc174d6d85312264a38ee4cc49b342507a10fff1a4b0730f1ef8e008b0897d97bfd6f70050adb124d3596","services":["streamer"],"enable_msg_events":true,"port":63095},"up":true}},{"node":{"config":{"id":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","private_key":"5237d9097b72efee4e45c4d3d14e320e49ded470809478f3ccf6a9a7cf732d74","name":"node_f0299035cbddfdf7a78e5e3a400aaedf2d719d04e907ac0f9f067525e2f9bb913d985308a3a0d05467a64adf58c68a615c6327acf716c23631d0e829903f8b34","services":["streamer"],"enable_msg_events":true,"port":63096},"up":true}},{"node":{"config":{"id":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","private_key":"a8d169922df4dcd07e1f102e3eae5692eb87b1f368124cb5c65fcca22f9743aa","name":"node_276256790c9317d09ff7802f4ad0a85840fe62527390ce1790e1e3186e8b3f04acfaa41dcd02303d333423678a4037e4f4854676e79ced3232a3dbd772cc2680","services":["streamer"],"enable_msg_events":true,"port":63097},"up":true}},{"node":{"config":{"id":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","private_key":"8b927156a8aa7b6cc711c9c2ce3016cab2e9d1ce220e9792207cc5b40fca3047","name":"node_155805f787600f9f9518cf8836f491885b64868bfe0023975bcd12776925a424fb5aa3199dc178e83294e97f347a373d05d2422578a08eeeb2d15a178d28964f","services":["streamer"],"enable_msg_events":true,"port":63098},"up":true}},{"node":{"config":{"id":"96b723de0840f656c914674ad20204da830c04d4d6956c32e20154cbcc40f9ef","private_key":"d2b8d940f626faf3204dd38721f5528e1c9db4b5d0cc28d0d627c7d191c1f21f","name":"node_4de606aa7e722197b918c5f7f0db86a3081d48e89d21428b04f19c3ff3755eac0bddbff5fad8bda94b9e57f58fba2fecdbabbf710f7afb381bf4f1a4fe55cd93","services":["streamer"],"enable_msg_events":true,"port":63099},"up":true}},{"node":{"config":{"id":"aec520f507eedd5d752b6ba8a0b509f14105e5e457eef0b6537015e39569762b","private_key":"1cd1a1e084096aa4f8f9933b0871657638dad24b3f47d18c9eb0007595ce46f1","name":"node_82a774be7146766585d2bec6b69b7803aa956f492a53d8ed5f071becdbbc617562885d8430f0afd505210aab7b032428fbea32c82dffbd12d9ccba776ef4729b","services":["streamer"],"enable_msg_events":true,"port":63100},"up":true}},{"node":{"config":{"id":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","private_key":"8ec23f2c16d923996d6bd74aa374cb6e5a69fab748ea8efa538d01a12ac62e16","name":"node_340420ee18d55913f790d0fcc2305b40b1e7bef2eddb79dda57801690aeeead693ebd1a9ff8557671f5ae136b3e65733306924bd00444cbc6e7c6235e5a2c77f","services":["streamer"],"enable_msg_events":true,"port":63101},"up":true}},{"node":{"config":{"id":"931a36980ca0788616cb1c9851f6069e44d94671a2a056e12a7d7cafe597dee5","private_key":"b7a9a55daea83c1d769fe68e820de46f4a8df4c425ea7ab98a44cb7ad3c7963e","name":"node_7a509686ebce91778fe22e834a94ab03f92457d41385099ba657fe62c7469a9669bddb9a3d7b0150efa1d2f40c69bc786c7f4ede1cf8b19411eea1d23cb7d56c","services":["streamer"],"enable_msg_events":true,"port":63102},"up":true}},{"node":{"config":{"id":"db596f4ff3f8c25835f9f10176e87a8823eaf1d39a513882f1ceab931dabfed9","private_key":"c1478fa3ebc5b2bd2a32567455e71e73a1787b1c8c6571fd94ee0d487d5fea4d","name":"node_816e91fa8ff68ba067a89390ef61e7f23211e5e05049daa103ad1ff84b94fbcf535a6f6b515fb571939f5656869767c608513808800baba7e5d2b5f3a17a9691","services":["streamer"],"enable_msg_events":true,"port":63103},"up":true}},{"node":{"config":{"id":"11a60d43d627470df1c1ebdc404093f6ae83c75b3a95469316b89a0185eeaa86","private_key":"bb118c1ce93cddc4fb540863d575d6ae584ebc2f2e2f221c9622cc105e7fd7b9","name":"node_77327983685f39f006806170fe351063a58ff1bd8dedc222d538e86cfd18abdfefd548328f25fc5afde8170aa5c35311353019468f2b439c331837ddfbb25a52","services":["streamer"],"enable_msg_events":true,"port":63104},"up":true}},{"node":{"config":{"id":"6cf1d30b063cf8352795a92c6af26c154fc44be06c69ea9fd4d9f6dcc626eda1","private_key":"7cbe501dc7bdd1bafc62c5c56af215559b06aef4ed398d4a3acdb78f3c84a735","name":"node_7a18392b8338108a996196ee190a93a1b9954c8e05a062c421da513a1828dfa739e7b224878c0670cf74bbc743c7ca7ee8cfee6b6a602fe1f4a82ecfbd38c2f4","services":["streamer"],"enable_msg_events":true,"port":63105},"up":true}},{"node":{"config":{"id":"7f620d306e05bac0f07838b7de6a2db69aa6494df3db09f26624ed167318a75a","private_key":"e853260ddb20771587ed33007ef55b07368a08f79ea0a64cf9830ab69498238f","name":"node_e7bb63c5187ee85d965fed5cb33d1678c0e090b4e4c3f3d859755565db18046cb60025453bdebf136373c416a2e6e56be063bca6c9257b7b175dcf966e274205","services":["streamer"],"enable_msg_events":true,"port":63106},"up":true}},{"node":{"config":{"id":"b9e5006b90bffae809136ad57e36b784b18e35f7491c47319807f6869d624465","private_key":"23c3dd23f790b16480c58305ab528346a7046dddd1d9f5c699a4963bfb926fb3","name":"node_545850cb90787d49c579b1ed54a753ebd00eaafe3f1bd04d57266e4912fb1cdd654446ef054a973a583ce8dfc6cc130b6f90e63bcf75372fc21c445f8e1e6005","services":["streamer"],"enable_msg_events":true,"port":63107},"up":true}},{"node":{"config":{"id":"c7fd88864731e6b79acf436a76908b91c61ba07680699c2cda316179c3719e8a","private_key":"e0f2a273dc23035aaf88bb97d7f5ccf57ee6304b184e7d61183556b28040076b","name":"node_86b84ddf64d301f6a9c4504c59eb4031d3167fb74101abea3b694845a009dc522be7b2e2720097ceea9f36058e160f42a2a438a33034929a5c1a7793c7ccef7b","services":["streamer"],"enable_msg_events":true,"port":63108},"up":true}},{"node":{"config":{"id":"7e65067fa5db52d40c0a162432200ca6d0d3917b25cad15ce498fa6b5375bc8d","private_key":"dacac1dfd252a4ad8878c023bd34aa1bc81b63d66d70c8ba9aafcfa8425bb253","name":"node_bf1e6eeb8b229f63b49213db499b71dcfe0ae45ee3f14685c7cbfa3f0a2150e52a89c853f4cd8c7a92e6e5f6083044efddfa17391662e3cff6290da679520404","services":["streamer"],"enable_msg_events":true,"port":63109},"up":true}},{"node":{"config":{"id":"1ea89c3f15b82385c5df8810ec7cbd30e8e4a4e05c1ddf5dbe7102edaa07afd2","private_key":"57a2dcb70aa306ae204744fce4dd4b4ad9b02516e080dca195604406e240627c","name":"node_a58a2dd3f5ee2471f0ddd555ffcc45d86e2d8c325585e3fe55eee878b8a626faccce73679d32811fc5d068c153e671673f4cd020f3c7fb37bc6fd9646931646a","services":["streamer"],"enable_msg_events":true,"port":63110},"up":true}},{"node":{"config":{"id":"d3864c160e6d8fcb3f540b864e6ee11d91ca708f2cbe1e5fd04beb2b6c1d8930","private_key":"4314dbd43bea3cdbe75a24ef256ee0dca9008f5133f8b1d6c9adf9a84210c2e1","name":"node_896c74ca4cc4cbb9d2b6606d0ebfd6427952c2e16aedbf36933fa3d01f1c505fcd663f3d01c0cf096bef9cfd0bae4595ec7c221cfc362e19a5b7c60614a9658b","services":["streamer"],"enable_msg_events":true,"port":63111},"up":true}},{"node":{"config":{"id":"311fc84bce90b1bc1d1983c78e45b1e82a703cdb7cfbe54d51e9bc40cfe04103","private_key":"2b4bd5ee9da5de0932418460699900ba0e40139dc910b087c586210e52816b77","name":"node_1e0a46ff50fb6d9a2c218a851763ff86c42e4d61dddffb7188481febc0f3180bc8f31973d336b2a6e25802acd4fed6d905d89c6d4d7d8080fb12741f9c5ab7e6","services":["streamer"],"enable_msg_events":true,"port":63112},"up":true}},{"node":{"config":{"id":"3e4fbab650cbd539383f7e26128c0aaa770f12c4a1fa44d553de9dda073f0cb2","private_key":"dd2b5c2e466c7276ff4c6c1a641ac204a34ddcb145523ab175701ba31faf44de","name":"node_b2bea653b6ec9629c6f934be72fccf30acf836698e21e81dd8085f070ba8259ba43eee43c342738a4b782f5fbddd44caa28f56dffc08237ca7567c965ac3beca","services":["streamer"],"enable_msg_events":true,"port":63113},"up":true}},{"node":{"config":{"id":"6c1f82c3a7bc3274cfdac710caec4fc814c9f5a6797674457414dd94be577a9b","private_key":"17f5b5341d575c1cfe673efd9957ca29a50fa23c028c5f5f7561b23e7dc92e04","name":"node_5771bef7f50439f9b81d2a7bf7060b1ad4b38675d8f6abbac3cc4c215fb0cb5dd07f6873545b42218337556925566025476e2915b7b98e662a3dcd28bebf0eb4","services":["streamer"],"enable_msg_events":true,"port":63114},"up":true}},{"node":{"config":{"id":"8c5b95bf1743e2d8a97210c5b64b6732f3615c4369fa3a52e426f93e168ae16f","private_key":"eeb9367b7cabbd3e4076edb5fb97cc2fba7445cf37f523685f2b24e617d718c3","name":"node_0e8c1a6b70524c4fdc492c74348bac4ccd5d140bd41352607e8cfba45561afb1e6e12f3e2fcf03c1778c9ae5ae19cb9218ee2d613983768f54e3f54e69bb6600","services":["streamer"],"enable_msg_events":true,"port":63115},"up":true}},{"node":{"config":{"id":"d382e36db7f13180cca7b169e8511c9b920bb481a340d8a61dd9e4969a5605b6","private_key":"a9a6414dc37916ed5794a644a51d6cc1c23c27d707b2dfb9957f9ee28af64e60","name":"node_d71c50805f284ed3a759e2e81f220fbc73d6d0a7a261b4c9e7878c3da143cfc07afa83e1635b6a45530f71a60b9f17c485a2fd6617c6c2bff82bacc71c208087","services":["streamer"],"enable_msg_events":true,"port":63116},"up":true}},{"node":{"config":{"id":"f80e1ce23869c7da4453f06521fa320632057194c9f9b3dcdd777cc1f06603eb","private_key":"29c714e8c1983b179cad17ffaa617cfbdd50272b496803b4a602afdb05d78117","name":"node_06472c89def07fa73188d253cf1acddc2be984842f3a234edb3db95449ba74928ab1395eca1b8978987769863afffb488fc27c9ac723aa24837b66c12f38d735","services":["streamer"],"enable_msg_events":true,"port":63117},"up":true}},{"node":{"config":{"id":"6632a8b23d42dfc55a103d625a3644f0067abaae554eb75a78b1094ad7b24c16","private_key":"f00cf99cd4b64e4242b7c878a4ce70044f1fe91966dd723b940e3a312f1b9f63","name":"node_eb097186ff58d96a7ead7a7f9c05a44075d84537bd4fd3f82857bf2c45bee1c6dece434a7707cde69e96f02366859cab4c991fdb7615e113a868d4b9c7524a45","services":["streamer"],"enable_msg_events":true,"port":63118},"up":true}},{"node":{"config":{"id":"a60b45dbab9c1de18783fed9eed8af6dabafbc33fdc5b3fef1ef4569bc6c70d9","private_key":"d089b29e9ea18a2cf17256e0e06c907164f712564fbebce476b40f67e39bcc73","name":"node_57bfbde13c71e035c96513870aa8215198f78806e7cdb01c54fbdb392de2c40386d768d6fdc4c68534a67e531867ca74d8ca4dcf34ff7f7f64ec35edee3e5f68","services":["streamer"],"enable_msg_events":true,"port":63119},"up":true}},{"node":{"config":{"id":"059a2d0e5db2c0204c10b66ef04bacc96d64453073efeee81ba7e30d43061a61","private_key":"219f34180d582584fdc64b9b7712bb15e490bcb2eaa2f4d2847f838196c6afe0","name":"node_5b4de68680c5d1a75cccd5e7a82319c031f5d61d79cbb6532e1254ab81c833c3fafb54390cca7a770e84690c490fcba90482b35321870f8506335a2f57cc052e","services":["streamer"],"enable_msg_events":true,"port":63120},"up":true}},{"node":{"config":{"id":"e39e8092c97b53dbeaf5ca73d95c71e8e41a2906aaa377fe0c1b23752c8c7423","private_key":"223faa782246247751eeb3191f8823874455005721e6738d5f5b5b631cda858b","name":"node_cd0e9a45b45f9a67417fcde29d9f92c45ca4db46eebbfe47dcf6999e23e549e4205f42ada8e3fcd00e2fb5f832bb92a27a59b43c4a5909148d81399c2e8ce492","services":["streamer"],"enable_msg_events":true,"port":63121},"up":true}},{"node":{"config":{"id":"aa19fb3565a2677828b1384f483e3eb7ea112940d21d0aaa7b16139c753c69d7","private_key":"2e3b422e1762ce5c22bc9cd6fa78cfb62f3ada7732d7e0b16d91ecc5f5ab9047","name":"node_5d917ffc9d70a38670941ce206aa7ea1b9ea65c1d783f14ebdf7c7afa6ca8b237112aaa9b1a3f757132093a604ef378280c5bdef02c2688049a91a412c399bfe","services":["streamer"],"enable_msg_events":true,"port":63122},"up":true}},{"node":{"config":{"id":"6640a8343b810399525545ee1545a50e3c76548f776c83d55465071cb55a00b1","private_key":"a3f487830e9fd87b5b404bc91d0f49d866cddfa869d32c08853273b9a2eaea7b","name":"node_26cf4ea45b9a76daab82a57126f9c6f8726d2eb973e205ab4ab69c8b8be11a32a7eb5c3807e952cd428ce5ddd88f143d4fc9ff8c3de3d159855675afce715615","services":["streamer"],"enable_msg_events":true,"port":63123},"up":true}},{"node":{"config":{"id":"237b54aa7ef077972fc65fb222ce70df0129f8c8d4d35505fcbdc281bb7d28ce","private_key":"9a3fe8b50af54e2b6ea84f05b832426f911dd557e1e874bb291075981a46baa7","name":"node_20f171ab01a814a2856a6cbe7929269f18f6329914289515e2cb9d078ac14ebdae457789dd638c6415b062799114570f556147d4bf9ac850f00b6d0762765ac1","services":["streamer"],"enable_msg_events":true,"port":63124},"up":true}},{"node":{"config":{"id":"d55213d3dc27542a780472564a4a8c1ce47668e05710be178baf3238ca8886b6","private_key":"8e83e239128a2ef402e58ceaffa0fe2d59d913588d12536ba4e7a88bac14ab5a","name":"node_f0b2f1e8d46be656109adb18c60677ac9eb8fce7f42e15d9dbb25f94b4e426064780eaf32b79954b9bb72ea89953175da8de35380aaba18931cca374a7de2b11","services":["streamer"],"enable_msg_events":true,"port":63125},"up":true}},{"node":{"config":{"id":"1f2a162a4e78c8935ed17eee206a8512ef37e126bb7ad9ff744f7b46c4b19aee","private_key":"81b194e85dff3ff43272bbf73e58a6dac8a9e3ad125f7ba3e71cb5de5341a975","name":"node_a6ca1d5340f2d7021f541c81cf9aea519675462c8443bb6ddd93919962561b8f5a8431c3ec7e7e7d46c600b653e9a0638d2bb07cf4e29516bf9c4d3653f451b0","services":["streamer"],"enable_msg_events":true,"port":63126},"up":true}},{"node":{"config":{"id":"44540a152f9fc924f10fefc013059d9ad228e74f921a67866ef28758f1886320","private_key":"b4ec9b24f6a962422bb63d20e415a5dc2f85168297e8abed3cb20e3fc0d70e25","name":"node_d8cfadac10473b31a4560c711636a05615f13054388065344642ff1d04246fb62eafeec06805264eafead111ed8134e11a8e21f42a0eb950c23b142e627bd8cb","services":["streamer"],"enable_msg_events":true,"port":63127},"up":true}},{"node":{"config":{"id":"c35893ea35ab764005fd81ee10d1a9ab3f361e044aa53e28ee64effca928d09d","private_key":"cd0667c4d17b56321858cceaca611f1c46758fa276d3d991f3a9fbb0c686af3a","name":"node_49eb706cd7e95ad78502a508487d88b818861d94334aee36b2acef5c25cfff5c0efc2df9dc5dbb18acd4003d5cc0c843d3b40363adf2f62a14c04d814268fdee","services":["streamer"],"enable_msg_events":true,"port":63128},"up":true}},{"node":{"config":{"id":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","private_key":"4556340931a4e95daa2043706f516eba3744d2ba4340bcb95358b58c95534090","name":"node_c93cb2df7ba6de8009872f5f7565891040d42e3b193ef1cdb321a0c167cc8fb8138900982bb8f6918fbaefac6e02dda01ee40f7a6beba1990dd44abe03cc3d01","services":["streamer"],"enable_msg_events":true,"port":63129},"up":true}},{"node":{"config":{"id":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","private_key":"f1b36e9da20cfc2416f02f9194a0522b426be8679b6478118962624b4963af27","name":"node_3e9c0bbd146d8b1040b4f379eecf802c27c4d0a70e64a9fb2e941a7edab9b00396b0b67fc5c891652743cdba3b342973ed49615b32da54b925954a799240431c","services":["streamer"],"enable_msg_events":true,"port":63130},"up":true}},{"node":{"config":{"id":"06db6e244213745e02175cca1101e72799c09236838affd8a020a05e5b88e2bd","private_key":"7f720ab5ccb4e3dbca347ff7104c677d8215261910d8e8e2ade703020d566842","name":"node_b38213eaa5b419de787b70073ad8c7c819e48c5f76dec1507b54f1d7cb027211facbe7a170ac50212abe130935e8947d5a19d6b13790114e71cc18357902a889","services":["streamer"],"enable_msg_events":true,"port":63131},"up":true}},{"node":{"config":{"id":"6aef7e94602e6e74ab4cdb57668fb751fa20807751f786d0232870da0ae32cfd","private_key":"1a4d1e1d6e198ac61f91d9ccba4212fdd32e9075df2fda41caa11a0a25e694f0","name":"node_20bdc859d58d8bf419df64fd6ee1d4363c1d5f403af3abef2720d7d3933924672e12e23e5d76b28e0f6726acf58bdc5eb258cba635e327cbd0b564523305da75","services":["streamer"],"enable_msg_events":true,"port":63132},"up":true}},{"node":{"config":{"id":"60ad041deb8cf72f0d2b29accb204a7e1838347b250e105ac72a0a4f17dc5f47","private_key":"9fc5b25f84b416e8224c296213b062e1dec7eb7074402a494a23cc1c70ef394b","name":"node_c6d1404873174254c0f15f25804da9a9d90db9c11c5cc895fab613b4deb7820f1733680b4ba9e4b61a664642ed6ee9ff012e13a0619c64ae067be6bab26962c6","services":["streamer"],"enable_msg_events":true,"port":63133},"up":true}},{"node":{"config":{"id":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","private_key":"43a6ff2fe67bdd16cdd0a4e7ba3eb8188632a796b90d34b477fcb5e0f5ba0b43","name":"node_f04c3ae9fe957a14c249acf3ea2e8407d04d108fc01e75f6d52aaf5073b3450025012d138a75b857473eea4d20e57c99b92bff9041f269d995543d7c67a92ec6","services":["streamer"],"enable_msg_events":true,"port":63134},"up":true}},{"node":{"config":{"id":"83119770a801a40f0f4015ce8a8cc6cfa6dbc211deaee193c5db05130002f9a7","private_key":"9202da3ce5dfea8df87f9f6145867414b45131634f70cd32dffffeb8975e0e86","name":"node_8e8de10fb3f53bd3a48455ebfa0a38ea5bd28c607e65506b263ece53a24d2361c2af7d7cd207e45b11bf71a3c33fa325fc8fd40a18b9c73019cce219c757c7ef","services":["streamer"],"enable_msg_events":true,"port":63135},"up":true}},{"node":{"config":{"id":"265040d12d0b79c8609232060b3ddd6ac16fb895b52bb7f3fee5c6e8b63a4d79","private_key":"6f53e22eece8ebd80ce64720dacf85c36c8aa737fe54d05c884ae9ad693144fb","name":"node_cdd86dea7dde96ff7cc1e3248fd17d107c83f2cc7ce2111c41530448962763309e91a05b5dad4663d0e02db59ed4129ab0c3e1eedc42c654e39d29f99038063b","services":["streamer"],"enable_msg_events":true,"port":63136},"up":true}},{"node":{"config":{"id":"ea9490f17813e0e5f39e8065f4c3954163f3bca6e80afca7a2c76d5ca3116e53","private_key":"ba12a0d7056bd5d7f8b80a1e30c126d8641ec4a20b2e8c9b890764785350cb7f","name":"node_da15d60a4b9a8816ce24c20f6c941c51229c1fb5e070b8b29be2977c2f7b41f8f17e4b6efe75f465e7935ded1ba17472f046eac73393db7197018fa86d11c31f","services":["streamer"],"enable_msg_events":true,"port":63137},"up":true}},{"node":{"config":{"id":"6838b88ff71c5419d3fd22a4c007a7b822405b1842d53427a9c075d093c13047","private_key":"f68a07d7241789486ffebae385ee73f2b9050d22612e9770a1aad1870e347556","name":"node_03e478a3aae82e06e215e40272a420037a61442d11c49c367a5ee6a21868d29a17ea8b9284f013d020c4740f296a6a22bc64d33d1c2807f9ab8dc48c0cfec18a","services":["streamer"],"enable_msg_events":true,"port":63138},"up":true}},{"node":{"config":{"id":"1385e97b879da9021aba65b2df36b91650aafb354a09c5be8a9532d1eb7d472b","private_key":"f9fa950ad709b5c36e36f5f49a4260ab81e08145d8c10611a2d46e1395ac87f6","name":"node_3df030a522a157e36f6b369aab048b9287792743a411a47073c4f9ef7686528f39bf7bf91c48e4d46afe180c8d08430b012bd216d50876baa3b1e7bc17cb55ef","services":["streamer"],"enable_msg_events":true,"port":63139},"up":true}},{"node":{"config":{"id":"d68e095e77246c3c08df2eaec294965bca2d01fa2819d9cd06c6ef8730c59ab4","private_key":"27eb2337ccaec369a786c3e86e92bfc20b6bd03e70244b7d1266e783f087a16f","name":"node_4bd17847bdf60ea93a670a84a0ff8fe028d35228e814d6ebc0ae4fa586ddef03cd79390d541d28ca3c05a7241ffe92ac182e63c251176b40db8fe05fefaa82be","services":["streamer"],"enable_msg_events":true,"port":63140},"up":true}},{"node":{"config":{"id":"ebf9a6425b47a78df1c99fe4d62a48c26d2f4ef966a8592802d5b5e95e6c12eb","private_key":"dc23bae09e0ef5811f615cd80fa3c264a5011f501f3fa6f91cf6772a408ba5b3","name":"node_f8052e328014f39157036f3dcecdb23419c0959473a88282605f10add681ef5cbfb1e926b522f98b8401272113b677a67225874d1afb0bff4b11140cc071de44","services":["streamer"],"enable_msg_events":true,"port":63141},"up":true}},{"node":{"config":{"id":"609958921d9107eaae6f3141e94f12b3a28fec203eb0e5b674ed8268934becc7","private_key":"f37b323598ac406038a1fba2fb8acf1ac7899c6606f263470141e3d8f364c70a","name":"node_3274b5f48e6929e2305e980b558a4ca3c7cf800b75a6445ea2875cb379e127f3e793e9450a11e927682aa91b8af52e9560dece633833e0f207a8a8a38b7b9c54","services":["streamer"],"enable_msg_events":true,"port":63142},"up":true}},{"node":{"config":{"id":"d9169f80683a2552475d60e6982f70c6605f9b40300f2b60895aa21cdc42db89","private_key":"fa14be3b746acd18c44299337f26807c3f0034349cc7eb6ad398a32061e68e12","name":"node_e59940a6dfe35a6214e2e3daba9ad94e630004cd8f029e13a6649a56dc528ff94bc09d8d21a2f14a56b4ff759b60ebf3d27c1029862c183b8416fa3e950bdb55","services":["streamer"],"enable_msg_events":true,"port":63143},"up":true}},{"node":{"config":{"id":"bf7b0b9825b9c878f7730014c7458fc52ee3490c98ee5ae2f647a8d73e4464f3","private_key":"67c3621e70643b370db5377452ca3a8bfe78a01172983da0ba5fb979ce341bd1","name":"node_f7637cdd5ef26de40b9055c1df91a45725670c8df11ae934c0d99d2547c3eb4c42a16dbb2e75bcaf4b1b9347c48db65f549a0623179a08dd8cbad92f5bca08cf","services":["streamer"],"enable_msg_events":true,"port":63144},"up":true}},{"node":{"config":{"id":"fba89f80c35e5f6ffbe6e5521f2011caec985d820fe42759ef1aaa36d4902f37","private_key":"e4e774ffb3fee42b1fb038f560cbb7ef3f4ec4f12d077fd90c38de877841eddd","name":"node_fa897ba112f34839064c6871a4c9504c5d709eff5095a137c6a42726d58eb623af976cb96cc30db67e6ac9347c769f032aa4ebda884285a057059040c008ad3e","services":["streamer"],"enable_msg_events":true,"port":63145},"up":true}},{"node":{"config":{"id":"dc2ae81b708a14f8b6e134986075835994c71d284b9bd9fa19d2efa73346f35e","private_key":"9fdcb97101ee0351d72b1cb7b832fc95f339a434e9d1e146af4062d0d43a88c6","name":"node_5d96577324edf1b5a1626999925982af1e0ba7bed8edcc3f740ba434f4b003cbbe2d632cad327c76e5b490d08c091c4a7b473353ab59139493657eb9525b8be2","services":["streamer"],"enable_msg_events":true,"port":63146},"up":true}},{"node":{"config":{"id":"8fe212fc9af12821f59b5f364f86f78b545be501933b926fc47b6d7deafca941","private_key":"05ba41be88727fe8ceb3dd01ae6314f6eb66de9c223da829a6cb0fcb9633dbc7","name":"node_b09af9e5552e72f918174963dad58a4492d3afa120f78e43820df7bff7fae9f6b52bc6c8c73d3a9af91d20134f4ff1b037db2ef0bc3d8c495a771d63de678bc0","services":["streamer"],"enable_msg_events":true,"port":63147},"up":true}},{"node":{"config":{"id":"7628bcf64532ca3ec293d26f60791a2a62542b2f5f3f1c782799075383ccde00","private_key":"4f458de05e760aece8dffaa29333d3a51382cceb7bb8265be7a25ea65cfaf2d2","name":"node_1955bedf0bc9044b13a2c16158087123197c74147c86aa3b9389d308a364e80edbc573bcc836d8a262a77f3c9233ebddb1b82eca83cd0a0e4bda6564c443bb70","services":["streamer"],"enable_msg_events":true,"port":63148},"up":true}},{"node":{"config":{"id":"740bebf4546e2ed32b2bd277555685e3be071aca4b1990c7c095e12e24fa4699","private_key":"c2adb7237525d3d280b964361432cfe1d2863e3011b4b83167648da04a8b22fb","name":"node_5984a296b49bfba30315501ce2ae88ed0392ab2959ac4e7e6b9a29090f344b9dd13873ca137e8317c402cd2e14a61965d5707065b8ded46c41a054731933719f","services":["streamer"],"enable_msg_events":true,"port":63149},"up":true}},{"node":{"config":{"id":"981be0d21bdb11ac807dc29937c6a3ac2ae3c2f45269c0edc21172d5550f4da9","private_key":"43223de0a2e5b61580a85a150f19528acd484002efde9df5041f8b93b5d9f0e9","name":"node_9c3be147f9fe0fa34e553d9e4332969086ea7fa65294b61ca35c9f731f6b81d0b70cdcfe2ba52b6cea3c0de14b7ea40031b5cc40661c4bb821147894130d7bf5","services":["streamer"],"enable_msg_events":true,"port":63150},"up":true}},{"node":{"config":{"id":"b1bf7a0572c1efcf034bf42f54f7334743c59f77b67225aa3c7d93552074ab56","private_key":"863d1cfaeee0df7e89ff906a00c0165a9e579fda9f82bc3fd9b694a593e37139","name":"node_ce1309c8505b446363ae37cd3b1ee3e03ced537bed20aca5d8c4be2133917c408845ef5d0f65876974be2803dfb824827cf5c5a2d050d6ef26cdabcbf3a2dc31","services":["streamer"],"enable_msg_events":true,"port":63151},"up":true}},{"node":{"config":{"id":"e4254cda903275296f17b47e5dcabbf16d1fa7432b64e0e6e0562fcd89de534d","private_key":"f8f2976509e8c27362fc450e79db0e7b99ee036277d5a1c860f265be28b6525c","name":"node_3e7820bb15c07f515bd63791b66136723dcc9878b3145fe0f238f6b41e3f2f7565ae55ef24f08ae461950ce1ae0c8a80fe5e4fd4f2f88c4acf4a2c94640df239","services":["streamer"],"enable_msg_events":true,"port":63152},"up":true}},{"node":{"config":{"id":"2dc2914f76ec9b291b2085bea6d2e0f6d45beb55bba5e3a90059b96e83010318","private_key":"eac78f03be8f9529f9c85dceadffbc6ff39de01456bab5c864f9ea5a4198ee23","name":"node_dafb58b2fc8a14f2b23b7df33d28aa7847a08f80e9c21a654d4c97d928e1afe6585644d27f22442cf70d229c32783be6a03c0920be153fc4d9f3b273dfa90ec3","services":["streamer"],"enable_msg_events":true,"port":63153},"up":true}},{"node":{"config":{"id":"83bc840da4e1f9201f0e85db2c44364f06c188fedef30fd2f72cbde70f6cfdb9","private_key":"6488421507de8fcdef9f2e89a73b5e9182ec8c0cb7d564e630dfe38ebcd732a1","name":"node_622646c74fdbae39ba191dbafff4906fb683fe0b0f2c303d080f5577a070876c41c8d3786ae7b953e5f682b8ec647727550d47302229ebb9f82bed24cea61132","services":["streamer"],"enable_msg_events":true,"port":63154},"up":true}},{"node":{"config":{"id":"7f5f9f955e368c906746d42953e30e781a8eb73428af5aa608af96af9bf4f60a","private_key":"7f547289aa8ba8dade535a2e50bfc2111536e7149162e360ea5e4db15c12d42a","name":"node_2d6d55edd34c0d9a0130b4806e409bbe18cdda5c2b221cf46136718ec20ffc9d8e92838d78aff92fbcc85f5d081da361dd1e3d7f3d4e1ea57877d6bb7aa0b6f7","services":["streamer"],"enable_msg_events":true,"port":63155},"up":true}},{"node":{"config":{"id":"d7f956d0f6445202699bf63700da408237fdcecd27e2e8ad969e51d7e8e61483","private_key":"f3d8d6aa0db8c604198cf9a317b45fb96b8407cb029951d7ef06042abedf5e1f","name":"node_f62bd19b0fd052207743ce53c3d48a3a71e9219b75e41a9497a43e6368e559d5487ff1ab644f2df4106b500583e4344515f73187eec773115deb977b4ebc3514","services":["streamer"],"enable_msg_events":true,"port":63156},"up":true}},{"node":{"config":{"id":"f29ff736fb944f8f8339a4e10f69ee72f108ed1d68b5a80d06a814b0807e8686","private_key":"cc5627eea1858ac168bbf1e34a3bb3410ade5b379cde7f67601bbe359506e76d","name":"node_a1b2d0c6f24f5924c61e057fc65b994d9a6755560d740018b4c9ad0bb69212ec69974e22cb037dfeb7ea90a4493997f4c94029870bcc5fd47013b51ca0a26b5d","services":["streamer"],"enable_msg_events":true,"port":63157},"up":true}},{"node":{"config":{"id":"b22078d9b5f3d57dec3fb1796599d5f04fcb32d4288939431ecea8516be76b58","private_key":"77d99b72e19bbe2da2d6d931653dc7b541dfe27092b944936562bafddd6114fa","name":"node_188bf77c9c1e45f009efe7aefdb040bdb47763980fe7eb0851295d657fa2a8978efbd2997c1dc30f4f0874eecf9ca9550487cf41da237b84d071963d35b6baff","services":["streamer"],"enable_msg_events":true,"port":63158},"up":true}},{"node":{"config":{"id":"9f9700ca395f36c84a38838c11f6889d00015d97aac7943c27cf74393e3fc22f","private_key":"e6874592c3f2d2fa94b126d565cec571767e64ef9067970a9f8d1f8ce7e88d0f","name":"node_f06ec3a90d34300f9fa2d48aea0c6b5f2f01f7833ffb0fad30e7f57e3916e344f3a4f9efe38e222443b8b00d3c7f5221c672491a129e4958e574afc283bd45b1","services":["streamer"],"enable_msg_events":true,"port":63159},"up":true}},{"node":{"config":{"id":"8ec663c22eff8ed185b8194c1240ff58df131c5f310df0a91b9790ec166e4346","private_key":"d733b22563798aae07facf0a489367c7cf0d6d3ebf786992b6ae98696d295cd2","name":"node_3c6faa2e75a1699ddadd0e21fd35d3d6215715cfdc2dc89961cfd66773d2541776e785f3ebc833a70e3d1017a4edc571a5d5d9f9fbfc3fa0a8588f6c5023c164","services":["streamer"],"enable_msg_events":true,"port":63160},"up":true}},{"node":{"config":{"id":"f2b809e97d3c3a652c4ddce19ca12b01c1de25f2ce007d561fd5606e85540d64","private_key":"145ffd39a00647c0142f3bce7ba2f21f4e168663488e0683b4c8cc72afa7b02b","name":"node_56e8f792ec9a75ec9e91d472b8a4b023655dd68c2a448a33397b125fe3584a5efa1b492e47077b24acfe0396b73e1cb564a6a6b2aee1b457781dfaf6d43fcaed","services":["streamer"],"enable_msg_events":true,"port":63161},"up":true}},{"node":{"config":{"id":"97a5959b95662f6e820a1b011a7e87568e0efd2257a73bf91868c98c46e5a571","private_key":"83fa451e08386b0966a5b8ec4b56c861bc367f0cbaa7e6e3a8c20564dbc0f1af","name":"node_3572a22097a313b3adef95a7b6cc679f8d1201a156d764e61a9fbc63d123fb4826f7125402fb6569beed359e1c1e5e6cb6993a75975da6cd4ce15669a2eea758","services":["streamer"],"enable_msg_events":true,"port":63162},"up":true}},{"node":{"config":{"id":"2feed66b03b7b057e1a64a3f877ad9c28ada96e30b98a78393b54ea31b98f4fa","private_key":"2057ae928cb89d7759bda21739956c8527bf11bb02571ee2e9bda07702779aae","name":"node_e3f2e777a96b2137b3104b84d5d827d484eac9ee1b585430e09f790d7e26978da12802325927c3c483bc19973e09cd3f70c513c34798e5da650f8d2f0c8c5c47","services":["streamer"],"enable_msg_events":true,"port":63163},"up":true}},{"node":{"config":{"id":"6ac71d55e984abe323c74ea85ac1aed1b06e56632c07c389e780268274a23810","private_key":"4fb682db9263cc8ae4d32757a0837296654120fc17593b9d0c3219a307193655","name":"node_1480f62d85ea32226d9f77ccd31780b3e704fb60618a588ae85dcd1c0e84e878c5fc092adfb306e8ebc7abba9ec429cb2447754502ef847cf931467f31fa50b2","services":["streamer"],"enable_msg_events":true,"port":63164},"up":true}},{"node":{"config":{"id":"92e2f8ec301e5858f7168b785040955d28bffae0c98e5d3f59ff9fa8636fe423","private_key":"7f212e2fa6d29b93f32d27a27b02eab5b9aa946ba044f1a20bada9f7db133907","name":"node_3540d888c3207d6df233afd1164ff9dde2551730862772547e04f7311de364968e113f38a7dea1ab0916a7f8307017de5b49578fa4ebcc39651e541fde51be48","services":["streamer"],"enable_msg_events":true,"port":63165},"up":true}},{"node":{"config":{"id":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","private_key":"d209b8a302d27579c28dd86104e2eab3c74101ff06bb17200388b614ec3f05ff","name":"node_077bcadade93e0d361e94f76dff464d61912bc067ce2ce17ebcabd757cca201cd64624ea809d99dd8ecb749d40528db9b3eb503ef5ec05e8845044cfaef720dc","services":["streamer"],"enable_msg_events":true,"port":63166},"up":true}},{"node":{"config":{"id":"cac97c7026184a2d6ba2b17fcd0c5440bec538961af836a60f2fadb78a310a77","private_key":"f0317274abc992d76728cf9a59b6dfd16b1422fea0c0089a47f0cbffbce53c34","name":"node_73be4d2291c68ca4554a86fa170f7595210e1b6bbbeef3c1d5623a2b5d03a8fd6e26caa26afc639c20c8a351a89dad1086f91734f09afab62d28ec17b700fa01","services":["streamer"],"enable_msg_events":true,"port":63167},"up":true}},{"node":{"config":{"id":"78bfaf122d10ee5e5304fdd247434c02b32de577bb410dc04d8ffe1906294fdb","private_key":"8d5bfe9aaf70d634b185efff991adfde6d80ee5d1c60e68eb1a105c17a4e03af","name":"node_a5cdff211813e17fadd43ec55a6cf4e97e6ca0c3b2cef0db58923b27d36207fd1a77146efb6093bd94d7eb89cc77d8c735fc64ab098839efc74a00b5e8687555","services":["streamer"],"enable_msg_events":true,"port":63168},"up":true}},{"node":{"config":{"id":"6a57cd355d641d1506957a988d93b2d3f9a613a7354c24130ad97dbc4c8296ce","private_key":"bd99f18955320e921a98f8103d8b4c1713d800d49c0a7bc946cb52fdf79e03d4","name":"node_27fb8fcda1986644f985d68430c399f0299644b00b234c355362721081d12f9eb7686eea8f92eabda1d342bf56255e51c07b200c6233f95ac009f15b874eee97","services":["streamer"],"enable_msg_events":true,"port":63169},"up":true}}],"conns":[{"one":"314e1e8a8d9ae18d0b5510341a6fab0f8654a7263a5db895480aca606b6a9859","other":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","up":true},{"one":"f3a1438cfc8b09a3a9c1fc8455ead9041e9532ef3e9a77ffee8e5ef62af2670c","other":"40871cbc7eff896f15e558e2c638ac980fd89c718881cd110a086c3397b564c0","up":true},{"one":"3f1ee2912d5446e4cdf7a42582f2e30a24bcddd6422a9b6a61f287da256d0bde","other":"8bf59dc9ce97e6e2f2be364380aca38f98ddec248e730422f581f5bb56171e34","up":true},{"one":"40871cbc7eff896f15e558e2c638ac980fd89c718881cd110a086c3397b564c0","other":"57dfed651ba7f0ab6f9e1d5d2d4ae18fee51a200f50925a88c861982bb4769cb","up":true},{"one":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","other":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","up":true},{"one":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","other":"3f1ee2912d5446e4cdf7a42582f2e30a24bcddd6422a9b6a61f287da256d0bde","up":true},{"one":"18f6cc40773a42ddad3e3e6c5fd865a652cbb42e9054f0e46dfaed17a41d4f4f","other":"f07c1ee0f9cadf2971a0cb0022d1c67fa82c79f63cb98769b31ccfb04d05c0a8","up":true},{"one":"87e0232acc7127ea47604a5bfe84d80e38292902502c2a714e0f96d8090eb4fe","other":"4ae614490d8739205773d16c8027c46b67d93bd336c33071d8707fea6ac1acf3","up":true},{"one":"004710b2e2194a8cd11292efe8b6f81bec4c1b673a9c8a45b7b5391f5f049895","other":"a2504bc38c94b7912bfafc42c7a4531cd27775473071e7a26173c4db932bf9ec","up":true},{"one":"f07c1ee0f9cadf2971a0cb0022d1c67fa82c79f63cb98769b31ccfb04d05c0a8","other":"53eaa90913134a5bdaa780d18bcd33e85b1932d9b15bcc1096fc4658ea5ea7f8","up":true},{"one":"4ae614490d8739205773d16c8027c46b67d93bd336c33071d8707fea6ac1acf3","other":"004710b2e2194a8cd11292efe8b6f81bec4c1b673a9c8a45b7b5391f5f049895","up":true},{"one":"8bf59dc9ce97e6e2f2be364380aca38f98ddec248e730422f581f5bb56171e34","other":"87e0232acc7127ea47604a5bfe84d80e38292902502c2a714e0f96d8090eb4fe","up":true},{"one":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","other":"e0ac6b34d04bc036026767288a8f0f96720f4a3efa5b52b3b86439fba562bcf4","up":true},{"one":"53eaa90913134a5bdaa780d18bcd33e85b1932d9b15bcc1096fc4658ea5ea7f8","other":"314e1e8a8d9ae18d0b5510341a6fab0f8654a7263a5db895480aca606b6a9859","up":true},{"one":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","other":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","up":true},{"one":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","other":"500f2135db1adafe22d8a62af63f1659d6f4538f490ee8151e9db970fa24389b","up":true},{"one":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","other":"fa62b685ae8a9aac669d245102ce68b030f0f270d95d7c88969bfdc9978f9070","up":true},{"one":"a2504bc38c94b7912bfafc42c7a4531cd27775473071e7a26173c4db932bf9ec","other":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","up":true},{"one":"500f2135db1adafe22d8a62af63f1659d6f4538f490ee8151e9db970fa24389b","other":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","up":true},{"one":"fa62b685ae8a9aac669d245102ce68b030f0f270d95d7c88969bfdc9978f9070","other":"2aef8c4da4e38b6a885785e406912cef6087344b4be9e135e2c597468b003315","up":true},{"one":"2aef8c4da4e38b6a885785e406912cef6087344b4be9e135e2c597468b003315","other":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","up":true},{"one":"e0ac6b34d04bc036026767288a8f0f96720f4a3efa5b52b3b86439fba562bcf4","other":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","up":true},{"one":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","other":"7692399f01177f2a8976d4c393a6813c53820e5f4edf54934414d1bcdfb8392c","up":true},{"one":"57dfed651ba7f0ab6f9e1d5d2d4ae18fee51a200f50925a88c861982bb4769cb","other":"d80b59b187b7765439b0e4c940f766ae71a903f599ac41a84d972e8703f7c9ff","up":true},{"one":"d80b59b187b7765439b0e4c940f766ae71a903f599ac41a84d972e8703f7c9ff","other":"ca9721b51497c5d570738eadc37c79fc8d5525749defb98c252371793c2a6f5c","up":true},{"one":"ca9721b51497c5d570738eadc37c79fc8d5525749defb98c252371793c2a6f5c","other":"e5cd769dbcc429587e3a92b192575965c0ac18e88c9a54b9f75936d929c43c62","up":true},{"one":"1943c1442da29872e77e4cfd282e9a81490c613e99cbe4503e470b7f23ce75fb","other":"954a22c6fd7d63fce47d8685d32bb892bcc0feab4209d5dc7c3601efc28e7d6d","up":true},{"one":"a12e73ee9cf7369825c1423608ce079cf70df75d0f0bbc56d07d484169fca26d","other":"3d3acee3657d98168e2f4e6463947646560b5ad1ca24b4a8a97e4284c39b02e1","up":true},{"one":"954a22c6fd7d63fce47d8685d32bb892bcc0feab4209d5dc7c3601efc28e7d6d","other":"5d6da3260504f8063af24960ac901bdc0c54a126b82d415656a083c496c8b6b8","up":true},{"one":"5d6da3260504f8063af24960ac901bdc0c54a126b82d415656a083c496c8b6b8","other":"8e317152297d5d048ca4512f733e63e48c3dc66910bad541d33f3cfc76828cb9","up":true},{"one":"3d3acee3657d98168e2f4e6463947646560b5ad1ca24b4a8a97e4284c39b02e1","other":"d192c64e09879b5107507e193fea23b0244a574e0d66e444f7a325de32c123fd","up":true},{"one":"6a57cd355d641d1506957a988d93b2d3f9a613a7354c24130ad97dbc4c8296ce","other":"18f6cc40773a42ddad3e3e6c5fd865a652cbb42e9054f0e46dfaed17a41d4f4f","up":true},{"one":"7692399f01177f2a8976d4c393a6813c53820e5f4edf54934414d1bcdfb8392c","other":"f3a1438cfc8b09a3a9c1fc8455ead9041e9532ef3e9a77ffee8e5ef62af2670c","up":true},{"one":"8e317152297d5d048ca4512f733e63e48c3dc66910bad541d33f3cfc76828cb9","other":"d79838c419be793292a24feaf1b9d26c67f0c5afe15bae85689420de3436ad95","up":true},{"one":"d192c64e09879b5107507e193fea23b0244a574e0d66e444f7a325de32c123fd","other":"f836b3ca82f75bc0901d83b2c02e24174bee78c5eab9b55af9c492406942637a","up":true},{"one":"d79838c419be793292a24feaf1b9d26c67f0c5afe15bae85689420de3436ad95","other":"7c766b57273777cc67f74870560449c9542cfec734b58b167aef99e09aeae962","up":true},{"one":"f836b3ca82f75bc0901d83b2c02e24174bee78c5eab9b55af9c492406942637a","other":"1943c1442da29872e77e4cfd282e9a81490c613e99cbe4503e470b7f23ce75fb","up":true},{"one":"7c766b57273777cc67f74870560449c9542cfec734b58b167aef99e09aeae962","other":"2a2bcb40404a427a50538ad9d1bfff1c5b1a7673990cbd20d830787fd96a00e5","up":true},{"one":"61207f4fc13c899b88e9b727eb202a8bab00868bd07d945100b2e56dbcd67683","other":"f99503dc6a4ace3317320d8454b3ac1a5977f508ae810c4d506001ed0126838b","up":true},{"one":"2a2bcb40404a427a50538ad9d1bfff1c5b1a7673990cbd20d830787fd96a00e5","other":"30a0ef4bf1f66ab4d02a027a24ba1f9b288ea93e86a5ceab64e1985aa4efcbd0","up":true},{"one":"30a0ef4bf1f66ab4d02a027a24ba1f9b288ea93e86a5ceab64e1985aa4efcbd0","other":"61207f4fc13c899b88e9b727eb202a8bab00868bd07d945100b2e56dbcd67683","up":true},{"one":"f99503dc6a4ace3317320d8454b3ac1a5977f508ae810c4d506001ed0126838b","other":"ac23190ee9ee30dd793bf3edef355450522d3f56973ab6b1acbb6dfd9d64137a","up":true},{"one":"e3c3f763b77a0bb72932d5d3dfca016e28d27b1c377690d2b074881b1a2a3259","other":"feb3455336b26365b81dfc8cba2a390690ce996e07fcb7f1226720d263205adf","up":true},{"one":"ac23190ee9ee30dd793bf3edef355450522d3f56973ab6b1acbb6dfd9d64137a","other":"e3c3f763b77a0bb72932d5d3dfca016e28d27b1c377690d2b074881b1a2a3259","up":true},{"one":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","other":"931a36980ca0788616cb1c9851f6069e44d94671a2a056e12a7d7cafe597dee5","up":true},{"one":"feb3455336b26365b81dfc8cba2a390690ce996e07fcb7f1226720d263205adf","other":"4309c2d963d91e65f7e8e8ab42df52e75f8d377d4eb07d1a048c0082af4f501e","up":true},{"one":"931a36980ca0788616cb1c9851f6069e44d94671a2a056e12a7d7cafe597dee5","other":"db596f4ff3f8c25835f9f10176e87a8823eaf1d39a513882f1ceab931dabfed9","up":true},{"one":"4309c2d963d91e65f7e8e8ab42df52e75f8d377d4eb07d1a048c0082af4f501e","other":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","up":true},{"one":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","other":"6e8315dc552c8112dcb01e9302b5e47fef7fbdc81fbe7a1d0b27e0cf80a65ca4","up":true},{"one":"db596f4ff3f8c25835f9f10176e87a8823eaf1d39a513882f1ceab931dabfed9","other":"11a60d43d627470df1c1ebdc404093f6ae83c75b3a95469316b89a0185eeaa86","up":true},{"one":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","other":"4124e83515480818c250a3ca2d8a7b159c890983c35165ee531d2e15066c7203","up":true},{"one":"6e8315dc552c8112dcb01e9302b5e47fef7fbdc81fbe7a1d0b27e0cf80a65ca4","other":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","up":true},{"one":"11a60d43d627470df1c1ebdc404093f6ae83c75b3a95469316b89a0185eeaa86","other":"6cf1d30b063cf8352795a92c6af26c154fc44be06c69ea9fd4d9f6dcc626eda1","up":true},{"one":"4124e83515480818c250a3ca2d8a7b159c890983c35165ee531d2e15066c7203","other":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","up":true},{"one":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","other":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","up":true},{"one":"6cf1d30b063cf8352795a92c6af26c154fc44be06c69ea9fd4d9f6dcc626eda1","other":"7f620d306e05bac0f07838b7de6a2db69aa6494df3db09f26624ed167318a75a","up":true},{"one":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","other":"96b723de0840f656c914674ad20204da830c04d4d6956c32e20154cbcc40f9ef","up":true},{"one":"e5cd769dbcc429587e3a92b192575965c0ac18e88c9a54b9f75936d929c43c62","other":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","up":true},{"one":"7f620d306e05bac0f07838b7de6a2db69aa6494df3db09f26624ed167318a75a","other":"b9e5006b90bffae809136ad57e36b784b18e35f7491c47319807f6869d624465","up":true},{"one":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","other":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","up":true},{"one":"96b723de0840f656c914674ad20204da830c04d4d6956c32e20154cbcc40f9ef","other":"aec520f507eedd5d752b6ba8a0b509f14105e5e457eef0b6537015e39569762b","up":true},{"one":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","other":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","up":true},{"one":"aec520f507eedd5d752b6ba8a0b509f14105e5e457eef0b6537015e39569762b","other":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","up":true},{"one":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","other":"d87f983aefd8f732c29718a95f4dfc54a10640d5c9fdd73b772c3c30df269d88","up":true},{"one":"d87f983aefd8f732c29718a95f4dfc54a10640d5c9fdd73b772c3c30df269d88","other":"a12e73ee9cf7369825c1423608ce079cf70df75d0f0bbc56d07d484169fca26d","up":true},{"one":"b9e5006b90bffae809136ad57e36b784b18e35f7491c47319807f6869d624465","other":"c7fd88864731e6b79acf436a76908b91c61ba07680699c2cda316179c3719e8a","up":true},{"one":"c7fd88864731e6b79acf436a76908b91c61ba07680699c2cda316179c3719e8a","other":"7e65067fa5db52d40c0a162432200ca6d0d3917b25cad15ce498fa6b5375bc8d","up":true},{"one":"7e65067fa5db52d40c0a162432200ca6d0d3917b25cad15ce498fa6b5375bc8d","other":"1ea89c3f15b82385c5df8810ec7cbd30e8e4a4e05c1ddf5dbe7102edaa07afd2","up":true},{"one":"1ea89c3f15b82385c5df8810ec7cbd30e8e4a4e05c1ddf5dbe7102edaa07afd2","other":"d3864c160e6d8fcb3f540b864e6ee11d91ca708f2cbe1e5fd04beb2b6c1d8930","up":true},{"one":"d3864c160e6d8fcb3f540b864e6ee11d91ca708f2cbe1e5fd04beb2b6c1d8930","other":"311fc84bce90b1bc1d1983c78e45b1e82a703cdb7cfbe54d51e9bc40cfe04103","up":true},{"one":"311fc84bce90b1bc1d1983c78e45b1e82a703cdb7cfbe54d51e9bc40cfe04103","other":"3e4fbab650cbd539383f7e26128c0aaa770f12c4a1fa44d553de9dda073f0cb2","up":true},{"one":"3e4fbab650cbd539383f7e26128c0aaa770f12c4a1fa44d553de9dda073f0cb2","other":"6c1f82c3a7bc3274cfdac710caec4fc814c9f5a6797674457414dd94be577a9b","up":true},{"one":"6c1f82c3a7bc3274cfdac710caec4fc814c9f5a6797674457414dd94be577a9b","other":"8c5b95bf1743e2d8a97210c5b64b6732f3615c4369fa3a52e426f93e168ae16f","up":true},{"one":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","other":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","up":true},{"one":"8c5b95bf1743e2d8a97210c5b64b6732f3615c4369fa3a52e426f93e168ae16f","other":"d382e36db7f13180cca7b169e8511c9b920bb481a340d8a61dd9e4969a5605b6","up":true},{"one":"e4254cda903275296f17b47e5dcabbf16d1fa7432b64e0e6e0562fcd89de534d","other":"2dc2914f76ec9b291b2085bea6d2e0f6d45beb55bba5e3a90059b96e83010318","up":true},{"one":"97a5959b95662f6e820a1b011a7e87568e0efd2257a73bf91868c98c46e5a571","other":"2feed66b03b7b057e1a64a3f877ad9c28ada96e30b98a78393b54ea31b98f4fa","up":true},{"one":"b1bf7a0572c1efcf034bf42f54f7334743c59f77b67225aa3c7d93552074ab56","other":"e4254cda903275296f17b47e5dcabbf16d1fa7432b64e0e6e0562fcd89de534d","up":true},{"one":"f2b809e97d3c3a652c4ddce19ca12b01c1de25f2ce007d561fd5606e85540d64","other":"97a5959b95662f6e820a1b011a7e87568e0efd2257a73bf91868c98c46e5a571","up":true},{"one":"dc2ae81b708a14f8b6e134986075835994c71d284b9bd9fa19d2efa73346f35e","other":"8fe212fc9af12821f59b5f364f86f78b545be501933b926fc47b6d7deafca941","up":true},{"one":"83bc840da4e1f9201f0e85db2c44364f06c188fedef30fd2f72cbde70f6cfdb9","other":"7f5f9f955e368c906746d42953e30e781a8eb73428af5aa608af96af9bf4f60a","up":true},{"one":"2dc2914f76ec9b291b2085bea6d2e0f6d45beb55bba5e3a90059b96e83010318","other":"83bc840da4e1f9201f0e85db2c44364f06c188fedef30fd2f72cbde70f6cfdb9","up":true},{"one":"7628bcf64532ca3ec293d26f60791a2a62542b2f5f3f1c782799075383ccde00","other":"740bebf4546e2ed32b2bd277555685e3be071aca4b1990c7c095e12e24fa4699","up":true},{"one":"8fe212fc9af12821f59b5f364f86f78b545be501933b926fc47b6d7deafca941","other":"7628bcf64532ca3ec293d26f60791a2a62542b2f5f3f1c782799075383ccde00","up":true},{"one":"059a2d0e5db2c0204c10b66ef04bacc96d64453073efeee81ba7e30d43061a61","other":"e39e8092c97b53dbeaf5ca73d95c71e8e41a2906aaa377fe0c1b23752c8c7423","up":true},{"one":"1f2a162a4e78c8935ed17eee206a8512ef37e126bb7ad9ff744f7b46c4b19aee","other":"44540a152f9fc924f10fefc013059d9ad228e74f921a67866ef28758f1886320","up":true},{"one":"6ac71d55e984abe323c74ea85ac1aed1b06e56632c07c389e780268274a23810","other":"92e2f8ec301e5858f7168b785040955d28bffae0c98e5d3f59ff9fa8636fe423","up":true},{"one":"6aef7e94602e6e74ab4cdb57668fb751fa20807751f786d0232870da0ae32cfd","other":"60ad041deb8cf72f0d2b29accb204a7e1838347b250e105ac72a0a4f17dc5f47","up":true},{"one":"06db6e244213745e02175cca1101e72799c09236838affd8a020a05e5b88e2bd","other":"6aef7e94602e6e74ab4cdb57668fb751fa20807751f786d0232870da0ae32cfd","up":true},{"one":"ebf9a6425b47a78df1c99fe4d62a48c26d2f4ef966a8592802d5b5e95e6c12eb","other":"609958921d9107eaae6f3141e94f12b3a28fec203eb0e5b674ed8268934becc7","up":true},{"one":"8ec663c22eff8ed185b8194c1240ff58df131c5f310df0a91b9790ec166e4346","other":"f2b809e97d3c3a652c4ddce19ca12b01c1de25f2ce007d561fd5606e85540d64","up":true},{"one":"609958921d9107eaae6f3141e94f12b3a28fec203eb0e5b674ed8268934becc7","other":"d9169f80683a2552475d60e6982f70c6605f9b40300f2b60895aa21cdc42db89","up":true},{"one":"265040d12d0b79c8609232060b3ddd6ac16fb895b52bb7f3fee5c6e8b63a4d79","other":"ea9490f17813e0e5f39e8065f4c3954163f3bca6e80afca7a2c76d5ca3116e53","up":true},{"one":"aa19fb3565a2677828b1384f483e3eb7ea112940d21d0aaa7b16139c753c69d7","other":"6640a8343b810399525545ee1545a50e3c76548f776c83d55465071cb55a00b1","up":true},{"one":"6838b88ff71c5419d3fd22a4c007a7b822405b1842d53427a9c075d093c13047","other":"1385e97b879da9021aba65b2df36b91650aafb354a09c5be8a9532d1eb7d472b","up":true},{"one":"981be0d21bdb11ac807dc29937c6a3ac2ae3c2f45269c0edc21172d5550f4da9","other":"b1bf7a0572c1efcf034bf42f54f7334743c59f77b67225aa3c7d93552074ab56","up":true},{"one":"d9169f80683a2552475d60e6982f70c6605f9b40300f2b60895aa21cdc42db89","other":"bf7b0b9825b9c878f7730014c7458fc52ee3490c98ee5ae2f647a8d73e4464f3","up":true},{"one":"9f9700ca395f36c84a38838c11f6889d00015d97aac7943c27cf74393e3fc22f","other":"8ec663c22eff8ed185b8194c1240ff58df131c5f310df0a91b9790ec166e4346","up":true},{"one":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","other":"83119770a801a40f0f4015ce8a8cc6cfa6dbc211deaee193c5db05130002f9a7","up":true},{"one":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","other":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","up":true},{"one":"740bebf4546e2ed32b2bd277555685e3be071aca4b1990c7c095e12e24fa4699","other":"981be0d21bdb11ac807dc29937c6a3ac2ae3c2f45269c0edc21172d5550f4da9","up":true},{"one":"d68e095e77246c3c08df2eaec294965bca2d01fa2819d9cd06c6ef8730c59ab4","other":"ebf9a6425b47a78df1c99fe4d62a48c26d2f4ef966a8592802d5b5e95e6c12eb","up":true},{"one":"ea9490f17813e0e5f39e8065f4c3954163f3bca6e80afca7a2c76d5ca3116e53","other":"6838b88ff71c5419d3fd22a4c007a7b822405b1842d53427a9c075d093c13047","up":true},{"one":"1385e97b879da9021aba65b2df36b91650aafb354a09c5be8a9532d1eb7d472b","other":"d68e095e77246c3c08df2eaec294965bca2d01fa2819d9cd06c6ef8730c59ab4","up":true},{"one":"83119770a801a40f0f4015ce8a8cc6cfa6dbc211deaee193c5db05130002f9a7","other":"265040d12d0b79c8609232060b3ddd6ac16fb895b52bb7f3fee5c6e8b63a4d79","up":true},{"one":"fba89f80c35e5f6ffbe6e5521f2011caec985d820fe42759ef1aaa36d4902f37","other":"dc2ae81b708a14f8b6e134986075835994c71d284b9bd9fa19d2efa73346f35e","up":true},{"one":"a60b45dbab9c1de18783fed9eed8af6dabafbc33fdc5b3fef1ef4569bc6c70d9","other":"059a2d0e5db2c0204c10b66ef04bacc96d64453073efeee81ba7e30d43061a61","up":true},{"one":"bf7b0b9825b9c878f7730014c7458fc52ee3490c98ee5ae2f647a8d73e4464f3","other":"fba89f80c35e5f6ffbe6e5521f2011caec985d820fe42759ef1aaa36d4902f37","up":true},{"one":"c35893ea35ab764005fd81ee10d1a9ab3f361e044aa53e28ee64effca928d09d","other":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","up":true},{"one":"6632a8b23d42dfc55a103d625a3644f0067abaae554eb75a78b1094ad7b24c16","other":"a60b45dbab9c1de18783fed9eed8af6dabafbc33fdc5b3fef1ef4569bc6c70d9","up":true},{"one":"d382e36db7f13180cca7b169e8511c9b920bb481a340d8a61dd9e4969a5605b6","other":"f80e1ce23869c7da4453f06521fa320632057194c9f9b3dcdd777cc1f06603eb","up":true},{"one":"d7f956d0f6445202699bf63700da408237fdcecd27e2e8ad969e51d7e8e61483","other":"f29ff736fb944f8f8339a4e10f69ee72f108ed1d68b5a80d06a814b0807e8686","up":true},{"one":"f80e1ce23869c7da4453f06521fa320632057194c9f9b3dcdd777cc1f06603eb","other":"6632a8b23d42dfc55a103d625a3644f0067abaae554eb75a78b1094ad7b24c16","up":true},{"one":"44540a152f9fc924f10fefc013059d9ad228e74f921a67866ef28758f1886320","other":"c35893ea35ab764005fd81ee10d1a9ab3f361e044aa53e28ee64effca928d09d","up":true},{"one":"f29ff736fb944f8f8339a4e10f69ee72f108ed1d68b5a80d06a814b0807e8686","other":"b22078d9b5f3d57dec3fb1796599d5f04fcb32d4288939431ecea8516be76b58","up":true},{"one":"6640a8343b810399525545ee1545a50e3c76548f776c83d55465071cb55a00b1","other":"237b54aa7ef077972fc65fb222ce70df0129f8c8d4d35505fcbdc281bb7d28ce","up":true},{"one":"e39e8092c97b53dbeaf5ca73d95c71e8e41a2906aaa377fe0c1b23752c8c7423","other":"aa19fb3565a2677828b1384f483e3eb7ea112940d21d0aaa7b16139c753c69d7","up":true},{"one":"78bfaf122d10ee5e5304fdd247434c02b32de577bb410dc04d8ffe1906294fdb","other":"6a57cd355d641d1506957a988d93b2d3f9a613a7354c24130ad97dbc4c8296ce","up":true},{"one":"b22078d9b5f3d57dec3fb1796599d5f04fcb32d4288939431ecea8516be76b58","other":"9f9700ca395f36c84a38838c11f6889d00015d97aac7943c27cf74393e3fc22f","up":true},{"one":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","other":"cac97c7026184a2d6ba2b17fcd0c5440bec538961af836a60f2fadb78a310a77","up":true},{"one":"2feed66b03b7b057e1a64a3f877ad9c28ada96e30b98a78393b54ea31b98f4fa","other":"6ac71d55e984abe323c74ea85ac1aed1b06e56632c07c389e780268274a23810","up":true},{"one":"60ad041deb8cf72f0d2b29accb204a7e1838347b250e105ac72a0a4f17dc5f47","other":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","up":true},{"one":"cac97c7026184a2d6ba2b17fcd0c5440bec538961af836a60f2fadb78a310a77","other":"78bfaf122d10ee5e5304fdd247434c02b32de577bb410dc04d8ffe1906294fdb","up":true},{"one":"d55213d3dc27542a780472564a4a8c1ce47668e05710be178baf3238ca8886b6","other":"1f2a162a4e78c8935ed17eee206a8512ef37e126bb7ad9ff744f7b46c4b19aee","up":true},{"one":"92e2f8ec301e5858f7168b785040955d28bffae0c98e5d3f59ff9fa8636fe423","other":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","up":true},{"one":"7f5f9f955e368c906746d42953e30e781a8eb73428af5aa608af96af9bf4f60a","other":"d7f956d0f6445202699bf63700da408237fdcecd27e2e8ad969e51d7e8e61483","up":true},{"one":"237b54aa7ef077972fc65fb222ce70df0129f8c8d4d35505fcbdc281bb7d28ce","other":"d55213d3dc27542a780472564a4a8c1ce47668e05710be178baf3238ca8886b6","up":true},{"one":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","other":"06db6e244213745e02175cca1101e72799c09236838affd8a020a05e5b88e2bd","up":true},{"one":"40871cbc7eff896f15e558e2c638ac980fd89c718881cd110a086c3397b564c0","other":"d80b59b187b7765439b0e4c940f766ae71a903f599ac41a84d972e8703f7c9ff","up":true},{"one":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","other":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","up":true},{"one":"4ae614490d8739205773d16c8027c46b67d93bd336c33071d8707fea6ac1acf3","other":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","up":true},{"one":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","other":"53eaa90913134a5bdaa780d18bcd33e85b1932d9b15bcc1096fc4658ea5ea7f8","up":true},{"one":"ca9721b51497c5d570738eadc37c79fc8d5525749defb98c252371793c2a6f5c","other":"40871cbc7eff896f15e558e2c638ac980fd89c718881cd110a086c3397b564c0","up":true},{"one":"7f620d306e05bac0f07838b7de6a2db69aa6494df3db09f26624ed167318a75a","other":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","up":true},{"one":"314e1e8a8d9ae18d0b5510341a6fab0f8654a7263a5db895480aca606b6a9859","other":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","up":true},{"one":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","other":"53eaa90913134a5bdaa780d18bcd33e85b1932d9b15bcc1096fc4658ea5ea7f8","up":true},{"one":"18f6cc40773a42ddad3e3e6c5fd865a652cbb42e9054f0e46dfaed17a41d4f4f","other":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","up":true},{"one":"feb3455336b26365b81dfc8cba2a390690ce996e07fcb7f1226720d263205adf","other":"f99503dc6a4ace3317320d8454b3ac1a5977f508ae810c4d506001ed0126838b","up":true},{"one":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","other":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","up":true},{"one":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","other":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","up":true},{"one":"1943c1442da29872e77e4cfd282e9a81490c613e99cbe4503e470b7f23ce75fb","other":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","up":true},{"one":"e0ac6b34d04bc036026767288a8f0f96720f4a3efa5b52b3b86439fba562bcf4","other":"fa62b685ae8a9aac669d245102ce68b030f0f270d95d7c88969bfdc9978f9070","up":true},{"one":"fa62b685ae8a9aac669d245102ce68b030f0f270d95d7c88969bfdc9978f9070","other":"f3a1438cfc8b09a3a9c1fc8455ead9041e9532ef3e9a77ffee8e5ef62af2670c","up":true},{"one":"2aef8c4da4e38b6a885785e406912cef6087344b4be9e135e2c597468b003315","other":"7692399f01177f2a8976d4c393a6813c53820e5f4edf54934414d1bcdfb8392c","up":true},{"one":"3f1ee2912d5446e4cdf7a42582f2e30a24bcddd6422a9b6a61f287da256d0bde","other":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","up":true},{"one":"57dfed651ba7f0ab6f9e1d5d2d4ae18fee51a200f50925a88c861982bb4769cb","other":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","up":true},{"one":"6a57cd355d641d1506957a988d93b2d3f9a613a7354c24130ad97dbc4c8296ce","other":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","up":true},{"one":"3d3acee3657d98168e2f4e6463947646560b5ad1ca24b4a8a97e4284c39b02e1","other":"30a0ef4bf1f66ab4d02a027a24ba1f9b288ea93e86a5ceab64e1985aa4efcbd0","up":true},{"one":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","other":"4124e83515480818c250a3ca2d8a7b159c890983c35165ee531d2e15066c7203","up":true},{"one":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","other":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","up":true},{"one":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","other":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","up":true},{"one":"a12e73ee9cf7369825c1423608ce079cf70df75d0f0bbc56d07d484169fca26d","other":"ac23190ee9ee30dd793bf3edef355450522d3f56973ab6b1acbb6dfd9d64137a","up":true},{"one":"87e0232acc7127ea47604a5bfe84d80e38292902502c2a714e0f96d8090eb4fe","other":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","up":true},{"one":"d79838c419be793292a24feaf1b9d26c67f0c5afe15bae85689420de3436ad95","other":"d192c64e09879b5107507e193fea23b0244a574e0d66e444f7a325de32c123fd","up":true},{"one":"2a2bcb40404a427a50538ad9d1bfff1c5b1a7673990cbd20d830787fd96a00e5","other":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","up":true},{"one":"30a0ef4bf1f66ab4d02a027a24ba1f9b288ea93e86a5ceab64e1985aa4efcbd0","other":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","up":true},{"one":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","other":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","up":true},{"one":"e3c3f763b77a0bb72932d5d3dfca016e28d27b1c377690d2b074881b1a2a3259","other":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","up":true},{"one":"ac23190ee9ee30dd793bf3edef355450522d3f56973ab6b1acbb6dfd9d64137a","other":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","up":true},{"one":"7c766b57273777cc67f74870560449c9542cfec734b58b167aef99e09aeae962","other":"6e8315dc552c8112dcb01e9302b5e47fef7fbdc81fbe7a1d0b27e0cf80a65ca4","up":true},{"one":"b22078d9b5f3d57dec3fb1796599d5f04fcb32d4288939431ecea8516be76b58","other":"d7f956d0f6445202699bf63700da408237fdcecd27e2e8ad969e51d7e8e61483","up":true},{"one":"f836b3ca82f75bc0901d83b2c02e24174bee78c5eab9b55af9c492406942637a","other":"feb3455336b26365b81dfc8cba2a390690ce996e07fcb7f1226720d263205adf","up":true},{"one":"740bebf4546e2ed32b2bd277555685e3be071aca4b1990c7c095e12e24fa4699","other":"fba89f80c35e5f6ffbe6e5521f2011caec985d820fe42759ef1aaa36d4902f37","up":true},{"one":"8ec663c22eff8ed185b8194c1240ff58df131c5f310df0a91b9790ec166e4346","other":"97a5959b95662f6e820a1b011a7e87568e0efd2257a73bf91868c98c46e5a571","up":true},{"one":"4309c2d963d91e65f7e8e8ab42df52e75f8d377d4eb07d1a048c0082af4f501e","other":"4124e83515480818c250a3ca2d8a7b159c890983c35165ee531d2e15066c7203","up":true},{"one":"60ad041deb8cf72f0d2b29accb204a7e1838347b250e105ac72a0a4f17dc5f47","other":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","up":true},{"one":"cac97c7026184a2d6ba2b17fcd0c5440bec538961af836a60f2fadb78a310a77","other":"6a57cd355d641d1506957a988d93b2d3f9a613a7354c24130ad97dbc4c8296ce","up":true},{"one":"f99503dc6a4ace3317320d8454b3ac1a5977f508ae810c4d506001ed0126838b","other":"f836b3ca82f75bc0901d83b2c02e24174bee78c5eab9b55af9c492406942637a","up":true},{"one":"7692399f01177f2a8976d4c393a6813c53820e5f4edf54934414d1bcdfb8392c","other":"40871cbc7eff896f15e558e2c638ac980fd89c718881cd110a086c3397b564c0","up":true},{"one":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","other":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","up":true},{"one":"ca9721b51497c5d570738eadc37c79fc8d5525749defb98c252371793c2a6f5c","other":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","up":true},{"one":"7f620d306e05bac0f07838b7de6a2db69aa6494df3db09f26624ed167318a75a","other":"1ea89c3f15b82385c5df8810ec7cbd30e8e4a4e05c1ddf5dbe7102edaa07afd2","up":true},{"one":"7e65067fa5db52d40c0a162432200ca6d0d3917b25cad15ce498fa6b5375bc8d","other":"6cf1d30b063cf8352795a92c6af26c154fc44be06c69ea9fd4d9f6dcc626eda1","up":true},{"one":"a2504bc38c94b7912bfafc42c7a4531cd27775473071e7a26173c4db932bf9ec","other":"8bf59dc9ce97e6e2f2be364380aca38f98ddec248e730422f581f5bb56171e34","up":true},{"one":"6cf1d30b063cf8352795a92c6af26c154fc44be06c69ea9fd4d9f6dcc626eda1","other":"c7fd88864731e6b79acf436a76908b91c61ba07680699c2cda316179c3719e8a","up":true},{"one":"3e4fbab650cbd539383f7e26128c0aaa770f12c4a1fa44d553de9dda073f0cb2","other":"1ea89c3f15b82385c5df8810ec7cbd30e8e4a4e05c1ddf5dbe7102edaa07afd2","up":true},{"one":"6c1f82c3a7bc3274cfdac710caec4fc814c9f5a6797674457414dd94be577a9b","other":"6cf1d30b063cf8352795a92c6af26c154fc44be06c69ea9fd4d9f6dcc626eda1","up":true},{"one":"f80e1ce23869c7da4453f06521fa320632057194c9f9b3dcdd777cc1f06603eb","other":"e39e8092c97b53dbeaf5ca73d95c71e8e41a2906aaa377fe0c1b23752c8c7423","up":true},{"one":"1f2a162a4e78c8935ed17eee206a8512ef37e126bb7ad9ff744f7b46c4b19aee","other":"06db6e244213745e02175cca1101e72799c09236838affd8a020a05e5b88e2bd","up":true},{"one":"d55213d3dc27542a780472564a4a8c1ce47668e05710be178baf3238ca8886b6","other":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","up":true},{"one":"237b54aa7ef077972fc65fb222ce70df0129f8c8d4d35505fcbdc281bb7d28ce","other":"06db6e244213745e02175cca1101e72799c09236838affd8a020a05e5b88e2bd","up":true},{"one":"c35893ea35ab764005fd81ee10d1a9ab3f361e044aa53e28ee64effca928d09d","other":"d55213d3dc27542a780472564a4a8c1ce47668e05710be178baf3238ca8886b6","up":true},{"one":"44540a152f9fc924f10fefc013059d9ad228e74f921a67866ef28758f1886320","other":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","up":true},{"one":"83119770a801a40f0f4015ce8a8cc6cfa6dbc211deaee193c5db05130002f9a7","other":"981be0d21bdb11ac807dc29937c6a3ac2ae3c2f45269c0edc21172d5550f4da9","up":true},{"one":"931a36980ca0788616cb1c9851f6069e44d94671a2a056e12a7d7cafe597dee5","other":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","up":true},{"one":"f3a1438cfc8b09a3a9c1fc8455ead9041e9532ef3e9a77ffee8e5ef62af2670c","other":"e5cd769dbcc429587e3a92b192575965c0ac18e88c9a54b9f75936d929c43c62","up":true},{"one":"d9169f80683a2552475d60e6982f70c6605f9b40300f2b60895aa21cdc42db89","other":"dc2ae81b708a14f8b6e134986075835994c71d284b9bd9fa19d2efa73346f35e","up":true},{"one":"8bf59dc9ce97e6e2f2be364380aca38f98ddec248e730422f581f5bb56171e34","other":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","up":true},{"one":"8e317152297d5d048ca4512f733e63e48c3dc66910bad541d33f3cfc76828cb9","other":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","up":true},{"one":"d382e36db7f13180cca7b169e8511c9b920bb481a340d8a61dd9e4969a5605b6","other":"d55213d3dc27542a780472564a4a8c1ce47668e05710be178baf3238ca8886b6","up":true},{"one":"d68e095e77246c3c08df2eaec294965bca2d01fa2819d9cd06c6ef8730c59ab4","other":"dc2ae81b708a14f8b6e134986075835994c71d284b9bd9fa19d2efa73346f35e","up":true},{"one":"d87f983aefd8f732c29718a95f4dfc54a10640d5c9fdd73b772c3c30df269d88","other":"d80b59b187b7765439b0e4c940f766ae71a903f599ac41a84d972e8703f7c9ff","up":true},{"one":"d7f956d0f6445202699bf63700da408237fdcecd27e2e8ad969e51d7e8e61483","other":"9f9700ca395f36c84a38838c11f6889d00015d97aac7943c27cf74393e3fc22f","up":true},{"one":"8fe212fc9af12821f59b5f364f86f78b545be501933b926fc47b6d7deafca941","other":"981be0d21bdb11ac807dc29937c6a3ac2ae3c2f45269c0edc21172d5550f4da9","up":true},{"one":"4124e83515480818c250a3ca2d8a7b159c890983c35165ee531d2e15066c7203","other":"6e8315dc552c8112dcb01e9302b5e47fef7fbdc81fbe7a1d0b27e0cf80a65ca4","up":true},{"one":"740bebf4546e2ed32b2bd277555685e3be071aca4b1990c7c095e12e24fa4699","other":"f29ff736fb944f8f8339a4e10f69ee72f108ed1d68b5a80d06a814b0807e8686","up":true},{"one":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","other":"6a57cd355d641d1506957a988d93b2d3f9a613a7354c24130ad97dbc4c8296ce","up":true},{"one":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","other":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","up":true},{"one":"18f6cc40773a42ddad3e3e6c5fd865a652cbb42e9054f0e46dfaed17a41d4f4f","other":"314e1e8a8d9ae18d0b5510341a6fab0f8654a7263a5db895480aca606b6a9859","up":true},{"one":"314e1e8a8d9ae18d0b5510341a6fab0f8654a7263a5db895480aca606b6a9859","other":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","up":true},{"one":"a60b45dbab9c1de18783fed9eed8af6dabafbc33fdc5b3fef1ef4569bc6c70d9","other":"aa19fb3565a2677828b1384f483e3eb7ea112940d21d0aaa7b16139c753c69d7","up":true},{"one":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","other":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","up":true},{"one":"e4254cda903275296f17b47e5dcabbf16d1fa7432b64e0e6e0562fcd89de534d","other":"fba89f80c35e5f6ffbe6e5521f2011caec985d820fe42759ef1aaa36d4902f37","up":true},{"one":"60ad041deb8cf72f0d2b29accb204a7e1838347b250e105ac72a0a4f17dc5f47","other":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","up":true},{"one":"3f1ee2912d5446e4cdf7a42582f2e30a24bcddd6422a9b6a61f287da256d0bde","other":"53eaa90913134a5bdaa780d18bcd33e85b1932d9b15bcc1096fc4658ea5ea7f8","up":true},{"one":"7628bcf64532ca3ec293d26f60791a2a62542b2f5f3f1c782799075383ccde00","other":"f29ff736fb944f8f8339a4e10f69ee72f108ed1d68b5a80d06a814b0807e8686","up":true},{"one":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","other":"e3c3f763b77a0bb72932d5d3dfca016e28d27b1c377690d2b074881b1a2a3259","up":true},{"one":"87e0232acc7127ea47604a5bfe84d80e38292902502c2a714e0f96d8090eb4fe","other":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","up":true},{"one":"db596f4ff3f8c25835f9f10176e87a8823eaf1d39a513882f1ceab931dabfed9","other":"c7fd88864731e6b79acf436a76908b91c61ba07680699c2cda316179c3719e8a","up":true},{"one":"f29ff736fb944f8f8339a4e10f69ee72f108ed1d68b5a80d06a814b0807e8686","other":"cac97c7026184a2d6ba2b17fcd0c5440bec538961af836a60f2fadb78a310a77","up":true},{"one":"9f9700ca395f36c84a38838c11f6889d00015d97aac7943c27cf74393e3fc22f","other":"97a5959b95662f6e820a1b011a7e87568e0efd2257a73bf91868c98c46e5a571","up":true},{"one":"b22078d9b5f3d57dec3fb1796599d5f04fcb32d4288939431ecea8516be76b58","other":"97a5959b95662f6e820a1b011a7e87568e0efd2257a73bf91868c98c46e5a571","up":true},{"one":"8ec663c22eff8ed185b8194c1240ff58df131c5f310df0a91b9790ec166e4346","other":"83bc840da4e1f9201f0e85db2c44364f06c188fedef30fd2f72cbde70f6cfdb9","up":true},{"one":"609958921d9107eaae6f3141e94f12b3a28fec203eb0e5b674ed8268934becc7","other":"60ad041deb8cf72f0d2b29accb204a7e1838347b250e105ac72a0a4f17dc5f47","up":true},{"one":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","other":"fa62b685ae8a9aac669d245102ce68b030f0f270d95d7c88969bfdc9978f9070","up":true},{"one":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","other":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","up":true},{"one":"4ae614490d8739205773d16c8027c46b67d93bd336c33071d8707fea6ac1acf3","other":"53eaa90913134a5bdaa780d18bcd33e85b1932d9b15bcc1096fc4658ea5ea7f8","up":true},{"one":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","other":"87e0232acc7127ea47604a5bfe84d80e38292902502c2a714e0f96d8090eb4fe","up":true},{"one":"92e2f8ec301e5858f7168b785040955d28bffae0c98e5d3f59ff9fa8636fe423","other":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","up":true},{"one":"2aef8c4da4e38b6a885785e406912cef6087344b4be9e135e2c597468b003315","other":"18f6cc40773a42ddad3e3e6c5fd865a652cbb42e9054f0e46dfaed17a41d4f4f","up":true},{"one":"e0ac6b34d04bc036026767288a8f0f96720f4a3efa5b52b3b86439fba562bcf4","other":"f3a1438cfc8b09a3a9c1fc8455ead9041e9532ef3e9a77ffee8e5ef62af2670c","up":true},{"one":"7692399f01177f2a8976d4c393a6813c53820e5f4edf54934414d1bcdfb8392c","other":"78bfaf122d10ee5e5304fdd247434c02b32de577bb410dc04d8ffe1906294fdb","up":true},{"one":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","other":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","up":true},{"one":"cac97c7026184a2d6ba2b17fcd0c5440bec538961af836a60f2fadb78a310a77","other":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","up":true},{"one":"57dfed651ba7f0ab6f9e1d5d2d4ae18fee51a200f50925a88c861982bb4769cb","other":"7692399f01177f2a8976d4c393a6813c53820e5f4edf54934414d1bcdfb8392c","up":true},{"one":"6a57cd355d641d1506957a988d93b2d3f9a613a7354c24130ad97dbc4c8296ce","other":"6ac71d55e984abe323c74ea85ac1aed1b06e56632c07c389e780268274a23810","up":true},{"one":"96b723de0840f656c914674ad20204da830c04d4d6956c32e20154cbcc40f9ef","other":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","up":true},{"one":"61207f4fc13c899b88e9b727eb202a8bab00868bd07d945100b2e56dbcd67683","other":"6e8315dc552c8112dcb01e9302b5e47fef7fbdc81fbe7a1d0b27e0cf80a65ca4","up":true},{"one":"ca9721b51497c5d570738eadc37c79fc8d5525749defb98c252371793c2a6f5c","other":"d87f983aefd8f732c29718a95f4dfc54a10640d5c9fdd73b772c3c30df269d88","up":true},{"one":"1943c1442da29872e77e4cfd282e9a81490c613e99cbe4503e470b7f23ce75fb","other":"2a2bcb40404a427a50538ad9d1bfff1c5b1a7673990cbd20d830787fd96a00e5","up":true},{"one":"a12e73ee9cf7369825c1423608ce079cf70df75d0f0bbc56d07d484169fca26d","other":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","up":true},{"one":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","other":"a12e73ee9cf7369825c1423608ce079cf70df75d0f0bbc56d07d484169fca26d","up":true},{"one":"3d3acee3657d98168e2f4e6463947646560b5ad1ca24b4a8a97e4284c39b02e1","other":"2a2bcb40404a427a50538ad9d1bfff1c5b1a7673990cbd20d830787fd96a00e5","up":true},{"one":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","other":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","up":true},{"one":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","other":"b9e5006b90bffae809136ad57e36b784b18e35f7491c47319807f6869d624465","up":true},{"one":"e39e8092c97b53dbeaf5ca73d95c71e8e41a2906aaa377fe0c1b23752c8c7423","other":"d55213d3dc27542a780472564a4a8c1ce47668e05710be178baf3238ca8886b6","up":true},{"one":"06db6e244213745e02175cca1101e72799c09236838affd8a020a05e5b88e2bd","other":"6640a8343b810399525545ee1545a50e3c76548f776c83d55465071cb55a00b1","up":true},{"one":"d79838c419be793292a24feaf1b9d26c67f0c5afe15bae85689420de3436ad95","other":"d87f983aefd8f732c29718a95f4dfc54a10640d5c9fdd73b772c3c30df269d88","up":true},{"one":"f836b3ca82f75bc0901d83b2c02e24174bee78c5eab9b55af9c492406942637a","other":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","up":true},{"one":"7c766b57273777cc67f74870560449c9542cfec734b58b167aef99e09aeae962","other":"7692399f01177f2a8976d4c393a6813c53820e5f4edf54934414d1bcdfb8392c","up":true},{"one":"e3c3f763b77a0bb72932d5d3dfca016e28d27b1c377690d2b074881b1a2a3259","other":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","up":true},{"one":"ac23190ee9ee30dd793bf3edef355450522d3f56973ab6b1acbb6dfd9d64137a","other":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","up":true},{"one":"ea9490f17813e0e5f39e8065f4c3954163f3bca6e80afca7a2c76d5ca3116e53","other":"ebf9a6425b47a78df1c99fe4d62a48c26d2f4ef966a8592802d5b5e95e6c12eb","up":true},{"one":"c7fd88864731e6b79acf436a76908b91c61ba07680699c2cda316179c3719e8a","other":"c35893ea35ab764005fd81ee10d1a9ab3f361e044aa53e28ee64effca928d09d","up":true},{"one":"30a0ef4bf1f66ab4d02a027a24ba1f9b288ea93e86a5ceab64e1985aa4efcbd0","other":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","up":true},{"one":"f99503dc6a4ace3317320d8454b3ac1a5977f508ae810c4d506001ed0126838b","other":"d87f983aefd8f732c29718a95f4dfc54a10640d5c9fdd73b772c3c30df269d88","up":true},{"one":"4309c2d963d91e65f7e8e8ab42df52e75f8d377d4eb07d1a048c0082af4f501e","other":"1943c1442da29872e77e4cfd282e9a81490c613e99cbe4503e470b7f23ce75fb","up":true},{"one":"311fc84bce90b1bc1d1983c78e45b1e82a703cdb7cfbe54d51e9bc40cfe04103","other":"11a60d43d627470df1c1ebdc404093f6ae83c75b3a95469316b89a0185eeaa86","up":true},{"one":"5d6da3260504f8063af24960ac901bdc0c54a126b82d415656a083c496c8b6b8","other":"4309c2d963d91e65f7e8e8ab42df52e75f8d377d4eb07d1a048c0082af4f501e","up":true},{"one":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","other":"a12e73ee9cf7369825c1423608ce079cf70df75d0f0bbc56d07d484169fca26d","up":true},{"one":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","other":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","up":true},{"one":"bf7b0b9825b9c878f7730014c7458fc52ee3490c98ee5ae2f647a8d73e4464f3","other":"b1bf7a0572c1efcf034bf42f54f7334743c59f77b67225aa3c7d93552074ab56","up":true},{"one":"931a36980ca0788616cb1c9851f6069e44d94671a2a056e12a7d7cafe597dee5","other":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","up":true},{"one":"004710b2e2194a8cd11292efe8b6f81bec4c1b673a9c8a45b7b5391f5f049895","other":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","up":true},{"one":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","other":"2aef8c4da4e38b6a885785e406912cef6087344b4be9e135e2c597468b003315","up":true},{"one":"40871cbc7eff896f15e558e2c638ac980fd89c718881cd110a086c3397b564c0","other":"e5cd769dbcc429587e3a92b192575965c0ac18e88c9a54b9f75936d929c43c62","up":true},{"one":"11a60d43d627470df1c1ebdc404093f6ae83c75b3a95469316b89a0185eeaa86","other":"1ea89c3f15b82385c5df8810ec7cbd30e8e4a4e05c1ddf5dbe7102edaa07afd2","up":true},{"one":"7f620d306e05bac0f07838b7de6a2db69aa6494df3db09f26624ed167318a75a","other":"7e65067fa5db52d40c0a162432200ca6d0d3917b25cad15ce498fa6b5375bc8d","up":true},{"one":"6cf1d30b063cf8352795a92c6af26c154fc44be06c69ea9fd4d9f6dcc626eda1","other":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","up":true},{"one":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","other":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","up":true},{"one":"6632a8b23d42dfc55a103d625a3644f0067abaae554eb75a78b1094ad7b24c16","other":"60ad041deb8cf72f0d2b29accb204a7e1838347b250e105ac72a0a4f17dc5f47","up":true},{"one":"db596f4ff3f8c25835f9f10176e87a8823eaf1d39a513882f1ceab931dabfed9","other":"d3864c160e6d8fcb3f540b864e6ee11d91ca708f2cbe1e5fd04beb2b6c1d8930","up":true},{"one":"3e4fbab650cbd539383f7e26128c0aaa770f12c4a1fa44d553de9dda073f0cb2","other":"11a60d43d627470df1c1ebdc404093f6ae83c75b3a95469316b89a0185eeaa86","up":true},{"one":"78bfaf122d10ee5e5304fdd247434c02b32de577bb410dc04d8ffe1906294fdb","other":"7f5f9f955e368c906746d42953e30e781a8eb73428af5aa608af96af9bf4f60a","up":true},{"one":"6838b88ff71c5419d3fd22a4c007a7b822405b1842d53427a9c075d093c13047","other":"6aef7e94602e6e74ab4cdb57668fb751fa20807751f786d0232870da0ae32cfd","up":true},{"one":"d382e36db7f13180cca7b169e8511c9b920bb481a340d8a61dd9e4969a5605b6","other":"d9169f80683a2552475d60e6982f70c6605f9b40300f2b60895aa21cdc42db89","up":true},{"one":"6c1f82c3a7bc3274cfdac710caec4fc814c9f5a6797674457414dd94be577a9b","other":"7f620d306e05bac0f07838b7de6a2db69aa6494df3db09f26624ed167318a75a","up":true},{"one":"f80e1ce23869c7da4453f06521fa320632057194c9f9b3dcdd777cc1f06603eb","other":"e3c3f763b77a0bb72932d5d3dfca016e28d27b1c377690d2b074881b1a2a3259","up":true},{"one":"a60b45dbab9c1de18783fed9eed8af6dabafbc33fdc5b3fef1ef4569bc6c70d9","other":"a12e73ee9cf7369825c1423608ce079cf70df75d0f0bbc56d07d484169fca26d","up":true},{"one":"d55213d3dc27542a780472564a4a8c1ce47668e05710be178baf3238ca8886b6","other":"d9169f80683a2552475d60e6982f70c6605f9b40300f2b60895aa21cdc42db89","up":true},{"one":"237b54aa7ef077972fc65fb222ce70df0129f8c8d4d35505fcbdc281bb7d28ce","other":"1f2a162a4e78c8935ed17eee206a8512ef37e126bb7ad9ff744f7b46c4b19aee","up":true},{"one":"44540a152f9fc924f10fefc013059d9ad228e74f921a67866ef28758f1886320","other":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","up":true},{"one":"c35893ea35ab764005fd81ee10d1a9ab3f361e044aa53e28ee64effca928d09d","other":"d382e36db7f13180cca7b169e8511c9b920bb481a340d8a61dd9e4969a5605b6","up":true},{"one":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","other":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","up":true},{"one":"60ad041deb8cf72f0d2b29accb204a7e1838347b250e105ac72a0a4f17dc5f47","other":"6640a8343b810399525545ee1545a50e3c76548f776c83d55465071cb55a00b1","up":true},{"one":"500f2135db1adafe22d8a62af63f1659d6f4538f490ee8151e9db970fa24389b","other":"53eaa90913134a5bdaa780d18bcd33e85b1932d9b15bcc1096fc4658ea5ea7f8","up":true},{"one":"2dc2914f76ec9b291b2085bea6d2e0f6d45beb55bba5e3a90059b96e83010318","other":"7f5f9f955e368c906746d42953e30e781a8eb73428af5aa608af96af9bf4f60a","up":true},{"one":"aa19fb3565a2677828b1384f483e3eb7ea112940d21d0aaa7b16139c753c69d7","other":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","up":true},{"one":"83119770a801a40f0f4015ce8a8cc6cfa6dbc211deaee193c5db05130002f9a7","other":"87e0232acc7127ea47604a5bfe84d80e38292902502c2a714e0f96d8090eb4fe","up":true},{"one":"a2504bc38c94b7912bfafc42c7a4531cd27775473071e7a26173c4db932bf9ec","other":"87e0232acc7127ea47604a5bfe84d80e38292902502c2a714e0f96d8090eb4fe","up":true},{"one":"ebf9a6425b47a78df1c99fe4d62a48c26d2f4ef966a8592802d5b5e95e6c12eb","other":"e4254cda903275296f17b47e5dcabbf16d1fa7432b64e0e6e0562fcd89de534d","up":true},{"one":"d68e095e77246c3c08df2eaec294965bca2d01fa2819d9cd06c6ef8730c59ab4","other":"d7f956d0f6445202699bf63700da408237fdcecd27e2e8ad969e51d7e8e61483","up":true},{"one":"7628bcf64532ca3ec293d26f60791a2a62542b2f5f3f1c782799075383ccde00","other":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","up":true},{"one":"981be0d21bdb11ac807dc29937c6a3ac2ae3c2f45269c0edc21172d5550f4da9","other":"9f9700ca395f36c84a38838c11f6889d00015d97aac7943c27cf74393e3fc22f","up":true},{"one":"8fe212fc9af12821f59b5f364f86f78b545be501933b926fc47b6d7deafca941","other":"8ec663c22eff8ed185b8194c1240ff58df131c5f310df0a91b9790ec166e4346","up":true},{"one":"609958921d9107eaae6f3141e94f12b3a28fec203eb0e5b674ed8268934becc7","other":"6838b88ff71c5419d3fd22a4c007a7b822405b1842d53427a9c075d093c13047","up":true},{"one":"7f5f9f955e368c906746d42953e30e781a8eb73428af5aa608af96af9bf4f60a","other":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","up":true},{"one":"83bc840da4e1f9201f0e85db2c44364f06c188fedef30fd2f72cbde70f6cfdb9","other":"83119770a801a40f0f4015ce8a8cc6cfa6dbc211deaee193c5db05130002f9a7","up":true},{"one":"740bebf4546e2ed32b2bd277555685e3be071aca4b1990c7c095e12e24fa4699","other":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","up":true},{"one":"954a22c6fd7d63fce47d8685d32bb892bcc0feab4209d5dc7c3601efc28e7d6d","other":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","up":true},{"one":"18f6cc40773a42ddad3e3e6c5fd865a652cbb42e9054f0e46dfaed17a41d4f4f","other":"3f1ee2912d5446e4cdf7a42582f2e30a24bcddd6422a9b6a61f287da256d0bde","up":true},{"one":"314e1e8a8d9ae18d0b5510341a6fab0f8654a7263a5db895480aca606b6a9859","other":"3f1ee2912d5446e4cdf7a42582f2e30a24bcddd6422a9b6a61f287da256d0bde","up":true},{"one":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","other":"6aef7e94602e6e74ab4cdb57668fb751fa20807751f786d0232870da0ae32cfd","up":true},{"one":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","other":"4ae614490d8739205773d16c8027c46b67d93bd336c33071d8707fea6ac1acf3","up":true},{"one":"e4254cda903275296f17b47e5dcabbf16d1fa7432b64e0e6e0562fcd89de534d","other":"f29ff736fb944f8f8339a4e10f69ee72f108ed1d68b5a80d06a814b0807e8686","up":true},{"one":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","other":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","up":true},{"one":"feb3455336b26365b81dfc8cba2a390690ce996e07fcb7f1226720d263205adf","other":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","up":true},{"one":"87e0232acc7127ea47604a5bfe84d80e38292902502c2a714e0f96d8090eb4fe","other":"bf7b0b9825b9c878f7730014c7458fc52ee3490c98ee5ae2f647a8d73e4464f3","up":true},{"one":"b22078d9b5f3d57dec3fb1796599d5f04fcb32d4288939431ecea8516be76b58","other":"bf7b0b9825b9c878f7730014c7458fc52ee3490c98ee5ae2f647a8d73e4464f3","up":true},{"one":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","other":"8ec663c22eff8ed185b8194c1240ff58df131c5f310df0a91b9790ec166e4346","up":true},{"one":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","other":"d7f956d0f6445202699bf63700da408237fdcecd27e2e8ad969e51d7e8e61483","up":true},{"one":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","other":"9f9700ca395f36c84a38838c11f6889d00015d97aac7943c27cf74393e3fc22f","up":true},{"one":"92e2f8ec301e5858f7168b785040955d28bffae0c98e5d3f59ff9fa8636fe423","other":"981be0d21bdb11ac807dc29937c6a3ac2ae3c2f45269c0edc21172d5550f4da9","up":true},{"one":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","other":"3d3acee3657d98168e2f4e6463947646560b5ad1ca24b4a8a97e4284c39b02e1","up":true},{"one":"b1bf7a0572c1efcf034bf42f54f7334743c59f77b67225aa3c7d93552074ab56","other":"b22078d9b5f3d57dec3fb1796599d5f04fcb32d4288939431ecea8516be76b58","up":true},{"one":"f29ff736fb944f8f8339a4e10f69ee72f108ed1d68b5a80d06a814b0807e8686","other":"f3a1438cfc8b09a3a9c1fc8455ead9041e9532ef3e9a77ffee8e5ef62af2670c","up":true},{"one":"e0ac6b34d04bc036026767288a8f0f96720f4a3efa5b52b3b86439fba562bcf4","other":"f29ff736fb944f8f8339a4e10f69ee72f108ed1d68b5a80d06a814b0807e8686","up":true},{"one":"2aef8c4da4e38b6a885785e406912cef6087344b4be9e135e2c597468b003315","other":"314e1e8a8d9ae18d0b5510341a6fab0f8654a7263a5db895480aca606b6a9859","up":true},{"one":"aec520f507eedd5d752b6ba8a0b509f14105e5e457eef0b6537015e39569762b","other":"ac23190ee9ee30dd793bf3edef355450522d3f56973ab6b1acbb6dfd9d64137a","up":true},{"one":"cac97c7026184a2d6ba2b17fcd0c5440bec538961af836a60f2fadb78a310a77","other":"dc2ae81b708a14f8b6e134986075835994c71d284b9bd9fa19d2efa73346f35e","up":true},{"one":"7692399f01177f2a8976d4c393a6813c53820e5f4edf54934414d1bcdfb8392c","other":"7f5f9f955e368c906746d42953e30e781a8eb73428af5aa608af96af9bf4f60a","up":true},{"one":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","other":"4ae614490d8739205773d16c8027c46b67d93bd336c33071d8707fea6ac1acf3","up":true},{"one":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","other":"7f620d306e05bac0f07838b7de6a2db69aa6494df3db09f26624ed167318a75a","up":true},{"one":"6a57cd355d641d1506957a988d93b2d3f9a613a7354c24130ad97dbc4c8296ce","other":"7f5f9f955e368c906746d42953e30e781a8eb73428af5aa608af96af9bf4f60a","up":true},{"one":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","other":"f3a1438cfc8b09a3a9c1fc8455ead9041e9532ef3e9a77ffee8e5ef62af2670c","up":true},{"one":"57dfed651ba7f0ab6f9e1d5d2d4ae18fee51a200f50925a88c861982bb4769cb","other":"4ae614490d8739205773d16c8027c46b67d93bd336c33071d8707fea6ac1acf3","up":true},{"one":"ca9721b51497c5d570738eadc37c79fc8d5525749defb98c252371793c2a6f5c","other":"b9e5006b90bffae809136ad57e36b784b18e35f7491c47319807f6869d624465","up":true},{"one":"53eaa90913134a5bdaa780d18bcd33e85b1932d9b15bcc1096fc4658ea5ea7f8","other":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","up":true},{"one":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","other":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","up":true},{"one":"8c5b95bf1743e2d8a97210c5b64b6732f3615c4369fa3a52e426f93e168ae16f","other":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","up":true},{"one":"f3a1438cfc8b09a3a9c1fc8455ead9041e9532ef3e9a77ffee8e5ef62af2670c","other":"d80b59b187b7765439b0e4c940f766ae71a903f599ac41a84d972e8703f7c9ff","up":true},{"one":"d9169f80683a2552475d60e6982f70c6605f9b40300f2b60895aa21cdc42db89","other":"d68e095e77246c3c08df2eaec294965bca2d01fa2819d9cd06c6ef8730c59ab4","up":true},{"one":"8bf59dc9ce97e6e2f2be364380aca38f98ddec248e730422f581f5bb56171e34","other":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","up":true},{"one":"8e317152297d5d048ca4512f733e63e48c3dc66910bad541d33f3cfc76828cb9","other":"954a22c6fd7d63fce47d8685d32bb892bcc0feab4209d5dc7c3601efc28e7d6d","up":true},{"one":"d7f956d0f6445202699bf63700da408237fdcecd27e2e8ad969e51d7e8e61483","other":"8ec663c22eff8ed185b8194c1240ff58df131c5f310df0a91b9790ec166e4346","up":true},{"one":"f836b3ca82f75bc0901d83b2c02e24174bee78c5eab9b55af9c492406942637a","other":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","up":true},{"one":"d87f983aefd8f732c29718a95f4dfc54a10640d5c9fdd73b772c3c30df269d88","other":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","up":true},{"one":"d79838c419be793292a24feaf1b9d26c67f0c5afe15bae85689420de3436ad95","other":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","up":true},{"one":"7c766b57273777cc67f74870560449c9542cfec734b58b167aef99e09aeae962","other":"61207f4fc13c899b88e9b727eb202a8bab00868bd07d945100b2e56dbcd67683","up":true},{"one":"40871cbc7eff896f15e558e2c638ac980fd89c718881cd110a086c3397b564c0","other":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","up":true},{"one":"f99503dc6a4ace3317320d8454b3ac1a5977f508ae810c4d506001ed0126838b","other":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","up":true},{"one":"e3c3f763b77a0bb72932d5d3dfca016e28d27b1c377690d2b074881b1a2a3259","other":"1943c1442da29872e77e4cfd282e9a81490c613e99cbe4503e470b7f23ce75fb","up":true},{"one":"dc2ae81b708a14f8b6e134986075835994c71d284b9bd9fa19d2efa73346f35e","other":"d7f956d0f6445202699bf63700da408237fdcecd27e2e8ad969e51d7e8e61483","up":true},{"one":"7e65067fa5db52d40c0a162432200ca6d0d3917b25cad15ce498fa6b5375bc8d","other":"6c1f82c3a7bc3274cfdac710caec4fc814c9f5a6797674457414dd94be577a9b","up":true},{"one":"d192c64e09879b5107507e193fea23b0244a574e0d66e444f7a325de32c123fd","other":"d87f983aefd8f732c29718a95f4dfc54a10640d5c9fdd73b772c3c30df269d88","up":true},{"one":"d3864c160e6d8fcb3f540b864e6ee11d91ca708f2cbe1e5fd04beb2b6c1d8930","other":"d382e36db7f13180cca7b169e8511c9b920bb481a340d8a61dd9e4969a5605b6","up":true},{"one":"5d6da3260504f8063af24960ac901bdc0c54a126b82d415656a083c496c8b6b8","other":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","up":true},{"one":"4309c2d963d91e65f7e8e8ab42df52e75f8d377d4eb07d1a048c0082af4f501e","other":"6e8315dc552c8112dcb01e9302b5e47fef7fbdc81fbe7a1d0b27e0cf80a65ca4","up":true},{"one":"f07c1ee0f9cadf2971a0cb0022d1c67fa82c79f63cb98769b31ccfb04d05c0a8","other":"fa62b685ae8a9aac669d245102ce68b030f0f270d95d7c88969bfdc9978f9070","up":true},{"one":"1f2a162a4e78c8935ed17eee206a8512ef37e126bb7ad9ff744f7b46c4b19aee","other":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","up":true},{"one":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","other":"aec520f507eedd5d752b6ba8a0b509f14105e5e457eef0b6537015e39569762b","up":true},{"one":"931a36980ca0788616cb1c9851f6069e44d94671a2a056e12a7d7cafe597dee5","other":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","up":true},{"one":"db596f4ff3f8c25835f9f10176e87a8823eaf1d39a513882f1ceab931dabfed9","other":"d382e36db7f13180cca7b169e8511c9b920bb481a340d8a61dd9e4969a5605b6","up":true},{"one":"7f620d306e05bac0f07838b7de6a2db69aa6494df3db09f26624ed167318a75a","other":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","up":true},{"one":"1ea89c3f15b82385c5df8810ec7cbd30e8e4a4e05c1ddf5dbe7102edaa07afd2","other":"311fc84bce90b1bc1d1983c78e45b1e82a703cdb7cfbe54d51e9bc40cfe04103","up":true},{"one":"3e4fbab650cbd539383f7e26128c0aaa770f12c4a1fa44d553de9dda073f0cb2","other":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","up":true},{"one":"96b723de0840f656c914674ad20204da830c04d4d6956c32e20154cbcc40f9ef","other":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","up":true},{"one":"d382e36db7f13180cca7b169e8511c9b920bb481a340d8a61dd9e4969a5605b6","other":"3e4fbab650cbd539383f7e26128c0aaa770f12c4a1fa44d553de9dda073f0cb2","up":true},{"one":"e5cd769dbcc429587e3a92b192575965c0ac18e88c9a54b9f75936d929c43c62","other":"b9e5006b90bffae809136ad57e36b784b18e35f7491c47319807f6869d624465","up":true},{"one":"f80e1ce23869c7da4453f06521fa320632057194c9f9b3dcdd777cc1f06603eb","other":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","up":true},{"one":"a60b45dbab9c1de18783fed9eed8af6dabafbc33fdc5b3fef1ef4569bc6c70d9","other":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","up":true},{"one":"b9e5006b90bffae809136ad57e36b784b18e35f7491c47319807f6869d624465","other":"aec520f507eedd5d752b6ba8a0b509f14105e5e457eef0b6537015e39569762b","up":true},{"one":"aa19fb3565a2677828b1384f483e3eb7ea112940d21d0aaa7b16139c753c69d7","other":"aec520f507eedd5d752b6ba8a0b509f14105e5e457eef0b6537015e39569762b","up":true},{"one":"97a5959b95662f6e820a1b011a7e87568e0efd2257a73bf91868c98c46e5a571","other":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","up":true},{"one":"61207f4fc13c899b88e9b727eb202a8bab00868bd07d945100b2e56dbcd67683","other":"7692399f01177f2a8976d4c393a6813c53820e5f4edf54934414d1bcdfb8392c","up":true},{"one":"3f1ee2912d5446e4cdf7a42582f2e30a24bcddd6422a9b6a61f287da256d0bde","other":"2aef8c4da4e38b6a885785e406912cef6087344b4be9e135e2c597468b003315","up":true},{"one":"8ec663c22eff8ed185b8194c1240ff58df131c5f310df0a91b9790ec166e4346","other":"83119770a801a40f0f4015ce8a8cc6cfa6dbc211deaee193c5db05130002f9a7","up":true},{"one":"fba89f80c35e5f6ffbe6e5521f2011caec985d820fe42759ef1aaa36d4902f37","other":"fa62b685ae8a9aac669d245102ce68b030f0f270d95d7c88969bfdc9978f9070","up":true},{"one":"c35893ea35ab764005fd81ee10d1a9ab3f361e044aa53e28ee64effca928d09d","other":"d9169f80683a2552475d60e6982f70c6605f9b40300f2b60895aa21cdc42db89","up":true},{"one":"44540a152f9fc924f10fefc013059d9ad228e74f921a67866ef28758f1886320","other":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","up":true},{"one":"60ad041deb8cf72f0d2b29accb204a7e1838347b250e105ac72a0a4f17dc5f47","other":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","up":true},{"one":"3d3acee3657d98168e2f4e6463947646560b5ad1ca24b4a8a97e4284c39b02e1","other":"1943c1442da29872e77e4cfd282e9a81490c613e99cbe4503e470b7f23ce75fb","up":true},{"one":"83119770a801a40f0f4015ce8a8cc6cfa6dbc211deaee193c5db05130002f9a7","other":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","up":true},{"one":"d68e095e77246c3c08df2eaec294965bca2d01fa2819d9cd06c6ef8730c59ab4","other":"d55213d3dc27542a780472564a4a8c1ce47668e05710be178baf3238ca8886b6","up":true},{"one":"609958921d9107eaae6f3141e94f12b3a28fec203eb0e5b674ed8268934becc7","other":"6640a8343b810399525545ee1545a50e3c76548f776c83d55465071cb55a00b1","up":true},{"one":"d80b59b187b7765439b0e4c940f766ae71a903f599ac41a84d972e8703f7c9ff","other":"b9e5006b90bffae809136ad57e36b784b18e35f7491c47319807f6869d624465","up":true},{"one":"87e0232acc7127ea47604a5bfe84d80e38292902502c2a714e0f96d8090eb4fe","other":"83bc840da4e1f9201f0e85db2c44364f06c188fedef30fd2f72cbde70f6cfdb9","up":true},{"one":"e4254cda903275296f17b47e5dcabbf16d1fa7432b64e0e6e0562fcd89de534d","other":"fa62b685ae8a9aac669d245102ce68b030f0f270d95d7c88969bfdc9978f9070","up":true},{"one":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","other":"d68e095e77246c3c08df2eaec294965bca2d01fa2819d9cd06c6ef8730c59ab4","up":true},{"one":"8fe212fc9af12821f59b5f364f86f78b545be501933b926fc47b6d7deafca941","other":"8bf59dc9ce97e6e2f2be364380aca38f98ddec248e730422f581f5bb56171e34","up":true},{"one":"7628bcf64532ca3ec293d26f60791a2a62542b2f5f3f1c782799075383ccde00","other":"6a57cd355d641d1506957a988d93b2d3f9a613a7354c24130ad97dbc4c8296ce","up":true},{"one":"b22078d9b5f3d57dec3fb1796599d5f04fcb32d4288939431ecea8516be76b58","other":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","up":true},{"one":"740bebf4546e2ed32b2bd277555685e3be071aca4b1990c7c095e12e24fa4699","other":"6a57cd355d641d1506957a988d93b2d3f9a613a7354c24130ad97dbc4c8296ce","up":true},{"one":"314e1e8a8d9ae18d0b5510341a6fab0f8654a7263a5db895480aca606b6a9859","other":"d68e095e77246c3c08df2eaec294965bca2d01fa2819d9cd06c6ef8730c59ab4","up":true},{"one":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","other":"d68e095e77246c3c08df2eaec294965bca2d01fa2819d9cd06c6ef8730c59ab4","up":true},{"one":"e0ac6b34d04bc036026767288a8f0f96720f4a3efa5b52b3b86439fba562bcf4","other":"e4254cda903275296f17b47e5dcabbf16d1fa7432b64e0e6e0562fcd89de534d","up":true},{"one":"2feed66b03b7b057e1a64a3f877ad9c28ada96e30b98a78393b54ea31b98f4fa","other":"2aef8c4da4e38b6a885785e406912cef6087344b4be9e135e2c597468b003315","up":true},{"one":"92e2f8ec301e5858f7168b785040955d28bffae0c98e5d3f59ff9fa8636fe423","other":"9f9700ca395f36c84a38838c11f6889d00015d97aac7943c27cf74393e3fc22f","up":true},{"one":"a12e73ee9cf7369825c1423608ce079cf70df75d0f0bbc56d07d484169fca26d","other":"b9e5006b90bffae809136ad57e36b784b18e35f7491c47319807f6869d624465","up":true},{"one":"1385e97b879da9021aba65b2df36b91650aafb354a09c5be8a9532d1eb7d472b","other":"265040d12d0b79c8609232060b3ddd6ac16fb895b52bb7f3fee5c6e8b63a4d79","up":true},{"one":"7692399f01177f2a8976d4c393a6813c53820e5f4edf54934414d1bcdfb8392c","other":"7628bcf64532ca3ec293d26f60791a2a62542b2f5f3f1c782799075383ccde00","up":true},{"one":"cac97c7026184a2d6ba2b17fcd0c5440bec538961af836a60f2fadb78a310a77","other":"d68e095e77246c3c08df2eaec294965bca2d01fa2819d9cd06c6ef8730c59ab4","up":true},{"one":"ea9490f17813e0e5f39e8065f4c3954163f3bca6e80afca7a2c76d5ca3116e53","other":"f29ff736fb944f8f8339a4e10f69ee72f108ed1d68b5a80d06a814b0807e8686","up":true},{"one":"2a2bcb40404a427a50538ad9d1bfff1c5b1a7673990cbd20d830787fd96a00e5","other":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","up":true},{"one":"c7fd88864731e6b79acf436a76908b91c61ba07680699c2cda316179c3719e8a","other":"d3864c160e6d8fcb3f540b864e6ee11d91ca708f2cbe1e5fd04beb2b6c1d8930","up":true},{"one":"4124e83515480818c250a3ca2d8a7b159c890983c35165ee531d2e15066c7203","other":"2a2bcb40404a427a50538ad9d1bfff1c5b1a7673990cbd20d830787fd96a00e5","up":true},{"one":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","other":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","up":true},{"one":"004710b2e2194a8cd11292efe8b6f81bec4c1b673a9c8a45b7b5391f5f049895","other":"3f1ee2912d5446e4cdf7a42582f2e30a24bcddd6422a9b6a61f287da256d0bde","up":true},{"one":"bf7b0b9825b9c878f7730014c7458fc52ee3490c98ee5ae2f647a8d73e4464f3","other":"83119770a801a40f0f4015ce8a8cc6cfa6dbc211deaee193c5db05130002f9a7","up":true},{"one":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","other":"fa62b685ae8a9aac669d245102ce68b030f0f270d95d7c88969bfdc9978f9070","up":true},{"one":"6aef7e94602e6e74ab4cdb57668fb751fa20807751f786d0232870da0ae32cfd","other":"609958921d9107eaae6f3141e94f12b3a28fec203eb0e5b674ed8268934becc7","up":true},{"one":"57dfed651ba7f0ab6f9e1d5d2d4ae18fee51a200f50925a88c861982bb4769cb","other":"53eaa90913134a5bdaa780d18bcd33e85b1932d9b15bcc1096fc4658ea5ea7f8","up":true},{"one":"ac23190ee9ee30dd793bf3edef355450522d3f56973ab6b1acbb6dfd9d64137a","other":"a60b45dbab9c1de18783fed9eed8af6dabafbc33fdc5b3fef1ef4569bc6c70d9","up":true},{"one":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","other":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","up":true},{"one":"6e8315dc552c8112dcb01e9302b5e47fef7fbdc81fbe7a1d0b27e0cf80a65ca4","other":"78bfaf122d10ee5e5304fdd247434c02b32de577bb410dc04d8ffe1906294fdb","up":true},{"one":"6632a8b23d42dfc55a103d625a3644f0067abaae554eb75a78b1094ad7b24c16","other":"6aef7e94602e6e74ab4cdb57668fb751fa20807751f786d0232870da0ae32cfd","up":true},{"one":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","other":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","up":true},{"one":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","other":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","up":true},{"one":"78bfaf122d10ee5e5304fdd247434c02b32de577bb410dc04d8ffe1906294fdb","other":"6ac71d55e984abe323c74ea85ac1aed1b06e56632c07c389e780268274a23810","up":true},{"one":"6838b88ff71c5419d3fd22a4c007a7b822405b1842d53427a9c075d093c13047","other":"60ad041deb8cf72f0d2b29accb204a7e1838347b250e105ac72a0a4f17dc5f47","up":true},{"one":"4ae614490d8739205773d16c8027c46b67d93bd336c33071d8707fea6ac1acf3","other":"6a57cd355d641d1506957a988d93b2d3f9a613a7354c24130ad97dbc4c8296ce","up":true},{"one":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","other":"87e0232acc7127ea47604a5bfe84d80e38292902502c2a714e0f96d8090eb4fe","up":true},{"one":"500f2135db1adafe22d8a62af63f1659d6f4538f490ee8151e9db970fa24389b","other":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","up":true},{"one":"2dc2914f76ec9b291b2085bea6d2e0f6d45beb55bba5e3a90059b96e83010318","other":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","up":true},{"one":"5d6da3260504f8063af24960ac901bdc0c54a126b82d415656a083c496c8b6b8","other":"4124e83515480818c250a3ca2d8a7b159c890983c35165ee531d2e15066c7203","up":true},{"one":"6c1f82c3a7bc3274cfdac710caec4fc814c9f5a6797674457414dd94be577a9b","other":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","up":true},{"one":"7c766b57273777cc67f74870560449c9542cfec734b58b167aef99e09aeae962","other":"7f5f9f955e368c906746d42953e30e781a8eb73428af5aa608af96af9bf4f60a","up":true},{"one":"ebf9a6425b47a78df1c99fe4d62a48c26d2f4ef966a8592802d5b5e95e6c12eb","other":"fba89f80c35e5f6ffbe6e5521f2011caec985d820fe42759ef1aaa36d4902f37","up":true},{"one":"981be0d21bdb11ac807dc29937c6a3ac2ae3c2f45269c0edc21172d5550f4da9","other":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","up":true},{"one":"83bc840da4e1f9201f0e85db2c44364f06c188fedef30fd2f72cbde70f6cfdb9","other":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","up":true},{"one":"30a0ef4bf1f66ab4d02a027a24ba1f9b288ea93e86a5ceab64e1985aa4efcbd0","other":"311fc84bce90b1bc1d1983c78e45b1e82a703cdb7cfbe54d51e9bc40cfe04103","up":true},{"one":"e3c3f763b77a0bb72932d5d3dfca016e28d27b1c377690d2b074881b1a2a3259","other":"e39e8092c97b53dbeaf5ca73d95c71e8e41a2906aaa377fe0c1b23752c8c7423","up":true},{"one":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","other":"b9e5006b90bffae809136ad57e36b784b18e35f7491c47319807f6869d624465","up":true},{"one":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","other":"c7fd88864731e6b79acf436a76908b91c61ba07680699c2cda316179c3719e8a","up":true},{"one":"9f9700ca395f36c84a38838c11f6889d00015d97aac7943c27cf74393e3fc22f","other":"8fe212fc9af12821f59b5f364f86f78b545be501933b926fc47b6d7deafca941","up":true},{"one":"6640a8343b810399525545ee1545a50e3c76548f776c83d55465071cb55a00b1","other":"6632a8b23d42dfc55a103d625a3644f0067abaae554eb75a78b1094ad7b24c16","up":true},{"one":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","other":"6838b88ff71c5419d3fd22a4c007a7b822405b1842d53427a9c075d093c13047","up":true},{"one":"1943c1442da29872e77e4cfd282e9a81490c613e99cbe4503e470b7f23ce75fb","other":"30a0ef4bf1f66ab4d02a027a24ba1f9b288ea93e86a5ceab64e1985aa4efcbd0","up":true},{"one":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","other":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","up":true},{"one":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","other":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","up":true},{"one":"6cf1d30b063cf8352795a92c6af26c154fc44be06c69ea9fd4d9f6dcc626eda1","other":"7c766b57273777cc67f74870560449c9542cfec734b58b167aef99e09aeae962","up":true},{"one":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","other":"7e65067fa5db52d40c0a162432200ca6d0d3917b25cad15ce498fa6b5375bc8d","up":true},{"one":"7f620d306e05bac0f07838b7de6a2db69aa6494df3db09f26624ed167318a75a","other":"7c766b57273777cc67f74870560449c9542cfec734b58b167aef99e09aeae962","up":true},{"one":"265040d12d0b79c8609232060b3ddd6ac16fb895b52bb7f3fee5c6e8b63a4d79","other":"237b54aa7ef077972fc65fb222ce70df0129f8c8d4d35505fcbdc281bb7d28ce","up":true},{"one":"e39e8092c97b53dbeaf5ca73d95c71e8e41a2906aaa377fe0c1b23752c8c7423","other":"d382e36db7f13180cca7b169e8511c9b920bb481a340d8a61dd9e4969a5605b6","up":true},{"one":"06db6e244213745e02175cca1101e72799c09236838affd8a020a05e5b88e2bd","other":"60ad041deb8cf72f0d2b29accb204a7e1838347b250e105ac72a0a4f17dc5f47","up":true},{"one":"b1bf7a0572c1efcf034bf42f54f7334743c59f77b67225aa3c7d93552074ab56","other":"83bc840da4e1f9201f0e85db2c44364f06c188fedef30fd2f72cbde70f6cfdb9","up":true},{"one":"f29ff736fb944f8f8339a4e10f69ee72f108ed1d68b5a80d06a814b0807e8686","other":"fba89f80c35e5f6ffbe6e5521f2011caec985d820fe42759ef1aaa36d4902f37","up":true},{"one":"3e4fbab650cbd539383f7e26128c0aaa770f12c4a1fa44d553de9dda073f0cb2","other":"30a0ef4bf1f66ab4d02a027a24ba1f9b288ea93e86a5ceab64e1985aa4efcbd0","up":true},{"one":"b9e5006b90bffae809136ad57e36b784b18e35f7491c47319807f6869d624465","other":"ac23190ee9ee30dd793bf3edef355450522d3f56973ab6b1acbb6dfd9d64137a","up":true},{"one":"11a60d43d627470df1c1ebdc404093f6ae83c75b3a95469316b89a0185eeaa86","other":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","up":true},{"one":"931a36980ca0788616cb1c9851f6069e44d94671a2a056e12a7d7cafe597dee5","other":"96b723de0840f656c914674ad20204da830c04d4d6956c32e20154cbcc40f9ef","up":true},{"one":"db596f4ff3f8c25835f9f10176e87a8823eaf1d39a513882f1ceab931dabfed9","other":"d9169f80683a2552475d60e6982f70c6605f9b40300f2b60895aa21cdc42db89","up":true},{"one":"f2b809e97d3c3a652c4ddce19ca12b01c1de25f2ce007d561fd5606e85540d64","other":"f29ff736fb944f8f8339a4e10f69ee72f108ed1d68b5a80d06a814b0807e8686","up":true},{"one":"fa62b685ae8a9aac669d245102ce68b030f0f270d95d7c88969bfdc9978f9070","other":"f29ff736fb944f8f8339a4e10f69ee72f108ed1d68b5a80d06a814b0807e8686","up":true},{"one":"53eaa90913134a5bdaa780d18bcd33e85b1932d9b15bcc1096fc4658ea5ea7f8","other":"7f5f9f955e368c906746d42953e30e781a8eb73428af5aa608af96af9bf4f60a","up":true},{"one":"d382e36db7f13180cca7b169e8511c9b920bb481a340d8a61dd9e4969a5605b6","other":"d68e095e77246c3c08df2eaec294965bca2d01fa2819d9cd06c6ef8730c59ab4","up":true},{"one":"a12e73ee9cf7369825c1423608ce079cf70df75d0f0bbc56d07d484169fca26d","other":"aec520f507eedd5d752b6ba8a0b509f14105e5e457eef0b6537015e39569762b","up":true},{"one":"f80e1ce23869c7da4453f06521fa320632057194c9f9b3dcdd777cc1f06603eb","other":"feb3455336b26365b81dfc8cba2a390690ce996e07fcb7f1226720d263205adf","up":true},{"one":"059a2d0e5db2c0204c10b66ef04bacc96d64453073efeee81ba7e30d43061a61","other":"06db6e244213745e02175cca1101e72799c09236838affd8a020a05e5b88e2bd","up":true},{"one":"8c5b95bf1743e2d8a97210c5b64b6732f3615c4369fa3a52e426f93e168ae16f","other":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","up":true},{"one":"d9169f80683a2552475d60e6982f70c6605f9b40300f2b60895aa21cdc42db89","other":"ebf9a6425b47a78df1c99fe4d62a48c26d2f4ef966a8592802d5b5e95e6c12eb","up":true},{"one":"6a57cd355d641d1506957a988d93b2d3f9a613a7354c24130ad97dbc4c8296ce","other":"7692399f01177f2a8976d4c393a6813c53820e5f4edf54934414d1bcdfb8392c","up":true},{"one":"a60b45dbab9c1de18783fed9eed8af6dabafbc33fdc5b3fef1ef4569bc6c70d9","other":"c7fd88864731e6b79acf436a76908b91c61ba07680699c2cda316179c3719e8a","up":true},{"one":"aa19fb3565a2677828b1384f483e3eb7ea112940d21d0aaa7b16139c753c69d7","other":"ac23190ee9ee30dd793bf3edef355450522d3f56973ab6b1acbb6dfd9d64137a","up":true},{"one":"311fc84bce90b1bc1d1983c78e45b1e82a703cdb7cfbe54d51e9bc40cfe04103","other":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","up":true},{"one":"d55213d3dc27542a780472564a4a8c1ce47668e05710be178baf3238ca8886b6","other":"d79838c419be793292a24feaf1b9d26c67f0c5afe15bae85689420de3436ad95","up":true},{"one":"c35893ea35ab764005fd81ee10d1a9ab3f361e044aa53e28ee64effca928d09d","other":"d68e095e77246c3c08df2eaec294965bca2d01fa2819d9cd06c6ef8730c59ab4","up":true},{"one":"44540a152f9fc924f10fefc013059d9ad228e74f921a67866ef28758f1886320","other":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","up":true},{"one":"83119770a801a40f0f4015ce8a8cc6cfa6dbc211deaee193c5db05130002f9a7","other":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","up":true},{"one":"60ad041deb8cf72f0d2b29accb204a7e1838347b250e105ac72a0a4f17dc5f47","other":"237b54aa7ef077972fc65fb222ce70df0129f8c8d4d35505fcbdc281bb7d28ce","up":true},{"one":"ac23190ee9ee30dd793bf3edef355450522d3f56973ab6b1acbb6dfd9d64137a","other":"7692399f01177f2a8976d4c393a6813c53820e5f4edf54934414d1bcdfb8392c","up":true},{"one":"dc2ae81b708a14f8b6e134986075835994c71d284b9bd9fa19d2efa73346f35e","other":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","up":true},{"one":"6e8315dc552c8112dcb01e9302b5e47fef7fbdc81fbe7a1d0b27e0cf80a65ca4","other":"7f5f9f955e368c906746d42953e30e781a8eb73428af5aa608af96af9bf4f60a","up":true},{"one":"d68e095e77246c3c08df2eaec294965bca2d01fa2819d9cd06c6ef8730c59ab4","other":"b1bf7a0572c1efcf034bf42f54f7334743c59f77b67225aa3c7d93552074ab56","up":true},{"one":"1385e97b879da9021aba65b2df36b91650aafb354a09c5be8a9532d1eb7d472b","other":"1f2a162a4e78c8935ed17eee206a8512ef37e126bb7ad9ff744f7b46c4b19aee","up":true},{"one":"d3864c160e6d8fcb3f540b864e6ee11d91ca708f2cbe1e5fd04beb2b6c1d8930","other":"d55213d3dc27542a780472564a4a8c1ce47668e05710be178baf3238ca8886b6","up":true},{"one":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","other":"d87f983aefd8f732c29718a95f4dfc54a10640d5c9fdd73b772c3c30df269d88","up":true},{"one":"d192c64e09879b5107507e193fea23b0244a574e0d66e444f7a325de32c123fd","other":"f99503dc6a4ace3317320d8454b3ac1a5977f508ae810c4d506001ed0126838b","up":true},{"one":"f07c1ee0f9cadf2971a0cb0022d1c67fa82c79f63cb98769b31ccfb04d05c0a8","other":"e0ac6b34d04bc036026767288a8f0f96720f4a3efa5b52b3b86439fba562bcf4","up":true},{"one":"609958921d9107eaae6f3141e94f12b3a28fec203eb0e5b674ed8268934becc7","other":"6632a8b23d42dfc55a103d625a3644f0067abaae554eb75a78b1094ad7b24c16","up":true},{"one":"a2504bc38c94b7912bfafc42c7a4531cd27775473071e7a26173c4db932bf9ec","other":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","up":true},{"one":"8fe212fc9af12821f59b5f364f86f78b545be501933b926fc47b6d7deafca941","other":"bf7b0b9825b9c878f7730014c7458fc52ee3490c98ee5ae2f647a8d73e4464f3","up":true},{"one":"7628bcf64532ca3ec293d26f60791a2a62542b2f5f3f1c782799075383ccde00","other":"314e1e8a8d9ae18d0b5510341a6fab0f8654a7263a5db895480aca606b6a9859","up":true},{"one":"954a22c6fd7d63fce47d8685d32bb892bcc0feab4209d5dc7c3601efc28e7d6d","other":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","up":true},{"one":"7f5f9f955e368c906746d42953e30e781a8eb73428af5aa608af96af9bf4f60a","other":"314e1e8a8d9ae18d0b5510341a6fab0f8654a7263a5db895480aca606b6a9859","up":true},{"one":"e5cd769dbcc429587e3a92b192575965c0ac18e88c9a54b9f75936d929c43c62","other":"ac23190ee9ee30dd793bf3edef355450522d3f56973ab6b1acbb6dfd9d64137a","up":true},{"one":"740bebf4546e2ed32b2bd277555685e3be071aca4b1990c7c095e12e24fa4699","other":"7692399f01177f2a8976d4c393a6813c53820e5f4edf54934414d1bcdfb8392c","up":true},{"one":"18f6cc40773a42ddad3e3e6c5fd865a652cbb42e9054f0e46dfaed17a41d4f4f","other":"1f2a162a4e78c8935ed17eee206a8512ef37e126bb7ad9ff744f7b46c4b19aee","up":true},{"one":"97a5959b95662f6e820a1b011a7e87568e0efd2257a73bf91868c98c46e5a571","other":"92e2f8ec301e5858f7168b785040955d28bffae0c98e5d3f59ff9fa8636fe423","up":true},{"one":"314e1e8a8d9ae18d0b5510341a6fab0f8654a7263a5db895480aca606b6a9859","other":"265040d12d0b79c8609232060b3ddd6ac16fb895b52bb7f3fee5c6e8b63a4d79","up":true},{"one":"feb3455336b26365b81dfc8cba2a390690ce996e07fcb7f1226720d263205adf","other":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","up":true},{"one":"e4254cda903275296f17b47e5dcabbf16d1fa7432b64e0e6e0562fcd89de534d","other":"e5cd769dbcc429587e3a92b192575965c0ac18e88c9a54b9f75936d929c43c62","up":true},{"one":"6ac71d55e984abe323c74ea85ac1aed1b06e56632c07c389e780268274a23810","other":"61207f4fc13c899b88e9b727eb202a8bab00868bd07d945100b2e56dbcd67683","up":true},{"one":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","other":"6a57cd355d641d1506957a988d93b2d3f9a613a7354c24130ad97dbc4c8296ce","up":true},{"one":"d80b59b187b7765439b0e4c940f766ae71a903f599ac41a84d972e8703f7c9ff","other":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","up":true},{"one":"b22078d9b5f3d57dec3fb1796599d5f04fcb32d4288939431ecea8516be76b58","other":"314e1e8a8d9ae18d0b5510341a6fab0f8654a7263a5db895480aca606b6a9859","up":true},{"one":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","other":"bf7b0b9825b9c878f7730014c7458fc52ee3490c98ee5ae2f647a8d73e4464f3","up":true},{"one":"b1bf7a0572c1efcf034bf42f54f7334743c59f77b67225aa3c7d93552074ab56","other":"314e1e8a8d9ae18d0b5510341a6fab0f8654a7263a5db895480aca606b6a9859","up":true},{"one":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","other":"d55213d3dc27542a780472564a4a8c1ce47668e05710be178baf3238ca8886b6","up":true},{"one":"e0ac6b34d04bc036026767288a8f0f96720f4a3efa5b52b3b86439fba562bcf4","other":"e5cd769dbcc429587e3a92b192575965c0ac18e88c9a54b9f75936d929c43c62","up":true},{"one":"2feed66b03b7b057e1a64a3f877ad9c28ada96e30b98a78393b54ea31b98f4fa","other":"2dc2914f76ec9b291b2085bea6d2e0f6d45beb55bba5e3a90059b96e83010318","up":true},{"one":"2aef8c4da4e38b6a885785e406912cef6087344b4be9e135e2c597468b003315","other":"2dc2914f76ec9b291b2085bea6d2e0f6d45beb55bba5e3a90059b96e83010318","up":true},{"one":"92e2f8ec301e5858f7168b785040955d28bffae0c98e5d3f59ff9fa8636fe423","other":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","up":true},{"one":"7692399f01177f2a8976d4c393a6813c53820e5f4edf54934414d1bcdfb8392c","other":"7e65067fa5db52d40c0a162432200ca6d0d3917b25cad15ce498fa6b5375bc8d","up":true},{"one":"cac97c7026184a2d6ba2b17fcd0c5440bec538961af836a60f2fadb78a310a77","other":"db596f4ff3f8c25835f9f10176e87a8823eaf1d39a513882f1ceab931dabfed9","up":true},{"one":"57dfed651ba7f0ab6f9e1d5d2d4ae18fee51a200f50925a88c861982bb4769cb","other":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","up":true},{"one":"ca9721b51497c5d570738eadc37c79fc8d5525749defb98c252371793c2a6f5c","other":"c7fd88864731e6b79acf436a76908b91c61ba07680699c2cda316179c3719e8a","up":true},{"one":"f836b3ca82f75bc0901d83b2c02e24174bee78c5eab9b55af9c492406942637a","other":"f3a1438cfc8b09a3a9c1fc8455ead9041e9532ef3e9a77ffee8e5ef62af2670c","up":true},{"one":"5d6da3260504f8063af24960ac901bdc0c54a126b82d415656a083c496c8b6b8","other":"3d3acee3657d98168e2f4e6463947646560b5ad1ca24b4a8a97e4284c39b02e1","up":true},{"one":"f3a1438cfc8b09a3a9c1fc8455ead9041e9532ef3e9a77ffee8e5ef62af2670c","other":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","up":true},{"one":"8bf59dc9ce97e6e2f2be364380aca38f98ddec248e730422f581f5bb56171e34","other":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","up":true},{"one":"7c766b57273777cc67f74870560449c9542cfec734b58b167aef99e09aeae962","other":"7e65067fa5db52d40c0a162432200ca6d0d3917b25cad15ce498fa6b5375bc8d","up":true},{"one":"f99503dc6a4ace3317320d8454b3ac1a5977f508ae810c4d506001ed0126838b","other":"f80e1ce23869c7da4453f06521fa320632057194c9f9b3dcdd777cc1f06603eb","up":true},{"one":"d7f956d0f6445202699bf63700da408237fdcecd27e2e8ad969e51d7e8e61483","other":"cac97c7026184a2d6ba2b17fcd0c5440bec538961af836a60f2fadb78a310a77","up":true},{"one":"8e317152297d5d048ca4512f733e63e48c3dc66910bad541d33f3cfc76828cb9","other":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","up":true},{"one":"e3c3f763b77a0bb72932d5d3dfca016e28d27b1c377690d2b074881b1a2a3259","other":"e5cd769dbcc429587e3a92b192575965c0ac18e88c9a54b9f75936d929c43c62","up":true},{"one":"d79838c419be793292a24feaf1b9d26c67f0c5afe15bae85689420de3436ad95","other":"d7f956d0f6445202699bf63700da408237fdcecd27e2e8ad969e51d7e8e61483","up":true},{"one":"004710b2e2194a8cd11292efe8b6f81bec4c1b673a9c8a45b7b5391f5f049895","other":"53eaa90913134a5bdaa780d18bcd33e85b1932d9b15bcc1096fc4658ea5ea7f8","up":true},{"one":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","other":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","up":true},{"one":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","other":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","up":true},{"one":"6e8315dc552c8112dcb01e9302b5e47fef7fbdc81fbe7a1d0b27e0cf80a65ca4","other":"6cf1d30b063cf8352795a92c6af26c154fc44be06c69ea9fd4d9f6dcc626eda1","up":true},{"one":"931a36980ca0788616cb1c9851f6069e44d94671a2a056e12a7d7cafe597dee5","other":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","up":true},{"one":"db596f4ff3f8c25835f9f10176e87a8823eaf1d39a513882f1ceab931dabfed9","other":"d87f983aefd8f732c29718a95f4dfc54a10640d5c9fdd73b772c3c30df269d88","up":true},{"one":"11a60d43d627470df1c1ebdc404093f6ae83c75b3a95469316b89a0185eeaa86","other":"1385e97b879da9021aba65b2df36b91650aafb354a09c5be8a9532d1eb7d472b","up":true},{"one":"d192c64e09879b5107507e193fea23b0244a574e0d66e444f7a325de32c123fd","other":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","up":true},{"one":"1f2a162a4e78c8935ed17eee206a8512ef37e126bb7ad9ff744f7b46c4b19aee","other":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","up":true},{"one":"7f620d306e05bac0f07838b7de6a2db69aa6494df3db09f26624ed167318a75a","other":"7f5f9f955e368c906746d42953e30e781a8eb73428af5aa608af96af9bf4f60a","up":true},{"one":"b9e5006b90bffae809136ad57e36b784b18e35f7491c47319807f6869d624465","other":"aa19fb3565a2677828b1384f483e3eb7ea112940d21d0aaa7b16139c753c69d7","up":true},{"one":"96b723de0840f656c914674ad20204da830c04d4d6956c32e20154cbcc40f9ef","other":"ac23190ee9ee30dd793bf3edef355450522d3f56973ab6b1acbb6dfd9d64137a","up":true},{"one":"6c1f82c3a7bc3274cfdac710caec4fc814c9f5a6797674457414dd94be577a9b","other":"6e8315dc552c8112dcb01e9302b5e47fef7fbdc81fbe7a1d0b27e0cf80a65ca4","up":true},{"one":"f80e1ce23869c7da4453f06521fa320632057194c9f9b3dcdd777cc1f06603eb","other":"f836b3ca82f75bc0901d83b2c02e24174bee78c5eab9b55af9c492406942637a","up":true},{"one":"61207f4fc13c899b88e9b727eb202a8bab00868bd07d945100b2e56dbcd67683","other":"4309c2d963d91e65f7e8e8ab42df52e75f8d377d4eb07d1a048c0082af4f501e","up":true},{"one":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","other":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","up":true},{"one":"a60b45dbab9c1de18783fed9eed8af6dabafbc33fdc5b3fef1ef4569bc6c70d9","other":"8c5b95bf1743e2d8a97210c5b64b6732f3615c4369fa3a52e426f93e168ae16f","up":true},{"one":"aa19fb3565a2677828b1384f483e3eb7ea112940d21d0aaa7b16139c753c69d7","other":"8c5b95bf1743e2d8a97210c5b64b6732f3615c4369fa3a52e426f93e168ae16f","up":true},{"one":"237b54aa7ef077972fc65fb222ce70df0129f8c8d4d35505fcbdc281bb7d28ce","other":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","up":true},{"one":"d55213d3dc27542a780472564a4a8c1ce47668e05710be178baf3238ca8886b6","other":"d7f956d0f6445202699bf63700da408237fdcecd27e2e8ad969e51d7e8e61483","up":true},{"one":"c35893ea35ab764005fd81ee10d1a9ab3f361e044aa53e28ee64effca928d09d","other":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","up":true},{"one":"44540a152f9fc924f10fefc013059d9ad228e74f921a67866ef28758f1886320","other":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","up":true},{"one":"1ea89c3f15b82385c5df8810ec7cbd30e8e4a4e05c1ddf5dbe7102edaa07afd2","other":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","up":true},{"one":"3e4fbab650cbd539383f7e26128c0aaa770f12c4a1fa44d553de9dda073f0cb2","other":"3d3acee3657d98168e2f4e6463947646560b5ad1ca24b4a8a97e4284c39b02e1","up":true},{"one":"d382e36db7f13180cca7b169e8511c9b920bb481a340d8a61dd9e4969a5605b6","other":"d192c64e09879b5107507e193fea23b0244a574e0d66e444f7a325de32c123fd","up":true},{"one":"1385e97b879da9021aba65b2df36b91650aafb354a09c5be8a9532d1eb7d472b","other":"18f6cc40773a42ddad3e3e6c5fd865a652cbb42e9054f0e46dfaed17a41d4f4f","up":true},{"one":"d68e095e77246c3c08df2eaec294965bca2d01fa2819d9cd06c6ef8730c59ab4","other":"d79838c419be793292a24feaf1b9d26c67f0c5afe15bae85689420de3436ad95","up":true},{"one":"609958921d9107eaae6f3141e94f12b3a28fec203eb0e5b674ed8268934becc7","other":"265040d12d0b79c8609232060b3ddd6ac16fb895b52bb7f3fee5c6e8b63a4d79","up":true},{"one":"d80b59b187b7765439b0e4c940f766ae71a903f599ac41a84d972e8703f7c9ff","other":"c7fd88864731e6b79acf436a76908b91c61ba07680699c2cda316179c3719e8a","up":true},{"one":"8fe212fc9af12821f59b5f364f86f78b545be501933b926fc47b6d7deafca941","other":"87e0232acc7127ea47604a5bfe84d80e38292902502c2a714e0f96d8090eb4fe","up":true},{"one":"7628bcf64532ca3ec293d26f60791a2a62542b2f5f3f1c782799075383ccde00","other":"7e65067fa5db52d40c0a162432200ca6d0d3917b25cad15ce498fa6b5375bc8d","up":true},{"one":"f2b809e97d3c3a652c4ddce19ca12b01c1de25f2ce007d561fd5606e85540d64","other":"f3a1438cfc8b09a3a9c1fc8455ead9041e9532ef3e9a77ffee8e5ef62af2670c","up":true},{"one":"740bebf4546e2ed32b2bd277555685e3be071aca4b1990c7c095e12e24fa4699","other":"265040d12d0b79c8609232060b3ddd6ac16fb895b52bb7f3fee5c6e8b63a4d79","up":true},{"one":"314e1e8a8d9ae18d0b5510341a6fab0f8654a7263a5db895480aca606b6a9859","other":"2dc2914f76ec9b291b2085bea6d2e0f6d45beb55bba5e3a90059b96e83010318","up":true},{"one":"b1bf7a0572c1efcf034bf42f54f7334743c59f77b67225aa3c7d93552074ab56","other":"aa19fb3565a2677828b1384f483e3eb7ea112940d21d0aaa7b16139c753c69d7","up":true},{"one":"e4254cda903275296f17b47e5dcabbf16d1fa7432b64e0e6e0562fcd89de534d","other":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","up":true},{"one":"53eaa90913134a5bdaa780d18bcd33e85b1932d9b15bcc1096fc4658ea5ea7f8","other":"78bfaf122d10ee5e5304fdd247434c02b32de577bb410dc04d8ffe1906294fdb","up":true},{"one":"059a2d0e5db2c0204c10b66ef04bacc96d64453073efeee81ba7e30d43061a61","other":"11a60d43d627470df1c1ebdc404093f6ae83c75b3a95469316b89a0185eeaa86","up":true},{"one":"b22078d9b5f3d57dec3fb1796599d5f04fcb32d4288939431ecea8516be76b58","other":"aa19fb3565a2677828b1384f483e3eb7ea112940d21d0aaa7b16139c753c69d7","up":true},{"one":"ea9490f17813e0e5f39e8065f4c3954163f3bca6e80afca7a2c76d5ca3116e53","other":"fba89f80c35e5f6ffbe6e5521f2011caec985d820fe42759ef1aaa36d4902f37","up":true},{"one":"2feed66b03b7b057e1a64a3f877ad9c28ada96e30b98a78393b54ea31b98f4fa","other":"1385e97b879da9021aba65b2df36b91650aafb354a09c5be8a9532d1eb7d472b","up":true},{"one":"e0ac6b34d04bc036026767288a8f0f96720f4a3efa5b52b3b86439fba562bcf4","other":"d68e095e77246c3c08df2eaec294965bca2d01fa2819d9cd06c6ef8730c59ab4","up":true},{"one":"2aef8c4da4e38b6a885785e406912cef6087344b4be9e135e2c597468b003315","other":"265040d12d0b79c8609232060b3ddd6ac16fb895b52bb7f3fee5c6e8b63a4d79","up":true},{"one":"92e2f8ec301e5858f7168b785040955d28bffae0c98e5d3f59ff9fa8636fe423","other":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","up":true},{"one":"c7fd88864731e6b79acf436a76908b91c61ba07680699c2cda316179c3719e8a","other":"d382e36db7f13180cca7b169e8511c9b920bb481a340d8a61dd9e4969a5605b6","up":true},{"one":"6aef7e94602e6e74ab4cdb57668fb751fa20807751f786d0232870da0ae32cfd","other":"6640a8343b810399525545ee1545a50e3c76548f776c83d55465071cb55a00b1","up":true},{"one":"ac23190ee9ee30dd793bf3edef355450522d3f56973ab6b1acbb6dfd9d64137a","other":"6e8315dc552c8112dcb01e9302b5e47fef7fbdc81fbe7a1d0b27e0cf80a65ca4","up":true},{"one":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","other":"6aef7e94602e6e74ab4cdb57668fb751fa20807751f786d0232870da0ae32cfd","up":true},{"one":"6632a8b23d42dfc55a103d625a3644f0067abaae554eb75a78b1094ad7b24c16","other":"7f620d306e05bac0f07838b7de6a2db69aa6494df3db09f26624ed167318a75a","up":true},{"one":"cac97c7026184a2d6ba2b17fcd0c5440bec538961af836a60f2fadb78a310a77","other":"c35893ea35ab764005fd81ee10d1a9ab3f361e044aa53e28ee64effca928d09d","up":true},{"one":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","other":"d192c64e09879b5107507e193fea23b0244a574e0d66e444f7a325de32c123fd","up":true},{"one":"6cf1d30b063cf8352795a92c6af26c154fc44be06c69ea9fd4d9f6dcc626eda1","other":"61207f4fc13c899b88e9b727eb202a8bab00868bd07d945100b2e56dbcd67683","up":true},{"one":"57dfed651ba7f0ab6f9e1d5d2d4ae18fee51a200f50925a88c861982bb4769cb","other":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","up":true},{"one":"78bfaf122d10ee5e5304fdd247434c02b32de577bb410dc04d8ffe1906294fdb","other":"18f6cc40773a42ddad3e3e6c5fd865a652cbb42e9054f0e46dfaed17a41d4f4f","up":true},{"one":"2dc2914f76ec9b291b2085bea6d2e0f6d45beb55bba5e3a90059b96e83010318","other":"f29ff736fb944f8f8339a4e10f69ee72f108ed1d68b5a80d06a814b0807e8686","up":true},{"one":"ca9721b51497c5d570738eadc37c79fc8d5525749defb98c252371793c2a6f5c","other":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","up":true},{"one":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","other":"e39e8092c97b53dbeaf5ca73d95c71e8e41a2906aaa377fe0c1b23752c8c7423","up":true},{"one":"500f2135db1adafe22d8a62af63f1659d6f4538f490ee8151e9db970fa24389b","other":"6a57cd355d641d1506957a988d93b2d3f9a613a7354c24130ad97dbc4c8296ce","up":true},{"one":"ebf9a6425b47a78df1c99fe4d62a48c26d2f4ef966a8592802d5b5e95e6c12eb","other":"f29ff736fb944f8f8339a4e10f69ee72f108ed1d68b5a80d06a814b0807e8686","up":true},{"one":"d192c64e09879b5107507e193fea23b0244a574e0d66e444f7a325de32c123fd","other":"d9169f80683a2552475d60e6982f70c6605f9b40300f2b60895aa21cdc42db89","up":true},{"one":"981be0d21bdb11ac807dc29937c6a3ac2ae3c2f45269c0edc21172d5550f4da9","other":"87e0232acc7127ea47604a5bfe84d80e38292902502c2a714e0f96d8090eb4fe","up":true},{"one":"5d6da3260504f8063af24960ac901bdc0c54a126b82d415656a083c496c8b6b8","other":"40871cbc7eff896f15e558e2c638ac980fd89c718881cd110a086c3397b564c0","up":true},{"one":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","other":"c35893ea35ab764005fd81ee10d1a9ab3f361e044aa53e28ee64effca928d09d","up":true},{"one":"7c766b57273777cc67f74870560449c9542cfec734b58b167aef99e09aeae962","other":"5d6da3260504f8063af24960ac901bdc0c54a126b82d415656a083c496c8b6b8","up":true},{"one":"f99503dc6a4ace3317320d8454b3ac1a5977f508ae810c4d506001ed0126838b","other":"f3a1438cfc8b09a3a9c1fc8455ead9041e9532ef3e9a77ffee8e5ef62af2670c","up":true},{"one":"30a0ef4bf1f66ab4d02a027a24ba1f9b288ea93e86a5ceab64e1985aa4efcbd0","other":"3f1ee2912d5446e4cdf7a42582f2e30a24bcddd6422a9b6a61f287da256d0bde","up":true},{"one":"e3c3f763b77a0bb72932d5d3dfca016e28d27b1c377690d2b074881b1a2a3259","other":"ca9721b51497c5d570738eadc37c79fc8d5525749defb98c252371793c2a6f5c","up":true},{"one":"4309c2d963d91e65f7e8e8ab42df52e75f8d377d4eb07d1a048c0082af4f501e","other":"40871cbc7eff896f15e558e2c638ac980fd89c718881cd110a086c3397b564c0","up":true},{"one":"8ec663c22eff8ed185b8194c1240ff58df131c5f310df0a91b9790ec166e4346","other":"87e0232acc7127ea47604a5bfe84d80e38292902502c2a714e0f96d8090eb4fe","up":true},{"one":"fba89f80c35e5f6ffbe6e5521f2011caec985d820fe42759ef1aaa36d4902f37","other":"d9169f80683a2552475d60e6982f70c6605f9b40300f2b60895aa21cdc42db89","up":true},{"one":"931a36980ca0788616cb1c9851f6069e44d94671a2a056e12a7d7cafe597dee5","other":"954a22c6fd7d63fce47d8685d32bb892bcc0feab4209d5dc7c3601efc28e7d6d","up":true},{"one":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","other":"609958921d9107eaae6f3141e94f12b3a28fec203eb0e5b674ed8268934becc7","up":true},{"one":"3d3acee3657d98168e2f4e6463947646560b5ad1ca24b4a8a97e4284c39b02e1","other":"7692399f01177f2a8976d4c393a6813c53820e5f4edf54934414d1bcdfb8392c","up":true},{"one":"aec520f507eedd5d752b6ba8a0b509f14105e5e457eef0b6537015e39569762b","other":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","up":true},{"one":"db596f4ff3f8c25835f9f10176e87a8823eaf1d39a513882f1ceab931dabfed9","other":"d80b59b187b7765439b0e4c940f766ae71a903f599ac41a84d972e8703f7c9ff","up":true},{"one":"11a60d43d627470df1c1ebdc404093f6ae83c75b3a95469316b89a0185eeaa86","other":"1f2a162a4e78c8935ed17eee206a8512ef37e126bb7ad9ff744f7b46c4b19aee","up":true},{"one":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","other":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","up":true},{"one":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","other":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","up":true},{"one":"6ac71d55e984abe323c74ea85ac1aed1b06e56632c07c389e780268274a23810","other":"7f5f9f955e368c906746d42953e30e781a8eb73428af5aa608af96af9bf4f60a","up":true},{"one":"265040d12d0b79c8609232060b3ddd6ac16fb895b52bb7f3fee5c6e8b63a4d79","other":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","up":true},{"one":"7f620d306e05bac0f07838b7de6a2db69aa6494df3db09f26624ed167318a75a","other":"7692399f01177f2a8976d4c393a6813c53820e5f4edf54934414d1bcdfb8392c","up":true},{"one":"b9e5006b90bffae809136ad57e36b784b18e35f7491c47319807f6869d624465","other":"a60b45dbab9c1de18783fed9eed8af6dabafbc33fdc5b3fef1ef4569bc6c70d9","up":true},{"one":"1ea89c3f15b82385c5df8810ec7cbd30e8e4a4e05c1ddf5dbe7102edaa07afd2","other":"1f2a162a4e78c8935ed17eee206a8512ef37e126bb7ad9ff744f7b46c4b19aee","up":true},{"one":"3e4fbab650cbd539383f7e26128c0aaa770f12c4a1fa44d553de9dda073f0cb2","other":"3f1ee2912d5446e4cdf7a42582f2e30a24bcddd6422a9b6a61f287da256d0bde","up":true},{"one":"6c1f82c3a7bc3274cfdac710caec4fc814c9f5a6797674457414dd94be577a9b","other":"61207f4fc13c899b88e9b727eb202a8bab00868bd07d945100b2e56dbcd67683","up":true},{"one":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","other":"c7fd88864731e6b79acf436a76908b91c61ba07680699c2cda316179c3719e8a","up":true},{"one":"8c5b95bf1743e2d8a97210c5b64b6732f3615c4369fa3a52e426f93e168ae16f","other":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","up":true},{"one":"2a2bcb40404a427a50538ad9d1bfff1c5b1a7673990cbd20d830787fd96a00e5","other":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","up":true},{"one":"4124e83515480818c250a3ca2d8a7b159c890983c35165ee531d2e15066c7203","other":"1943c1442da29872e77e4cfd282e9a81490c613e99cbe4503e470b7f23ce75fb","up":true},{"one":"a60b45dbab9c1de18783fed9eed8af6dabafbc33fdc5b3fef1ef4569bc6c70d9","other":"b1bf7a0572c1efcf034bf42f54f7334743c59f77b67225aa3c7d93552074ab56","up":true},{"one":"f80e1ce23869c7da4453f06521fa320632057194c9f9b3dcdd777cc1f06603eb","other":"8e317152297d5d048ca4512f733e63e48c3dc66910bad541d33f3cfc76828cb9","up":true},{"one":"311fc84bce90b1bc1d1983c78e45b1e82a703cdb7cfbe54d51e9bc40cfe04103","other":"7f620d306e05bac0f07838b7de6a2db69aa6494df3db09f26624ed167318a75a","up":true},{"one":"dc2ae81b708a14f8b6e134986075835994c71d284b9bd9fa19d2efa73346f35e","other":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","up":true},{"one":"c35893ea35ab764005fd81ee10d1a9ab3f361e044aa53e28ee64effca928d09d","other":"8c5b95bf1743e2d8a97210c5b64b6732f3615c4369fa3a52e426f93e168ae16f","up":true},{"one":"44540a152f9fc924f10fefc013059d9ad228e74f921a67866ef28758f1886320","other":"500f2135db1adafe22d8a62af63f1659d6f4538f490ee8151e9db970fa24389b","up":true},{"one":"237b54aa7ef077972fc65fb222ce70df0129f8c8d4d35505fcbdc281bb7d28ce","other":"1943c1442da29872e77e4cfd282e9a81490c613e99cbe4503e470b7f23ce75fb","up":true},{"one":"d3864c160e6d8fcb3f540b864e6ee11d91ca708f2cbe1e5fd04beb2b6c1d8930","other":"d79838c419be793292a24feaf1b9d26c67f0c5afe15bae85689420de3436ad95","up":true},{"one":"6838b88ff71c5419d3fd22a4c007a7b822405b1842d53427a9c075d093c13047","other":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","up":true},{"one":"f07c1ee0f9cadf2971a0cb0022d1c67fa82c79f63cb98769b31ccfb04d05c0a8","other":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","up":true},{"one":"4ae614490d8739205773d16c8027c46b67d93bd336c33071d8707fea6ac1acf3","other":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","up":true},{"one":"954a22c6fd7d63fce47d8685d32bb892bcc0feab4209d5dc7c3601efc28e7d6d","other":"a12e73ee9cf7369825c1423608ce079cf70df75d0f0bbc56d07d484169fca26d","up":true},{"one":"83bc840da4e1f9201f0e85db2c44364f06c188fedef30fd2f72cbde70f6cfdb9","other":"8fe212fc9af12821f59b5f364f86f78b545be501933b926fc47b6d7deafca941","up":true},{"one":"97a5959b95662f6e820a1b011a7e87568e0efd2257a73bf91868c98c46e5a571","other":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","up":true},{"one":"1385e97b879da9021aba65b2df36b91650aafb354a09c5be8a9532d1eb7d472b","other":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","up":true},{"one":"609958921d9107eaae6f3141e94f12b3a28fec203eb0e5b674ed8268934becc7","other":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","up":true},{"one":"740bebf4546e2ed32b2bd277555685e3be071aca4b1990c7c095e12e24fa4699","other":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","up":true},{"one":"d79838c419be793292a24feaf1b9d26c67f0c5afe15bae85689420de3436ad95","other":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","up":true},{"one":"18f6cc40773a42ddad3e3e6c5fd865a652cbb42e9054f0e46dfaed17a41d4f4f","other":"11a60d43d627470df1c1ebdc404093f6ae83c75b3a95469316b89a0185eeaa86","up":true},{"one":"314e1e8a8d9ae18d0b5510341a6fab0f8654a7263a5db895480aca606b6a9859","other":"2feed66b03b7b057e1a64a3f877ad9c28ada96e30b98a78393b54ea31b98f4fa","up":true},{"one":"b1bf7a0572c1efcf034bf42f54f7334743c59f77b67225aa3c7d93552074ab56","other":"b9e5006b90bffae809136ad57e36b784b18e35f7491c47319807f6869d624465","up":true},{"one":"b22078d9b5f3d57dec3fb1796599d5f04fcb32d4288939431ecea8516be76b58","other":"b9e5006b90bffae809136ad57e36b784b18e35f7491c47319807f6869d624465","up":true},{"one":"6e8315dc552c8112dcb01e9302b5e47fef7fbdc81fbe7a1d0b27e0cf80a65ca4","other":"6838b88ff71c5419d3fd22a4c007a7b822405b1842d53427a9c075d093c13047","up":true},{"one":"e0ac6b34d04bc036026767288a8f0f96720f4a3efa5b52b3b86439fba562bcf4","other":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","up":true},{"one":"2feed66b03b7b057e1a64a3f877ad9c28ada96e30b98a78393b54ea31b98f4fa","other":"3f1ee2912d5446e4cdf7a42582f2e30a24bcddd6422a9b6a61f287da256d0bde","up":true},{"one":"9f9700ca395f36c84a38838c11f6889d00015d97aac7943c27cf74393e3fc22f","other":"87e0232acc7127ea47604a5bfe84d80e38292902502c2a714e0f96d8090eb4fe","up":true},{"one":"92e2f8ec301e5858f7168b785040955d28bffae0c98e5d3f59ff9fa8636fe423","other":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","up":true},{"one":"6640a8343b810399525545ee1545a50e3c76548f776c83d55465071cb55a00b1","other":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","up":true},{"one":"1943c1442da29872e77e4cfd282e9a81490c613e99cbe4503e470b7f23ce75fb","other":"5d6da3260504f8063af24960ac901bdc0c54a126b82d415656a083c496c8b6b8","up":true},{"one":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","other":"6cf1d30b063cf8352795a92c6af26c154fc44be06c69ea9fd4d9f6dcc626eda1","up":true},{"one":"cac97c7026184a2d6ba2b17fcd0c5440bec538961af836a60f2fadb78a310a77","other":"ca9721b51497c5d570738eadc37c79fc8d5525749defb98c252371793c2a6f5c","up":true},{"one":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","other":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","up":true},{"one":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","other":"11a60d43d627470df1c1ebdc404093f6ae83c75b3a95469316b89a0185eeaa86","up":true},{"one":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","other":"1943c1442da29872e77e4cfd282e9a81490c613e99cbe4503e470b7f23ce75fb","up":true},{"one":"57dfed651ba7f0ab6f9e1d5d2d4ae18fee51a200f50925a88c861982bb4769cb","other":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","up":true},{"one":"e39e8092c97b53dbeaf5ca73d95c71e8e41a2906aaa377fe0c1b23752c8c7423","other":"a60b45dbab9c1de18783fed9eed8af6dabafbc33fdc5b3fef1ef4569bc6c70d9","up":true},{"one":"06db6e244213745e02175cca1101e72799c09236838affd8a020a05e5b88e2bd","other":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","up":true},{"one":"f29ff736fb944f8f8339a4e10f69ee72f108ed1d68b5a80d06a814b0807e8686","other":"e5cd769dbcc429587e3a92b192575965c0ac18e88c9a54b9f75936d929c43c62","up":true},{"one":"f2b809e97d3c3a652c4ddce19ca12b01c1de25f2ce007d561fd5606e85540d64","other":"fa62b685ae8a9aac669d245102ce68b030f0f270d95d7c88969bfdc9978f9070","up":true},{"one":"059a2d0e5db2c0204c10b66ef04bacc96d64453073efeee81ba7e30d43061a61","other":"1f2a162a4e78c8935ed17eee206a8512ef37e126bb7ad9ff744f7b46c4b19aee","up":true},{"one":"d9169f80683a2552475d60e6982f70c6605f9b40300f2b60895aa21cdc42db89","other":"f29ff736fb944f8f8339a4e10f69ee72f108ed1d68b5a80d06a814b0807e8686","up":true},{"one":"ea9490f17813e0e5f39e8065f4c3954163f3bca6e80afca7a2c76d5ca3116e53","other":"e4254cda903275296f17b47e5dcabbf16d1fa7432b64e0e6e0562fcd89de534d","up":true},{"one":"d192c64e09879b5107507e193fea23b0244a574e0d66e444f7a325de32c123fd","other":"d3864c160e6d8fcb3f540b864e6ee11d91ca708f2cbe1e5fd04beb2b6c1d8930","up":true},{"one":"d87f983aefd8f732c29718a95f4dfc54a10640d5c9fdd73b772c3c30df269d88","other":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","up":true},{"one":"bf7b0b9825b9c878f7730014c7458fc52ee3490c98ee5ae2f647a8d73e4464f3","other":"8ec663c22eff8ed185b8194c1240ff58df131c5f310df0a91b9790ec166e4346","up":true},{"one":"5d6da3260504f8063af24960ac901bdc0c54a126b82d415656a083c496c8b6b8","other":"57dfed651ba7f0ab6f9e1d5d2d4ae18fee51a200f50925a88c861982bb4769cb","up":true},{"one":"004710b2e2194a8cd11292efe8b6f81bec4c1b673a9c8a45b7b5391f5f049895","other":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","up":true},{"one":"7c766b57273777cc67f74870560449c9542cfec734b58b167aef99e09aeae962","other":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","up":true},{"one":"a2504bc38c94b7912bfafc42c7a4531cd27775473071e7a26173c4db932bf9ec","other":"e0ac6b34d04bc036026767288a8f0f96720f4a3efa5b52b3b86439fba562bcf4","up":true},{"one":"f07c1ee0f9cadf2971a0cb0022d1c67fa82c79f63cb98769b31ccfb04d05c0a8","other":"f3a1438cfc8b09a3a9c1fc8455ead9041e9532ef3e9a77ffee8e5ef62af2670c","up":true},{"one":"7f5f9f955e368c906746d42953e30e781a8eb73428af5aa608af96af9bf4f60a","other":"83119770a801a40f0f4015ce8a8cc6cfa6dbc211deaee193c5db05130002f9a7","up":true},{"one":"e5cd769dbcc429587e3a92b192575965c0ac18e88c9a54b9f75936d929c43c62","other":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","up":true},{"one":"500f2135db1adafe22d8a62af63f1659d6f4538f490ee8151e9db970fa24389b","other":"2aef8c4da4e38b6a885785e406912cef6087344b4be9e135e2c597468b003315","up":true},{"one":"96b723de0840f656c914674ad20204da830c04d4d6956c32e20154cbcc40f9ef","other":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","up":true},{"one":"e5cd769dbcc429587e3a92b192575965c0ac18e88c9a54b9f75936d929c43c62","other":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","up":true},{"one":"4309c2d963d91e65f7e8e8ab42df52e75f8d377d4eb07d1a048c0082af4f501e","other":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","up":true},{"one":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","other":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","up":true},{"one":"931a36980ca0788616cb1c9851f6069e44d94671a2a056e12a7d7cafe597dee5","other":"92e2f8ec301e5858f7168b785040955d28bffae0c98e5d3f59ff9fa8636fe423","up":true},{"one":"db596f4ff3f8c25835f9f10176e87a8823eaf1d39a513882f1ceab931dabfed9","other":"f99503dc6a4ace3317320d8454b3ac1a5977f508ae810c4d506001ed0126838b","up":true},{"one":"6e8315dc552c8112dcb01e9302b5e47fef7fbdc81fbe7a1d0b27e0cf80a65ca4","other":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","up":true},{"one":"11a60d43d627470df1c1ebdc404093f6ae83c75b3a95469316b89a0185eeaa86","other":"1943c1442da29872e77e4cfd282e9a81490c613e99cbe4503e470b7f23ce75fb","up":true},{"one":"9f9700ca395f36c84a38838c11f6889d00015d97aac7943c27cf74393e3fc22f","other":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","up":true},{"one":"7f620d306e05bac0f07838b7de6a2db69aa6494df3db09f26624ed167318a75a","other":"78bfaf122d10ee5e5304fdd247434c02b32de577bb410dc04d8ffe1906294fdb","up":true},{"one":"b9e5006b90bffae809136ad57e36b784b18e35f7491c47319807f6869d624465","other":"bf7b0b9825b9c878f7730014c7458fc52ee3490c98ee5ae2f647a8d73e4464f3","up":true},{"one":"3e4fbab650cbd539383f7e26128c0aaa770f12c4a1fa44d553de9dda073f0cb2","other":"265040d12d0b79c8609232060b3ddd6ac16fb895b52bb7f3fee5c6e8b63a4d79","up":true},{"one":"6c1f82c3a7bc3274cfdac710caec4fc814c9f5a6797674457414dd94be577a9b","other":"6a57cd355d641d1506957a988d93b2d3f9a613a7354c24130ad97dbc4c8296ce","up":true},{"one":"1ea89c3f15b82385c5df8810ec7cbd30e8e4a4e05c1ddf5dbe7102edaa07afd2","other":"18f6cc40773a42ddad3e3e6c5fd865a652cbb42e9054f0e46dfaed17a41d4f4f","up":true},{"one":"f80e1ce23869c7da4453f06521fa320632057194c9f9b3dcdd777cc1f06603eb","other":"f07c1ee0f9cadf2971a0cb0022d1c67fa82c79f63cb98769b31ccfb04d05c0a8","up":true},{"one":"44540a152f9fc924f10fefc013059d9ad228e74f921a67866ef28758f1886320","other":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","up":true},{"one":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","other":"53eaa90913134a5bdaa780d18bcd33e85b1932d9b15bcc1096fc4658ea5ea7f8","up":true},{"one":"1385e97b879da9021aba65b2df36b91650aafb354a09c5be8a9532d1eb7d472b","other":"1ea89c3f15b82385c5df8810ec7cbd30e8e4a4e05c1ddf5dbe7102edaa07afd2","up":true},{"one":"18f6cc40773a42ddad3e3e6c5fd865a652cbb42e9054f0e46dfaed17a41d4f4f","other":"1943c1442da29872e77e4cfd282e9a81490c613e99cbe4503e470b7f23ce75fb","up":true},{"one":"e39e8092c97b53dbeaf5ca73d95c71e8e41a2906aaa377fe0c1b23752c8c7423","other":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","up":true},{"one":"314e1e8a8d9ae18d0b5510341a6fab0f8654a7263a5db895480aca606b6a9859","other":"30a0ef4bf1f66ab4d02a027a24ba1f9b288ea93e86a5ceab64e1985aa4efcbd0","up":true},{"one":"e4254cda903275296f17b47e5dcabbf16d1fa7432b64e0e6e0562fcd89de534d","other":"e39e8092c97b53dbeaf5ca73d95c71e8e41a2906aaa377fe0c1b23752c8c7423","up":true},{"one":"fa62b685ae8a9aac669d245102ce68b030f0f270d95d7c88969bfdc9978f9070","other":"e5cd769dbcc429587e3a92b192575965c0ac18e88c9a54b9f75936d929c43c62","up":true},{"one":"ca9721b51497c5d570738eadc37c79fc8d5525749defb98c252371793c2a6f5c","other":"c35893ea35ab764005fd81ee10d1a9ab3f361e044aa53e28ee64effca928d09d","up":true},{"one":"f3a1438cfc8b09a3a9c1fc8455ead9041e9532ef3e9a77ffee8e5ef62af2670c","other":"ca9721b51497c5d570738eadc37c79fc8d5525749defb98c252371793c2a6f5c","up":true},{"one":"8bf59dc9ce97e6e2f2be364380aca38f98ddec248e730422f581f5bb56171e34","other":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","up":true},{"one":"d7f956d0f6445202699bf63700da408237fdcecd27e2e8ad969e51d7e8e61483","other":"83119770a801a40f0f4015ce8a8cc6cfa6dbc211deaee193c5db05130002f9a7","up":true},{"one":"8e317152297d5d048ca4512f733e63e48c3dc66910bad541d33f3cfc76828cb9","other":"a12e73ee9cf7369825c1423608ce079cf70df75d0f0bbc56d07d484169fca26d","up":true},{"one":"e0ac6b34d04bc036026767288a8f0f96720f4a3efa5b52b3b86439fba562bcf4","other":"e3c3f763b77a0bb72932d5d3dfca016e28d27b1c377690d2b074881b1a2a3259","up":true},{"one":"2feed66b03b7b057e1a64a3f877ad9c28ada96e30b98a78393b54ea31b98f4fa","other":"2a2bcb40404a427a50538ad9d1bfff1c5b1a7673990cbd20d830787fd96a00e5","up":true},{"one":"6aef7e94602e6e74ab4cdb57668fb751fa20807751f786d0232870da0ae32cfd","other":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","up":true},{"one":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","other":"8e317152297d5d048ca4512f733e63e48c3dc66910bad541d33f3cfc76828cb9","up":true},{"one":"6632a8b23d42dfc55a103d625a3644f0067abaae554eb75a78b1094ad7b24c16","other":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","up":true},{"one":"6cf1d30b063cf8352795a92c6af26c154fc44be06c69ea9fd4d9f6dcc626eda1","other":"7f5f9f955e368c906746d42953e30e781a8eb73428af5aa608af96af9bf4f60a","up":true},{"one":"1f2a162a4e78c8935ed17eee206a8512ef37e126bb7ad9ff744f7b46c4b19aee","other":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","up":true},{"one":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","other":"40871cbc7eff896f15e558e2c638ac980fd89c718881cd110a086c3397b564c0","up":true},{"one":"cac97c7026184a2d6ba2b17fcd0c5440bec538961af836a60f2fadb78a310a77","other":"c7fd88864731e6b79acf436a76908b91c61ba07680699c2cda316179c3719e8a","up":true},{"one":"2aef8c4da4e38b6a885785e406912cef6087344b4be9e135e2c597468b003315","other":"2a2bcb40404a427a50538ad9d1bfff1c5b1a7673990cbd20d830787fd96a00e5","up":true},{"one":"954a22c6fd7d63fce47d8685d32bb892bcc0feab4209d5dc7c3601efc28e7d6d","other":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","up":true},{"one":"57dfed651ba7f0ab6f9e1d5d2d4ae18fee51a200f50925a88c861982bb4769cb","other":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","up":true},{"one":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","other":"e4254cda903275296f17b47e5dcabbf16d1fa7432b64e0e6e0562fcd89de534d","up":true},{"one":"61207f4fc13c899b88e9b727eb202a8bab00868bd07d945100b2e56dbcd67683","other":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","up":true},{"one":"feb3455336b26365b81dfc8cba2a390690ce996e07fcb7f1226720d263205adf","other":"d87f983aefd8f732c29718a95f4dfc54a10640d5c9fdd73b772c3c30df269d88","up":true},{"one":"d192c64e09879b5107507e193fea23b0244a574e0d66e444f7a325de32c123fd","other":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","up":true},{"one":"5d6da3260504f8063af24960ac901bdc0c54a126b82d415656a083c496c8b6b8","other":"53eaa90913134a5bdaa780d18bcd33e85b1932d9b15bcc1096fc4658ea5ea7f8","up":true},{"one":"4124e83515480818c250a3ca2d8a7b159c890983c35165ee531d2e15066c7203","other":"40871cbc7eff896f15e558e2c638ac980fd89c718881cd110a086c3397b564c0","up":true},{"one":"aec520f507eedd5d752b6ba8a0b509f14105e5e457eef0b6537015e39569762b","other":"1ea89c3f15b82385c5df8810ec7cbd30e8e4a4e05c1ddf5dbe7102edaa07afd2","up":true},{"one":"db596f4ff3f8c25835f9f10176e87a8823eaf1d39a513882f1ceab931dabfed9","other":"dc2ae81b708a14f8b6e134986075835994c71d284b9bd9fa19d2efa73346f35e","up":true},{"one":"b9e5006b90bffae809136ad57e36b784b18e35f7491c47319807f6869d624465","other":"8c5b95bf1743e2d8a97210c5b64b6732f3615c4369fa3a52e426f93e168ae16f","up":true},{"one":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","other":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","up":true},{"one":"6ac71d55e984abe323c74ea85ac1aed1b06e56632c07c389e780268274a23810","other":"7f620d306e05bac0f07838b7de6a2db69aa6494df3db09f26624ed167318a75a","up":true},{"one":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","other":"8ec663c22eff8ed185b8194c1240ff58df131c5f310df0a91b9790ec166e4346","up":true},{"one":"6c1f82c3a7bc3274cfdac710caec4fc814c9f5a6797674457414dd94be577a9b","other":"6838b88ff71c5419d3fd22a4c007a7b822405b1842d53427a9c075d093c13047","up":true},{"one":"d80b59b187b7765439b0e4c940f766ae71a903f599ac41a84d972e8703f7c9ff","other":"a12e73ee9cf7369825c1423608ce079cf70df75d0f0bbc56d07d484169fca26d","up":true},{"one":"237b54aa7ef077972fc65fb222ce70df0129f8c8d4d35505fcbdc281bb7d28ce","other":"30a0ef4bf1f66ab4d02a027a24ba1f9b288ea93e86a5ceab64e1985aa4efcbd0","up":true},{"one":"53eaa90913134a5bdaa780d18bcd33e85b1932d9b15bcc1096fc4658ea5ea7f8","other":"6ac71d55e984abe323c74ea85ac1aed1b06e56632c07c389e780268274a23810","up":true},{"one":"44540a152f9fc924f10fefc013059d9ad228e74f921a67866ef28758f1886320","other":"4309c2d963d91e65f7e8e8ab42df52e75f8d377d4eb07d1a048c0082af4f501e","up":true},{"one":"d9169f80683a2552475d60e6982f70c6605f9b40300f2b60895aa21cdc42db89","other":"d7f956d0f6445202699bf63700da408237fdcecd27e2e8ad969e51d7e8e61483","up":true},{"one":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","other":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","up":true},{"one":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","other":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","up":true},{"one":"92e2f8ec301e5858f7168b785040955d28bffae0c98e5d3f59ff9fa8636fe423","other":"ac23190ee9ee30dd793bf3edef355450522d3f56973ab6b1acbb6dfd9d64137a","up":true},{"one":"78bfaf122d10ee5e5304fdd247434c02b32de577bb410dc04d8ffe1906294fdb","other":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","up":true},{"one":"2dc2914f76ec9b291b2085bea6d2e0f6d45beb55bba5e3a90059b96e83010318","other":"fa62b685ae8a9aac669d245102ce68b030f0f270d95d7c88969bfdc9978f9070","up":true},{"one":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","other":"e0ac6b34d04bc036026767288a8f0f96720f4a3efa5b52b3b86439fba562bcf4","up":true},{"one":"1385e97b879da9021aba65b2df36b91650aafb354a09c5be8a9532d1eb7d472b","other":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","up":true},{"one":"ebf9a6425b47a78df1c99fe4d62a48c26d2f4ef966a8592802d5b5e95e6c12eb","other":"dc2ae81b708a14f8b6e134986075835994c71d284b9bd9fa19d2efa73346f35e","up":true},{"one":"981be0d21bdb11ac807dc29937c6a3ac2ae3c2f45269c0edc21172d5550f4da9","other":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","up":true},{"one":"d79838c419be793292a24feaf1b9d26c67f0c5afe15bae85689420de3436ad95","other":"c35893ea35ab764005fd81ee10d1a9ab3f361e044aa53e28ee64effca928d09d","up":true},{"one":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","other":"d382e36db7f13180cca7b169e8511c9b920bb481a340d8a61dd9e4969a5605b6","up":true},{"one":"18f6cc40773a42ddad3e3e6c5fd865a652cbb42e9054f0e46dfaed17a41d4f4f","other":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","up":true},{"one":"314e1e8a8d9ae18d0b5510341a6fab0f8654a7263a5db895480aca606b6a9859","other":"311fc84bce90b1bc1d1983c78e45b1e82a703cdb7cfbe54d51e9bc40cfe04103","up":true},{"one":"e4254cda903275296f17b47e5dcabbf16d1fa7432b64e0e6e0562fcd89de534d","other":"e3c3f763b77a0bb72932d5d3dfca016e28d27b1c377690d2b074881b1a2a3259","up":true},{"one":"3f1ee2912d5446e4cdf7a42582f2e30a24bcddd6422a9b6a61f287da256d0bde","other":"3d3acee3657d98168e2f4e6463947646560b5ad1ca24b4a8a97e4284c39b02e1","up":true},{"one":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","other":"6632a8b23d42dfc55a103d625a3644f0067abaae554eb75a78b1094ad7b24c16","up":true},{"one":"2feed66b03b7b057e1a64a3f877ad9c28ada96e30b98a78393b54ea31b98f4fa","other":"265040d12d0b79c8609232060b3ddd6ac16fb895b52bb7f3fee5c6e8b63a4d79","up":true},{"one":"e0ac6b34d04bc036026767288a8f0f96720f4a3efa5b52b3b86439fba562bcf4","other":"e39e8092c97b53dbeaf5ca73d95c71e8e41a2906aaa377fe0c1b23752c8c7423","up":true},{"one":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","other":"d87f983aefd8f732c29718a95f4dfc54a10640d5c9fdd73b772c3c30df269d88","up":true},{"one":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","other":"4309c2d963d91e65f7e8e8ab42df52e75f8d377d4eb07d1a048c0082af4f501e","up":true},{"one":"1ea89c3f15b82385c5df8810ec7cbd30e8e4a4e05c1ddf5dbe7102edaa07afd2","other":"1943c1442da29872e77e4cfd282e9a81490c613e99cbe4503e470b7f23ce75fb","up":true},{"one":"3e4fbab650cbd539383f7e26128c0aaa770f12c4a1fa44d553de9dda073f0cb2","other":"2dc2914f76ec9b291b2085bea6d2e0f6d45beb55bba5e3a90059b96e83010318","up":true},{"one":"cac97c7026184a2d6ba2b17fcd0c5440bec538961af836a60f2fadb78a310a77","other":"ac23190ee9ee30dd793bf3edef355450522d3f56973ab6b1acbb6dfd9d64137a","up":true},{"one":"57dfed651ba7f0ab6f9e1d5d2d4ae18fee51a200f50925a88c861982bb4769cb","other":"500f2135db1adafe22d8a62af63f1659d6f4538f490ee8151e9db970fa24389b","up":true},{"one":"f2b809e97d3c3a652c4ddce19ca12b01c1de25f2ce007d561fd5606e85540d64","other":"fba89f80c35e5f6ffbe6e5521f2011caec985d820fe42759ef1aaa36d4902f37","up":true},{"one":"059a2d0e5db2c0204c10b66ef04bacc96d64453073efeee81ba7e30d43061a61","other":"6632a8b23d42dfc55a103d625a3644f0067abaae554eb75a78b1094ad7b24c16","up":true},{"one":"8c5b95bf1743e2d8a97210c5b64b6732f3615c4369fa3a52e426f93e168ae16f","other":"a12e73ee9cf7369825c1423608ce079cf70df75d0f0bbc56d07d484169fca26d","up":true},{"one":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","other":"ea9490f17813e0e5f39e8065f4c3954163f3bca6e80afca7a2c76d5ca3116e53","up":true},{"one":"a12e73ee9cf7369825c1423608ce079cf70df75d0f0bbc56d07d484169fca26d","other":"a2504bc38c94b7912bfafc42c7a4531cd27775473071e7a26173c4db932bf9ec","up":true},{"one":"311fc84bce90b1bc1d1983c78e45b1e82a703cdb7cfbe54d51e9bc40cfe04103","other":"7e65067fa5db52d40c0a162432200ca6d0d3917b25cad15ce498fa6b5375bc8d","up":true},{"one":"f836b3ca82f75bc0901d83b2c02e24174bee78c5eab9b55af9c492406942637a","other":"fa62b685ae8a9aac669d245102ce68b030f0f270d95d7c88969bfdc9978f9070","up":true},{"one":"dc2ae81b708a14f8b6e134986075835994c71d284b9bd9fa19d2efa73346f35e","other":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","up":true},{"one":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","other":"f836b3ca82f75bc0901d83b2c02e24174bee78c5eab9b55af9c492406942637a","up":true},{"one":"d3864c160e6d8fcb3f540b864e6ee11d91ca708f2cbe1e5fd04beb2b6c1d8930","other":"c35893ea35ab764005fd81ee10d1a9ab3f361e044aa53e28ee64effca928d09d","up":true},{"one":"6838b88ff71c5419d3fd22a4c007a7b822405b1842d53427a9c075d093c13047","other":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","up":true},{"one":"5d6da3260504f8063af24960ac901bdc0c54a126b82d415656a083c496c8b6b8","other":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","up":true},{"one":"f99503dc6a4ace3317320d8454b3ac1a5977f508ae810c4d506001ed0126838b","other":"fa62b685ae8a9aac669d245102ce68b030f0f270d95d7c88969bfdc9978f9070","up":true},{"one":"4309c2d963d91e65f7e8e8ab42df52e75f8d377d4eb07d1a048c0082af4f501e","other":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","up":true},{"one":"4124e83515480818c250a3ca2d8a7b159c890983c35165ee531d2e15066c7203","other":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","up":true},{"one":"4ae614490d8739205773d16c8027c46b67d93bd336c33071d8707fea6ac1acf3","other":"3f1ee2912d5446e4cdf7a42582f2e30a24bcddd6422a9b6a61f287da256d0bde","up":true},{"one":"97a5959b95662f6e820a1b011a7e87568e0efd2257a73bf91868c98c46e5a571","other":"981be0d21bdb11ac807dc29937c6a3ac2ae3c2f45269c0edc21172d5550f4da9","up":true},{"one":"f80e1ce23869c7da4453f06521fa320632057194c9f9b3dcdd777cc1f06603eb","other":"fa62b685ae8a9aac669d245102ce68b030f0f270d95d7c88969bfdc9978f9070","up":true},{"one":"a60b45dbab9c1de18783fed9eed8af6dabafbc33fdc5b3fef1ef4569bc6c70d9","other":"a2504bc38c94b7912bfafc42c7a4531cd27775473071e7a26173c4db932bf9ec","up":true},{"one":"fba89f80c35e5f6ffbe6e5521f2011caec985d820fe42759ef1aaa36d4902f37","other":"d7f956d0f6445202699bf63700da408237fdcecd27e2e8ad969e51d7e8e61483","up":true},{"one":"3d3acee3657d98168e2f4e6463947646560b5ad1ca24b4a8a97e4284c39b02e1","other":"6e8315dc552c8112dcb01e9302b5e47fef7fbdc81fbe7a1d0b27e0cf80a65ca4","up":true},{"one":"237b54aa7ef077972fc65fb222ce70df0129f8c8d4d35505fcbdc281bb7d28ce","other":"2aef8c4da4e38b6a885785e406912cef6087344b4be9e135e2c597468b003315","up":true},{"one":"44540a152f9fc924f10fefc013059d9ad228e74f921a67866ef28758f1886320","other":"4124e83515480818c250a3ca2d8a7b159c890983c35165ee531d2e15066c7203","up":true},{"one":"265040d12d0b79c8609232060b3ddd6ac16fb895b52bb7f3fee5c6e8b63a4d79","other":"6838b88ff71c5419d3fd22a4c007a7b822405b1842d53427a9c075d093c13047","up":true},{"one":"2a2bcb40404a427a50538ad9d1bfff1c5b1a7673990cbd20d830787fd96a00e5","other":"b9e5006b90bffae809136ad57e36b784b18e35f7491c47319807f6869d624465","up":true},{"one":"bf7b0b9825b9c878f7730014c7458fc52ee3490c98ee5ae2f647a8d73e4464f3","other":"9f9700ca395f36c84a38838c11f6889d00015d97aac7943c27cf74393e3fc22f","up":true},{"one":"c7fd88864731e6b79acf436a76908b91c61ba07680699c2cda316179c3719e8a","other":"d55213d3dc27542a780472564a4a8c1ce47668e05710be178baf3238ca8886b6","up":true},{"one":"004710b2e2194a8cd11292efe8b6f81bec4c1b673a9c8a45b7b5391f5f049895","other":"6a57cd355d641d1506957a988d93b2d3f9a613a7354c24130ad97dbc4c8296ce","up":true},{"one":"1385e97b879da9021aba65b2df36b91650aafb354a09c5be8a9532d1eb7d472b","other":"1943c1442da29872e77e4cfd282e9a81490c613e99cbe4503e470b7f23ce75fb","up":true},{"one":"6632a8b23d42dfc55a103d625a3644f0067abaae554eb75a78b1094ad7b24c16","other":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","up":true},{"one":"a2504bc38c94b7912bfafc42c7a4531cd27775473071e7a26173c4db932bf9ec","other":"fa62b685ae8a9aac669d245102ce68b030f0f270d95d7c88969bfdc9978f9070","up":true},{"one":"954a22c6fd7d63fce47d8685d32bb892bcc0feab4209d5dc7c3601efc28e7d6d","other":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","up":true},{"one":"83bc840da4e1f9201f0e85db2c44364f06c188fedef30fd2f72cbde70f6cfdb9","other":"981be0d21bdb11ac807dc29937c6a3ac2ae3c2f45269c0edc21172d5550f4da9","up":true},{"one":"40871cbc7eff896f15e558e2c638ac980fd89c718881cd110a086c3397b564c0","other":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","up":true},{"one":"57dfed651ba7f0ab6f9e1d5d2d4ae18fee51a200f50925a88c861982bb4769cb","other":"314e1e8a8d9ae18d0b5510341a6fab0f8654a7263a5db895480aca606b6a9859","up":true},{"one":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","other":"7692399f01177f2a8976d4c393a6813c53820e5f4edf54934414d1bcdfb8392c","up":true},{"one":"8ec663c22eff8ed185b8194c1240ff58df131c5f310df0a91b9790ec166e4346","other":"92e2f8ec301e5858f7168b785040955d28bffae0c98e5d3f59ff9fa8636fe423","up":true},{"one":"e5cd769dbcc429587e3a92b192575965c0ac18e88c9a54b9f75936d929c43c62","other":"e39e8092c97b53dbeaf5ca73d95c71e8e41a2906aaa377fe0c1b23752c8c7423","up":true},{"one":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","other":"ebf9a6425b47a78df1c99fe4d62a48c26d2f4ef966a8592802d5b5e95e6c12eb","up":true},{"one":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","other":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","up":true},{"one":"6640a8343b810399525545ee1545a50e3c76548f776c83d55465071cb55a00b1","other":"44540a152f9fc924f10fefc013059d9ad228e74f921a67866ef28758f1886320","up":true},{"one":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","other":"1ea89c3f15b82385c5df8810ec7cbd30e8e4a4e05c1ddf5dbe7102edaa07afd2","up":true},{"one":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","other":"1ea89c3f15b82385c5df8810ec7cbd30e8e4a4e05c1ddf5dbe7102edaa07afd2","up":true},{"one":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","other":"3d3acee3657d98168e2f4e6463947646560b5ad1ca24b4a8a97e4284c39b02e1","up":true},{"one":"06db6e244213745e02175cca1101e72799c09236838affd8a020a05e5b88e2bd","other":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","up":true},{"one":"1ea89c3f15b82385c5df8810ec7cbd30e8e4a4e05c1ddf5dbe7102edaa07afd2","other":"004710b2e2194a8cd11292efe8b6f81bec4c1b673a9c8a45b7b5391f5f049895","up":true},{"one":"ea9490f17813e0e5f39e8065f4c3954163f3bca6e80afca7a2c76d5ca3116e53","other":"d68e095e77246c3c08df2eaec294965bca2d01fa2819d9cd06c6ef8730c59ab4","up":true},{"one":"5d6da3260504f8063af24960ac901bdc0c54a126b82d415656a083c496c8b6b8","other":"500f2135db1adafe22d8a62af63f1659d6f4538f490ee8151e9db970fa24389b","up":true},{"one":"d87f983aefd8f732c29718a95f4dfc54a10640d5c9fdd73b772c3c30df269d88","other":"ac23190ee9ee30dd793bf3edef355450522d3f56973ab6b1acbb6dfd9d64137a","up":true},{"one":"8e317152297d5d048ca4512f733e63e48c3dc66910bad541d33f3cfc76828cb9","other":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","up":true},{"one":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","other":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","up":true},{"one":"6aef7e94602e6e74ab4cdb57668fb751fa20807751f786d0232870da0ae32cfd","other":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","up":true},{"one":"4309c2d963d91e65f7e8e8ab42df52e75f8d377d4eb07d1a048c0082af4f501e","other":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","up":true},{"one":"4124e83515480818c250a3ca2d8a7b159c890983c35165ee531d2e15066c7203","other":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","up":true},{"one":"bf7b0b9825b9c878f7730014c7458fc52ee3490c98ee5ae2f647a8d73e4464f3","other":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","up":true},{"one":"92e2f8ec301e5858f7168b785040955d28bffae0c98e5d3f59ff9fa8636fe423","other":"aec520f507eedd5d752b6ba8a0b509f14105e5e457eef0b6537015e39569762b","up":true},{"one":"6cf1d30b063cf8352795a92c6af26c154fc44be06c69ea9fd4d9f6dcc626eda1","other":"7692399f01177f2a8976d4c393a6813c53820e5f4edf54934414d1bcdfb8392c","up":true},{"one":"44540a152f9fc924f10fefc013059d9ad228e74f921a67866ef28758f1886320","other":"40871cbc7eff896f15e558e2c638ac980fd89c718881cd110a086c3397b564c0","up":true},{"one":"237b54aa7ef077972fc65fb222ce70df0129f8c8d4d35505fcbdc281bb7d28ce","other":"2a2bcb40404a427a50538ad9d1bfff1c5b1a7673990cbd20d830787fd96a00e5","up":true},{"one":"2dc2914f76ec9b291b2085bea6d2e0f6d45beb55bba5e3a90059b96e83010318","other":"e0ac6b34d04bc036026767288a8f0f96720f4a3efa5b52b3b86439fba562bcf4","up":true},{"one":"f07c1ee0f9cadf2971a0cb0022d1c67fa82c79f63cb98769b31ccfb04d05c0a8","other":"cac97c7026184a2d6ba2b17fcd0c5440bec538961af836a60f2fadb78a310a77","up":true},{"one":"7f5f9f955e368c906746d42953e30e781a8eb73428af5aa608af96af9bf4f60a","other":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","up":true},{"one":"500f2135db1adafe22d8a62af63f1659d6f4538f490ee8151e9db970fa24389b","other":"a2504bc38c94b7912bfafc42c7a4531cd27775473071e7a26173c4db932bf9ec","up":true},{"one":"96b723de0840f656c914674ad20204da830c04d4d6956c32e20154cbcc40f9ef","other":"a12e73ee9cf7369825c1423608ce079cf70df75d0f0bbc56d07d484169fca26d","up":true},{"one":"feb3455336b26365b81dfc8cba2a390690ce996e07fcb7f1226720d263205adf","other":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","up":true},{"one":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","other":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","up":true},{"one":"aec520f507eedd5d752b6ba8a0b509f14105e5e457eef0b6537015e39569762b","other":"3e4fbab650cbd539383f7e26128c0aaa770f12c4a1fa44d553de9dda073f0cb2","up":true},{"one":"6ac71d55e984abe323c74ea85ac1aed1b06e56632c07c389e780268274a23810","other":"7e65067fa5db52d40c0a162432200ca6d0d3917b25cad15ce498fa6b5375bc8d","up":true},{"one":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","other":"1943c1442da29872e77e4cfd282e9a81490c613e99cbe4503e470b7f23ce75fb","up":true},{"one":"3e4fbab650cbd539383f7e26128c0aaa770f12c4a1fa44d553de9dda073f0cb2","other":"2feed66b03b7b057e1a64a3f877ad9c28ada96e30b98a78393b54ea31b98f4fa","up":true},{"one":"fa62b685ae8a9aac669d245102ce68b030f0f270d95d7c88969bfdc9978f9070","other":"ebf9a6425b47a78df1c99fe4d62a48c26d2f4ef966a8592802d5b5e95e6c12eb","up":true},{"one":"f3a1438cfc8b09a3a9c1fc8455ead9041e9532ef3e9a77ffee8e5ef62af2670c","other":"87e0232acc7127ea47604a5bfe84d80e38292902502c2a714e0f96d8090eb4fe","up":true},{"one":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","other":"d3864c160e6d8fcb3f540b864e6ee11d91ca708f2cbe1e5fd04beb2b6c1d8930","up":true},{"one":"d7f956d0f6445202699bf63700da408237fdcecd27e2e8ad969e51d7e8e61483","other":"83bc840da4e1f9201f0e85db2c44364f06c188fedef30fd2f72cbde70f6cfdb9","up":true},{"one":"40871cbc7eff896f15e558e2c638ac980fd89c718881cd110a086c3397b564c0","other":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","up":true},{"one":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","other":"87e0232acc7127ea47604a5bfe84d80e38292902502c2a714e0f96d8090eb4fe","up":true},{"one":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","other":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","up":true},{"one":"c7fd88864731e6b79acf436a76908b91c61ba07680699c2cda316179c3719e8a","other":"d79838c419be793292a24feaf1b9d26c67f0c5afe15bae85689420de3436ad95","up":true},{"one":"1f2a162a4e78c8935ed17eee206a8512ef37e126bb7ad9ff744f7b46c4b19aee","other":"609958921d9107eaae6f3141e94f12b3a28fec203eb0e5b674ed8268934becc7","up":true},{"one":"4124e83515480818c250a3ca2d8a7b159c890983c35165ee531d2e15066c7203","other":"4ae614490d8739205773d16c8027c46b67d93bd336c33071d8707fea6ac1acf3","up":true},{"one":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","other":"d3864c160e6d8fcb3f540b864e6ee11d91ca708f2cbe1e5fd04beb2b6c1d8930","up":true},{"one":"61207f4fc13c899b88e9b727eb202a8bab00868bd07d945100b2e56dbcd67683","other":"4124e83515480818c250a3ca2d8a7b159c890983c35165ee531d2e15066c7203","up":true},{"one":"9f9700ca395f36c84a38838c11f6889d00015d97aac7943c27cf74393e3fc22f","other":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","up":true},{"one":"8ec663c22eff8ed185b8194c1240ff58df131c5f310df0a91b9790ec166e4346","other":"8bf59dc9ce97e6e2f2be364380aca38f98ddec248e730422f581f5bb56171e34","up":true},{"one":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","other":"6640a8343b810399525545ee1545a50e3c76548f776c83d55465071cb55a00b1","up":true},{"one":"6ac71d55e984abe323c74ea85ac1aed1b06e56632c07c389e780268274a23810","other":"6632a8b23d42dfc55a103d625a3644f0067abaae554eb75a78b1094ad7b24c16","up":true},{"one":"f29ff736fb944f8f8339a4e10f69ee72f108ed1d68b5a80d06a814b0807e8686","other":"d68e095e77246c3c08df2eaec294965bca2d01fa2819d9cd06c6ef8730c59ab4","up":true},{"one":"d80b59b187b7765439b0e4c940f766ae71a903f599ac41a84d972e8703f7c9ff","other":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","up":true},{"one":"53eaa90913134a5bdaa780d18bcd33e85b1932d9b15bcc1096fc4658ea5ea7f8","other":"6a57cd355d641d1506957a988d93b2d3f9a613a7354c24130ad97dbc4c8296ce","up":true},{"one":"f2b809e97d3c3a652c4ddce19ca12b01c1de25f2ce007d561fd5606e85540d64","other":"e5cd769dbcc429587e3a92b192575965c0ac18e88c9a54b9f75936d929c43c62","up":true},{"one":"059a2d0e5db2c0204c10b66ef04bacc96d64453073efeee81ba7e30d43061a61","other":"609958921d9107eaae6f3141e94f12b3a28fec203eb0e5b674ed8268934becc7","up":true},{"one":"237b54aa7ef077972fc65fb222ce70df0129f8c8d4d35505fcbdc281bb7d28ce","other":"2dc2914f76ec9b291b2085bea6d2e0f6d45beb55bba5e3a90059b96e83010318","up":true},{"one":"60ad041deb8cf72f0d2b29accb204a7e1838347b250e105ac72a0a4f17dc5f47","other":"61207f4fc13c899b88e9b727eb202a8bab00868bd07d945100b2e56dbcd67683","up":true},{"one":"609958921d9107eaae6f3141e94f12b3a28fec203eb0e5b674ed8268934becc7","other":"61207f4fc13c899b88e9b727eb202a8bab00868bd07d945100b2e56dbcd67683","up":true},{"one":"d9169f80683a2552475d60e6982f70c6605f9b40300f2b60895aa21cdc42db89","other":"e4254cda903275296f17b47e5dcabbf16d1fa7432b64e0e6e0562fcd89de534d","up":true},{"one":"78bfaf122d10ee5e5304fdd247434c02b32de577bb410dc04d8ffe1906294fdb","other":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","up":true},{"one":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","other":"e5cd769dbcc429587e3a92b192575965c0ac18e88c9a54b9f75936d929c43c62","up":true},{"one":"954a22c6fd7d63fce47d8685d32bb892bcc0feab4209d5dc7c3601efc28e7d6d","other":"e3c3f763b77a0bb72932d5d3dfca016e28d27b1c377690d2b074881b1a2a3259","up":true},{"one":"981be0d21bdb11ac807dc29937c6a3ac2ae3c2f45269c0edc21172d5550f4da9","other":"8ec663c22eff8ed185b8194c1240ff58df131c5f310df0a91b9790ec166e4346","up":true},{"one":"d79838c419be793292a24feaf1b9d26c67f0c5afe15bae85689420de3436ad95","other":"cac97c7026184a2d6ba2b17fcd0c5440bec538961af836a60f2fadb78a310a77","up":true},{"one":"fba89f80c35e5f6ffbe6e5521f2011caec985d820fe42759ef1aaa36d4902f37","other":"e5cd769dbcc429587e3a92b192575965c0ac18e88c9a54b9f75936d929c43c62","up":true},{"one":"1943c1442da29872e77e4cfd282e9a81490c613e99cbe4503e470b7f23ce75fb","other":"6e8315dc552c8112dcb01e9302b5e47fef7fbdc81fbe7a1d0b27e0cf80a65ca4","up":true},{"one":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","other":"7c766b57273777cc67f74870560449c9542cfec734b58b167aef99e09aeae962","up":true},{"one":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","other":"97a5959b95662f6e820a1b011a7e87568e0efd2257a73bf91868c98c46e5a571","up":true},{"one":"83119770a801a40f0f4015ce8a8cc6cfa6dbc211deaee193c5db05130002f9a7","other":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","up":true},{"one":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","other":"d192c64e09879b5107507e193fea23b0244a574e0d66e444f7a325de32c123fd","up":true},{"one":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","other":"059a2d0e5db2c0204c10b66ef04bacc96d64453073efeee81ba7e30d43061a61","up":true},{"one":"6ac71d55e984abe323c74ea85ac1aed1b06e56632c07c389e780268274a23810","other":"6c1f82c3a7bc3274cfdac710caec4fc814c9f5a6797674457414dd94be577a9b","up":true},{"one":"265040d12d0b79c8609232060b3ddd6ac16fb895b52bb7f3fee5c6e8b63a4d79","other":"6aef7e94602e6e74ab4cdb57668fb751fa20807751f786d0232870da0ae32cfd","up":true},{"one":"1ea89c3f15b82385c5df8810ec7cbd30e8e4a4e05c1ddf5dbe7102edaa07afd2","other":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","up":true},{"one":"8c5b95bf1743e2d8a97210c5b64b6732f3615c4369fa3a52e426f93e168ae16f","other":"ac23190ee9ee30dd793bf3edef355450522d3f56973ab6b1acbb6dfd9d64137a","up":true},{"one":"311fc84bce90b1bc1d1983c78e45b1e82a703cdb7cfbe54d51e9bc40cfe04103","other":"6cf1d30b063cf8352795a92c6af26c154fc44be06c69ea9fd4d9f6dcc626eda1","up":true},{"one":"dc2ae81b708a14f8b6e134986075835994c71d284b9bd9fa19d2efa73346f35e","other":"740bebf4546e2ed32b2bd277555685e3be071aca4b1990c7c095e12e24fa4699","up":true},{"one":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","other":"265040d12d0b79c8609232060b3ddd6ac16fb895b52bb7f3fee5c6e8b63a4d79","up":true},{"one":"bf7b0b9825b9c878f7730014c7458fc52ee3490c98ee5ae2f647a8d73e4464f3","other":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","up":true},{"one":"a2504bc38c94b7912bfafc42c7a4531cd27775473071e7a26173c4db932bf9ec","other":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","up":true},{"one":"97a5959b95662f6e820a1b011a7e87568e0efd2257a73bf91868c98c46e5a571","other":"8fe212fc9af12821f59b5f364f86f78b545be501933b926fc47b6d7deafca941","up":true},{"one":"ebf9a6425b47a78df1c99fe4d62a48c26d2f4ef966a8592802d5b5e95e6c12eb","other":"d55213d3dc27542a780472564a4a8c1ce47668e05710be178baf3238ca8886b6","up":true},{"one":"4124e83515480818c250a3ca2d8a7b159c890983c35165ee531d2e15066c7203","other":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","up":true},{"one":"40871cbc7eff896f15e558e2c638ac980fd89c718881cd110a086c3397b564c0","other":"2dc2914f76ec9b291b2085bea6d2e0f6d45beb55bba5e3a90059b96e83010318","up":true},{"one":"6640a8343b810399525545ee1545a50e3c76548f776c83d55465071cb55a00b1","other":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","up":true},{"one":"feb3455336b26365b81dfc8cba2a390690ce996e07fcb7f1226720d263205adf","other":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","up":true},{"one":"aec520f507eedd5d752b6ba8a0b509f14105e5e457eef0b6537015e39569762b","other":"311fc84bce90b1bc1d1983c78e45b1e82a703cdb7cfbe54d51e9bc40cfe04103","up":true},{"one":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","other":"83bc840da4e1f9201f0e85db2c44364f06c188fedef30fd2f72cbde70f6cfdb9","up":true},{"one":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","other":"30a0ef4bf1f66ab4d02a027a24ba1f9b288ea93e86a5ceab64e1985aa4efcbd0","up":true},{"one":"237b54aa7ef077972fc65fb222ce70df0129f8c8d4d35505fcbdc281bb7d28ce","other":"2feed66b03b7b057e1a64a3f877ad9c28ada96e30b98a78393b54ea31b98f4fa","up":true},{"one":"8bf59dc9ce97e6e2f2be364380aca38f98ddec248e730422f581f5bb56171e34","other":"cac97c7026184a2d6ba2b17fcd0c5440bec538961af836a60f2fadb78a310a77","up":true},{"one":"8e317152297d5d048ca4512f733e63e48c3dc66910bad541d33f3cfc76828cb9","other":"ac23190ee9ee30dd793bf3edef355450522d3f56973ab6b1acbb6dfd9d64137a","up":true},{"one":"6aef7e94602e6e74ab4cdb57668fb751fa20807751f786d0232870da0ae32cfd","other":"8fe212fc9af12821f59b5f364f86f78b545be501933b926fc47b6d7deafca941","up":true},{"one":"d3864c160e6d8fcb3f540b864e6ee11d91ca708f2cbe1e5fd04beb2b6c1d8930","other":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","up":true},{"one":"92e2f8ec301e5858f7168b785040955d28bffae0c98e5d3f59ff9fa8636fe423","other":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","up":true},{"one":"6838b88ff71c5419d3fd22a4c007a7b822405b1842d53427a9c075d093c13047","other":"6632a8b23d42dfc55a103d625a3644f0067abaae554eb75a78b1094ad7b24c16","up":true},{"one":"2dc2914f76ec9b291b2085bea6d2e0f6d45beb55bba5e3a90059b96e83010318","other":"ebf9a6425b47a78df1c99fe4d62a48c26d2f4ef966a8592802d5b5e95e6c12eb","up":true},{"one":"a2504bc38c94b7912bfafc42c7a4531cd27775473071e7a26173c4db932bf9ec","other":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","up":true},{"one":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","other":"7628bcf64532ca3ec293d26f60791a2a62542b2f5f3f1c782799075383ccde00","up":true},{"one":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","other":"83bc840da4e1f9201f0e85db2c44364f06c188fedef30fd2f72cbde70f6cfdb9","up":true},{"one":"3d3acee3657d98168e2f4e6463947646560b5ad1ca24b4a8a97e4284c39b02e1","other":"57dfed651ba7f0ab6f9e1d5d2d4ae18fee51a200f50925a88c861982bb4769cb","up":true},{"one":"3e4fbab650cbd539383f7e26128c0aaa770f12c4a1fa44d553de9dda073f0cb2","other":"2aef8c4da4e38b6a885785e406912cef6087344b4be9e135e2c597468b003315","up":true},{"one":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","other":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","up":true},{"one":"6ac71d55e984abe323c74ea85ac1aed1b06e56632c07c389e780268274a23810","other":"6cf1d30b063cf8352795a92c6af26c154fc44be06c69ea9fd4d9f6dcc626eda1","up":true},{"one":"6a57cd355d641d1506957a988d93b2d3f9a613a7354c24130ad97dbc4c8296ce","other":"61207f4fc13c899b88e9b727eb202a8bab00868bd07d945100b2e56dbcd67683","up":true},{"one":"ea9490f17813e0e5f39e8065f4c3954163f3bca6e80afca7a2c76d5ca3116e53","other":"d7f956d0f6445202699bf63700da408237fdcecd27e2e8ad969e51d7e8e61483","up":true},{"one":"d87f983aefd8f732c29718a95f4dfc54a10640d5c9fdd73b772c3c30df269d88","other":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","up":true},{"one":"2a2bcb40404a427a50538ad9d1bfff1c5b1a7673990cbd20d830787fd96a00e5","other":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","up":true},{"one":"4ae614490d8739205773d16c8027c46b67d93bd336c33071d8707fea6ac1acf3","other":"44540a152f9fc924f10fefc013059d9ad228e74f921a67866ef28758f1886320","up":true},{"one":"6632a8b23d42dfc55a103d625a3644f0067abaae554eb75a78b1094ad7b24c16","other":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","up":true},{"one":"78bfaf122d10ee5e5304fdd247434c02b32de577bb410dc04d8ffe1906294fdb","other":"3f1ee2912d5446e4cdf7a42582f2e30a24bcddd6422a9b6a61f287da256d0bde","up":true},{"one":"f07c1ee0f9cadf2971a0cb0022d1c67fa82c79f63cb98769b31ccfb04d05c0a8","other":"d7f956d0f6445202699bf63700da408237fdcecd27e2e8ad969e51d7e8e61483","up":true},{"one":"40871cbc7eff896f15e558e2c638ac980fd89c718881cd110a086c3397b564c0","other":"4ae614490d8739205773d16c8027c46b67d93bd336c33071d8707fea6ac1acf3","up":true},{"one":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","other":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","up":true},{"one":"83bc840da4e1f9201f0e85db2c44364f06c188fedef30fd2f72cbde70f6cfdb9","other":"9f9700ca395f36c84a38838c11f6889d00015d97aac7943c27cf74393e3fc22f","up":true},{"one":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","other":"11a60d43d627470df1c1ebdc404093f6ae83c75b3a95469316b89a0185eeaa86","up":true},{"one":"06db6e244213745e02175cca1101e72799c09236838affd8a020a05e5b88e2bd","other":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","up":true},{"one":"311fc84bce90b1bc1d1983c78e45b1e82a703cdb7cfbe54d51e9bc40cfe04103","other":"7c766b57273777cc67f74870560449c9542cfec734b58b167aef99e09aeae962","up":true},{"one":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","other":"87e0232acc7127ea47604a5bfe84d80e38292902502c2a714e0f96d8090eb4fe","up":true},{"one":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","other":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","up":true},{"one":"6cf1d30b063cf8352795a92c6af26c154fc44be06c69ea9fd4d9f6dcc626eda1","other":"6a57cd355d641d1506957a988d93b2d3f9a613a7354c24130ad97dbc4c8296ce","up":true},{"one":"7f5f9f955e368c906746d42953e30e781a8eb73428af5aa608af96af9bf4f60a","other":"87e0232acc7127ea47604a5bfe84d80e38292902502c2a714e0f96d8090eb4fe","up":true},{"one":"954a22c6fd7d63fce47d8685d32bb892bcc0feab4209d5dc7c3601efc28e7d6d","other":"feb3455336b26365b81dfc8cba2a390690ce996e07fcb7f1226720d263205adf","up":true},{"one":"96b723de0840f656c914674ad20204da830c04d4d6956c32e20154cbcc40f9ef","other":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","up":true},{"one":"4124e83515480818c250a3ca2d8a7b159c890983c35165ee531d2e15066c7203","other":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","up":true},{"one":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","other":"3f1ee2912d5446e4cdf7a42582f2e30a24bcddd6422a9b6a61f287da256d0bde","up":true},{"one":"feb3455336b26365b81dfc8cba2a390690ce996e07fcb7f1226720d263205adf","other":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","up":true},{"one":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","other":"237b54aa7ef077972fc65fb222ce70df0129f8c8d4d35505fcbdc281bb7d28ce","up":true},{"one":"1ea89c3f15b82385c5df8810ec7cbd30e8e4a4e05c1ddf5dbe7102edaa07afd2","other":"059a2d0e5db2c0204c10b66ef04bacc96d64453073efeee81ba7e30d43061a61","up":true},{"one":"f2b809e97d3c3a652c4ddce19ca12b01c1de25f2ce007d561fd5606e85540d64","other":"e4254cda903275296f17b47e5dcabbf16d1fa7432b64e0e6e0562fcd89de534d","up":true},{"one":"059a2d0e5db2c0204c10b66ef04bacc96d64453073efeee81ba7e30d43061a61","other":"60ad041deb8cf72f0d2b29accb204a7e1838347b250e105ac72a0a4f17dc5f47","up":true},{"one":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","other":"d382e36db7f13180cca7b169e8511c9b920bb481a340d8a61dd9e4969a5605b6","up":true},{"one":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","other":"9f9700ca395f36c84a38838c11f6889d00015d97aac7943c27cf74393e3fc22f","up":true},{"one":"d87f983aefd8f732c29718a95f4dfc54a10640d5c9fdd73b772c3c30df269d88","other":"57dfed651ba7f0ab6f9e1d5d2d4ae18fee51a200f50925a88c861982bb4769cb","up":true},{"one":"004710b2e2194a8cd11292efe8b6f81bec4c1b673a9c8a45b7b5391f5f049895","other":"78bfaf122d10ee5e5304fdd247434c02b32de577bb410dc04d8ffe1906294fdb","up":true},{"one":"dc2ae81b708a14f8b6e134986075835994c71d284b9bd9fa19d2efa73346f35e","other":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","up":true},{"one":"6632a8b23d42dfc55a103d625a3644f0067abaae554eb75a78b1094ad7b24c16","other":"11a60d43d627470df1c1ebdc404093f6ae83c75b3a95469316b89a0185eeaa86","up":true},{"one":"1f2a162a4e78c8935ed17eee206a8512ef37e126bb7ad9ff744f7b46c4b19aee","other":"60ad041deb8cf72f0d2b29accb204a7e1838347b250e105ac72a0a4f17dc5f47","up":true},{"one":"f07c1ee0f9cadf2971a0cb0022d1c67fa82c79f63cb98769b31ccfb04d05c0a8","other":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","up":true},{"one":"40871cbc7eff896f15e558e2c638ac980fd89c718881cd110a086c3397b564c0","other":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","up":true},{"one":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","other":"d79838c419be793292a24feaf1b9d26c67f0c5afe15bae85689420de3436ad95","up":true},{"one":"61207f4fc13c899b88e9b727eb202a8bab00868bd07d945100b2e56dbcd67683","other":"3d3acee3657d98168e2f4e6463947646560b5ad1ca24b4a8a97e4284c39b02e1","up":true},{"one":"6640a8343b810399525545ee1545a50e3c76548f776c83d55465071cb55a00b1","other":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","up":true},{"one":"aec520f507eedd5d752b6ba8a0b509f14105e5e457eef0b6537015e39569762b","other":"6cf1d30b063cf8352795a92c6af26c154fc44be06c69ea9fd4d9f6dcc626eda1","up":true},{"one":"feb3455336b26365b81dfc8cba2a390690ce996e07fcb7f1226720d263205adf","other":"a12e73ee9cf7369825c1423608ce079cf70df75d0f0bbc56d07d484169fca26d","up":true},{"one":"d80b59b187b7765439b0e4c940f766ae71a903f599ac41a84d972e8703f7c9ff","other":"aec520f507eedd5d752b6ba8a0b509f14105e5e457eef0b6537015e39569762b","up":true},{"one":"265040d12d0b79c8609232060b3ddd6ac16fb895b52bb7f3fee5c6e8b63a4d79","other":"6640a8343b810399525545ee1545a50e3c76548f776c83d55465071cb55a00b1","up":true},{"one":"f3a1438cfc8b09a3a9c1fc8455ead9041e9532ef3e9a77ffee8e5ef62af2670c","other":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","up":true},{"one":"ea9490f17813e0e5f39e8065f4c3954163f3bca6e80afca7a2c76d5ca3116e53","other":"b1bf7a0572c1efcf034bf42f54f7334743c59f77b67225aa3c7d93552074ab56","up":true},{"one":"6aef7e94602e6e74ab4cdb57668fb751fa20807751f786d0232870da0ae32cfd","other":"b22078d9b5f3d57dec3fb1796599d5f04fcb32d4288939431ecea8516be76b58","up":true},{"one":"dc2ae81b708a14f8b6e134986075835994c71d284b9bd9fa19d2efa73346f35e","other":"d55213d3dc27542a780472564a4a8c1ce47668e05710be178baf3238ca8886b6","up":true},{"one":"4ae614490d8739205773d16c8027c46b67d93bd336c33071d8707fea6ac1acf3","other":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","up":true},{"one":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","other":"237b54aa7ef077972fc65fb222ce70df0129f8c8d4d35505fcbdc281bb7d28ce","up":true},{"one":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","other":"f3a1438cfc8b09a3a9c1fc8455ead9041e9532ef3e9a77ffee8e5ef62af2670c","up":true},{"one":"bf7b0b9825b9c878f7730014c7458fc52ee3490c98ee5ae2f647a8d73e4464f3","other":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","up":true},{"one":"d79838c419be793292a24feaf1b9d26c67f0c5afe15bae85689420de3436ad95","other":"ca9721b51497c5d570738eadc37c79fc8d5525749defb98c252371793c2a6f5c","up":true},{"one":"9f9700ca395f36c84a38838c11f6889d00015d97aac7943c27cf74393e3fc22f","other":"83119770a801a40f0f4015ce8a8cc6cfa6dbc211deaee193c5db05130002f9a7","up":true},{"one":"fba89f80c35e5f6ffbe6e5521f2011caec985d820fe42759ef1aaa36d4902f37","other":"d55213d3dc27542a780472564a4a8c1ce47668e05710be178baf3238ca8886b6","up":true},{"one":"3d3acee3657d98168e2f4e6463947646560b5ad1ca24b4a8a97e4284c39b02e1","other":"40871cbc7eff896f15e558e2c638ac980fd89c718881cd110a086c3397b564c0","up":true},{"one":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","other":"8fe212fc9af12821f59b5f364f86f78b545be501933b926fc47b6d7deafca941","up":true},{"one":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","other":"2a2bcb40404a427a50538ad9d1bfff1c5b1a7673990cbd20d830787fd96a00e5","up":true},{"one":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","other":"7e65067fa5db52d40c0a162432200ca6d0d3917b25cad15ce498fa6b5375bc8d","up":true},{"one":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","other":"f836b3ca82f75bc0901d83b2c02e24174bee78c5eab9b55af9c492406942637a","up":true},{"one":"53eaa90913134a5bdaa780d18bcd33e85b1932d9b15bcc1096fc4658ea5ea7f8","other":"44540a152f9fc924f10fefc013059d9ad228e74f921a67866ef28758f1886320","up":true},{"one":"d7f956d0f6445202699bf63700da408237fdcecd27e2e8ad969e51d7e8e61483","other":"d3864c160e6d8fcb3f540b864e6ee11d91ca708f2cbe1e5fd04beb2b6c1d8930","up":true},{"one":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","other":"1f2a162a4e78c8935ed17eee206a8512ef37e126bb7ad9ff744f7b46c4b19aee","up":true},{"one":"8c5b95bf1743e2d8a97210c5b64b6732f3615c4369fa3a52e426f93e168ae16f","other":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","up":true},{"one":"6ac71d55e984abe323c74ea85ac1aed1b06e56632c07c389e780268274a23810","other":"6aef7e94602e6e74ab4cdb57668fb751fa20807751f786d0232870da0ae32cfd","up":true},{"one":"004710b2e2194a8cd11292efe8b6f81bec4c1b673a9c8a45b7b5391f5f049895","other":"059a2d0e5db2c0204c10b66ef04bacc96d64453073efeee81ba7e30d43061a61","up":true},{"one":"d87f983aefd8f732c29718a95f4dfc54a10640d5c9fdd73b772c3c30df269d88","other":"d9169f80683a2552475d60e6982f70c6605f9b40300f2b60895aa21cdc42db89","up":true},{"one":"92e2f8ec301e5858f7168b785040955d28bffae0c98e5d3f59ff9fa8636fe423","other":"aa19fb3565a2677828b1384f483e3eb7ea112940d21d0aaa7b16139c753c69d7","up":true},{"one":"97a5959b95662f6e820a1b011a7e87568e0efd2257a73bf91868c98c46e5a571","other":"8bf59dc9ce97e6e2f2be364380aca38f98ddec248e730422f581f5bb56171e34","up":true},{"one":"bf7b0b9825b9c878f7730014c7458fc52ee3490c98ee5ae2f647a8d73e4464f3","other":"60ad041deb8cf72f0d2b29accb204a7e1838347b250e105ac72a0a4f17dc5f47","up":true},{"one":"83bc840da4e1f9201f0e85db2c44364f06c188fedef30fd2f72cbde70f6cfdb9","other":"b22078d9b5f3d57dec3fb1796599d5f04fcb32d4288939431ecea8516be76b58","up":true},{"one":"f07c1ee0f9cadf2971a0cb0022d1c67fa82c79f63cb98769b31ccfb04d05c0a8","other":"f29ff736fb944f8f8339a4e10f69ee72f108ed1d68b5a80d06a814b0807e8686","up":true},{"one":"bf7b0b9825b9c878f7730014c7458fc52ee3490c98ee5ae2f647a8d73e4464f3","other":"6aef7e94602e6e74ab4cdb57668fb751fa20807751f786d0232870da0ae32cfd","up":true},{"one":"d79838c419be793292a24feaf1b9d26c67f0c5afe15bae85689420de3436ad95","other":"dc2ae81b708a14f8b6e134986075835994c71d284b9bd9fa19d2efa73346f35e","up":true},{"one":"40871cbc7eff896f15e558e2c638ac980fd89c718881cd110a086c3397b564c0","other":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","up":true},{"one":"83bc840da4e1f9201f0e85db2c44364f06c188fedef30fd2f72cbde70f6cfdb9","other":"bf7b0b9825b9c878f7730014c7458fc52ee3490c98ee5ae2f647a8d73e4464f3","up":true},{"one":"f07c1ee0f9cadf2971a0cb0022d1c67fa82c79f63cb98769b31ccfb04d05c0a8","other":"92e2f8ec301e5858f7168b785040955d28bffae0c98e5d3f59ff9fa8636fe423","up":true},{"one":"004710b2e2194a8cd11292efe8b6f81bec4c1b673a9c8a45b7b5391f5f049895","other":"06db6e244213745e02175cca1101e72799c09236838affd8a020a05e5b88e2bd","up":true},{"one":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","other":"7f620d306e05bac0f07838b7de6a2db69aa6494df3db09f26624ed167318a75a","up":true},{"one":"9f9700ca395f36c84a38838c11f6889d00015d97aac7943c27cf74393e3fc22f","other":"3f1ee2912d5446e4cdf7a42582f2e30a24bcddd6422a9b6a61f287da256d0bde","up":true},{"one":"8ec663c22eff8ed185b8194c1240ff58df131c5f310df0a91b9790ec166e4346","other":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","up":true},{"one":"6ac71d55e984abe323c74ea85ac1aed1b06e56632c07c389e780268274a23810","other":"6838b88ff71c5419d3fd22a4c007a7b822405b1842d53427a9c075d093c13047","up":true},{"one":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","other":"d55213d3dc27542a780472564a4a8c1ce47668e05710be178baf3238ca8886b6","up":true},{"one":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","other":"7f5f9f955e368c906746d42953e30e781a8eb73428af5aa608af96af9bf4f60a","up":true},{"one":"fba89f80c35e5f6ffbe6e5521f2011caec985d820fe42759ef1aaa36d4902f37","other":"ea51459c5ac142391297bb73aa26501eae65bc6f661b534a91a344de8dba2f4f","up":true},{"one":"9f9700ca395f36c84a38838c11f6889d00015d97aac7943c27cf74393e3fc22f","other":"314e1e8a8d9ae18d0b5510341a6fab0f8654a7263a5db895480aca606b6a9859","up":true},{"one":"8ec663c22eff8ed185b8194c1240ff58df131c5f310df0a91b9790ec166e4346","other":"3f1ee2912d5446e4cdf7a42582f2e30a24bcddd6422a9b6a61f287da256d0bde","up":true},{"one":"96b723de0840f656c914674ad20204da830c04d4d6956c32e20154cbcc40f9ef","other":"b9e5006b90bffae809136ad57e36b784b18e35f7491c47319807f6869d624465","up":true},{"one":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","other":"d87f983aefd8f732c29718a95f4dfc54a10640d5c9fdd73b772c3c30df269d88","up":true},{"one":"fba89f80c35e5f6ffbe6e5521f2011caec985d820fe42759ef1aaa36d4902f37","other":"d68e095e77246c3c08df2eaec294965bca2d01fa2819d9cd06c6ef8730c59ab4","up":true},{"one":"1943c1442da29872e77e4cfd282e9a81490c613e99cbe4503e470b7f23ce75fb","other":"7692399f01177f2a8976d4c393a6813c53820e5f4edf54934414d1bcdfb8392c","up":true},{"one":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","other":"e5cd769dbcc429587e3a92b192575965c0ac18e88c9a54b9f75936d929c43c62","up":true},{"one":"fa62b685ae8a9aac669d245102ce68b030f0f270d95d7c88969bfdc9978f9070","other":"feb3455336b26365b81dfc8cba2a390690ce996e07fcb7f1226720d263205adf","up":true},{"one":"61207f4fc13c899b88e9b727eb202a8bab00868bd07d945100b2e56dbcd67683","other":"2a2bcb40404a427a50538ad9d1bfff1c5b1a7673990cbd20d830787fd96a00e5","up":true},{"one":"96b723de0840f656c914674ad20204da830c04d4d6956c32e20154cbcc40f9ef","other":"d3864c160e6d8fcb3f540b864e6ee11d91ca708f2cbe1e5fd04beb2b6c1d8930","up":true},{"one":"1943c1442da29872e77e4cfd282e9a81490c613e99cbe4503e470b7f23ce75fb","other":"7c766b57273777cc67f74870560449c9542cfec734b58b167aef99e09aeae962","up":true},{"one":"6640a8343b810399525545ee1545a50e3c76548f776c83d55465071cb55a00b1","other":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","up":true},{"one":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","other":"e4254cda903275296f17b47e5dcabbf16d1fa7432b64e0e6e0562fcd89de534d","up":true},{"one":"8230e499c9c01cb6e5717336f3a88b6a8c1010081712fbfab96c301e23044d14","other":"e0ac6b34d04bc036026767288a8f0f96720f4a3efa5b52b3b86439fba562bcf4","up":true},{"one":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","other":"83119770a801a40f0f4015ce8a8cc6cfa6dbc211deaee193c5db05130002f9a7","up":true},{"one":"96b723de0840f656c914674ad20204da830c04d4d6956c32e20154cbcc40f9ef","other":"d55213d3dc27542a780472564a4a8c1ce47668e05710be178baf3238ca8886b6","up":true},{"one":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","other":"3e4fbab650cbd539383f7e26128c0aaa770f12c4a1fa44d553de9dda073f0cb2","up":true},{"one":"8ec663c22eff8ed185b8194c1240ff58df131c5f310df0a91b9790ec166e4346","other":"314e1e8a8d9ae18d0b5510341a6fab0f8654a7263a5db895480aca606b6a9859","up":true},{"one":"fba89f80c35e5f6ffbe6e5521f2011caec985d820fe42759ef1aaa36d4902f37","other":"f3a1438cfc8b09a3a9c1fc8455ead9041e9532ef3e9a77ffee8e5ef62af2670c","up":true},{"one":"8fe212fc9af12821f59b5f364f86f78b545be501933b926fc47b6d7deafca941","other":"8c5b95bf1743e2d8a97210c5b64b6732f3615c4369fa3a52e426f93e168ae16f","up":true},{"one":"1f2a162a4e78c8935ed17eee206a8512ef37e126bb7ad9ff744f7b46c4b19aee","other":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","up":true},{"one":"bf7b0b9825b9c878f7730014c7458fc52ee3490c98ee5ae2f647a8d73e4464f3","other":"a2504bc38c94b7912bfafc42c7a4531cd27775473071e7a26173c4db932bf9ec","up":true},{"one":"3d3acee3657d98168e2f4e6463947646560b5ad1ca24b4a8a97e4284c39b02e1","other":"4124e83515480818c250a3ca2d8a7b159c890983c35165ee531d2e15066c7203","up":true},{"one":"aec520f507eedd5d752b6ba8a0b509f14105e5e457eef0b6537015e39569762b","other":"6c1f82c3a7bc3274cfdac710caec4fc814c9f5a6797674457414dd94be577a9b","up":true},{"one":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","other":"1f2a162a4e78c8935ed17eee206a8512ef37e126bb7ad9ff744f7b46c4b19aee","up":true},{"one":"aec520f507eedd5d752b6ba8a0b509f14105e5e457eef0b6537015e39569762b","other":"7e65067fa5db52d40c0a162432200ca6d0d3917b25cad15ce498fa6b5375bc8d","up":true},{"one":"aec520f507eedd5d752b6ba8a0b509f14105e5e457eef0b6537015e39569762b","other":"7f620d306e05bac0f07838b7de6a2db69aa6494df3db09f26624ed167318a75a","up":true},{"one":"3e4fbab650cbd539383f7e26128c0aaa770f12c4a1fa44d553de9dda073f0cb2","other":"2a2bcb40404a427a50538ad9d1bfff1c5b1a7673990cbd20d830787fd96a00e5","up":true},{"one":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","other":"aa19fb3565a2677828b1384f483e3eb7ea112940d21d0aaa7b16139c753c69d7","up":true},{"one":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","other":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","up":true},{"one":"f29ff736fb944f8f8339a4e10f69ee72f108ed1d68b5a80d06a814b0807e8686","other":"dc2ae81b708a14f8b6e134986075835994c71d284b9bd9fa19d2efa73346f35e","up":true},{"one":"1f2a162a4e78c8935ed17eee206a8512ef37e126bb7ad9ff744f7b46c4b19aee","other":"1943c1442da29872e77e4cfd282e9a81490c613e99cbe4503e470b7f23ce75fb","up":false},{"one":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","other":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","up":true},{"one":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","other":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","up":true},{"one":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","other":"311fc84bce90b1bc1d1983c78e45b1e82a703cdb7cfbe54d51e9bc40cfe04103","up":true},{"one":"fba89f80c35e5f6ffbe6e5521f2011caec985d820fe42759ef1aaa36d4902f37","other":"f836b3ca82f75bc0901d83b2c02e24174bee78c5eab9b55af9c492406942637a","up":true},{"one":"3e4fbab650cbd539383f7e26128c0aaa770f12c4a1fa44d553de9dda073f0cb2","other":"314e1e8a8d9ae18d0b5510341a6fab0f8654a7263a5db895480aca606b6a9859","up":true},{"one":"f29ff736fb944f8f8339a4e10f69ee72f108ed1d68b5a80d06a814b0807e8686","other":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","up":true},{"one":"06db6e244213745e02175cca1101e72799c09236838affd8a020a05e5b88e2bd","other":"f29ff736fb944f8f8339a4e10f69ee72f108ed1d68b5a80d06a814b0807e8686","up":true},{"one":"1ab1591431f8ba2a5aa1110ff97926cbf80d080ded09192dcae0c4bfeaea1ec0","other":"ac23190ee9ee30dd793bf3edef355450522d3f56973ab6b1acbb6dfd9d64137a","up":true},{"one":"96b723de0840f656c914674ad20204da830c04d4d6956c32e20154cbcc40f9ef","other":"954a22c6fd7d63fce47d8685d32bb892bcc0feab4209d5dc7c3601efc28e7d6d","up":true},{"one":"aec520f507eedd5d752b6ba8a0b509f14105e5e457eef0b6537015e39569762b","other":"d55213d3dc27542a780472564a4a8c1ce47668e05710be178baf3238ca8886b6","up":true},{"one":"f2b809e97d3c3a652c4ddce19ca12b01c1de25f2ce007d561fd5606e85540d64","other":"e0ac6b34d04bc036026767288a8f0f96720f4a3efa5b52b3b86439fba562bcf4","up":true},{"one":"feb3455336b26365b81dfc8cba2a390690ce996e07fcb7f1226720d263205adf","other":"d192c64e09879b5107507e193fea23b0244a574e0d66e444f7a325de32c123fd","up":true},{"one":"06db6e244213745e02175cca1101e72799c09236838affd8a020a05e5b88e2bd","other":"d9169f80683a2552475d60e6982f70c6605f9b40300f2b60895aa21cdc42db89","up":true},{"one":"6640a8343b810399525545ee1545a50e3c76548f776c83d55465071cb55a00b1","other":"61207f4fc13c899b88e9b727eb202a8bab00868bd07d945100b2e56dbcd67683","up":false},{"one":"06db6e244213745e02175cca1101e72799c09236838affd8a020a05e5b88e2bd","other":"d68e095e77246c3c08df2eaec294965bca2d01fa2819d9cd06c6ef8730c59ab4","up":true},{"one":"fba89f80c35e5f6ffbe6e5521f2011caec985d820fe42759ef1aaa36d4902f37","other":"f99503dc6a4ace3317320d8454b3ac1a5977f508ae810c4d506001ed0126838b","up":true},{"one":"8fe212fc9af12821f59b5f364f86f78b545be501933b926fc47b6d7deafca941","other":"8e317152297d5d048ca4512f733e63e48c3dc66910bad541d33f3cfc76828cb9","up":true},{"one":"059a2d0e5db2c0204c10b66ef04bacc96d64453073efeee81ba7e30d43061a61","other":"6aef7e94602e6e74ab4cdb57668fb751fa20807751f786d0232870da0ae32cfd","up":true},{"one":"f2b809e97d3c3a652c4ddce19ca12b01c1de25f2ce007d561fd5606e85540d64","other":"ebf9a6425b47a78df1c99fe4d62a48c26d2f4ef966a8592802d5b5e95e6c12eb","up":true},{"one":"265040d12d0b79c8609232060b3ddd6ac16fb895b52bb7f3fee5c6e8b63a4d79","other":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","up":true},{"one":"059a2d0e5db2c0204c10b66ef04bacc96d64453073efeee81ba7e30d43061a61","other":"44540a152f9fc924f10fefc013059d9ad228e74f921a67866ef28758f1886320","up":true},{"one":"059a2d0e5db2c0204c10b66ef04bacc96d64453073efeee81ba7e30d43061a61","other":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","up":true},{"one":"fba89f80c35e5f6ffbe6e5521f2011caec985d820fe42759ef1aaa36d4902f37","other":"f80e1ce23869c7da4453f06521fa320632057194c9f9b3dcdd777cc1f06603eb","up":true},{"one":"d9169f80683a2552475d60e6982f70c6605f9b40300f2b60895aa21cdc42db89","other":"981be0d21bdb11ac807dc29937c6a3ac2ae3c2f45269c0edc21172d5550f4da9","up":true},{"one":"8ec663c22eff8ed185b8194c1240ff58df131c5f310df0a91b9790ec166e4346","other":"8c5b95bf1743e2d8a97210c5b64b6732f3615c4369fa3a52e426f93e168ae16f","up":true},{"one":"f2b809e97d3c3a652c4ddce19ca12b01c1de25f2ce007d561fd5606e85540d64","other":"2aef8c4da4e38b6a885785e406912cef6087344b4be9e135e2c597468b003315","up":true},{"one":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","other":"93868ef90fc89dff185d7e59dba34b3e9449ab0336fff12c64ff4ecc3f033b78","up":false},{"one":"d9169f80683a2552475d60e6982f70c6605f9b40300f2b60895aa21cdc42db89","other":"83119770a801a40f0f4015ce8a8cc6cfa6dbc211deaee193c5db05130002f9a7","up":true},{"one":"ea9490f17813e0e5f39e8065f4c3954163f3bca6e80afca7a2c76d5ca3116e53","other":"981be0d21bdb11ac807dc29937c6a3ac2ae3c2f45269c0edc21172d5550f4da9","up":true},{"one":"ea9490f17813e0e5f39e8065f4c3954163f3bca6e80afca7a2c76d5ca3116e53","other":"8fe212fc9af12821f59b5f364f86f78b545be501933b926fc47b6d7deafca941","up":true},{"one":"8c5b95bf1743e2d8a97210c5b64b6732f3615c4369fa3a52e426f93e168ae16f","other":"d3864c160e6d8fcb3f540b864e6ee11d91ca708f2cbe1e5fd04beb2b6c1d8930","up":true},{"one":"d80b59b187b7765439b0e4c940f766ae71a903f599ac41a84d972e8703f7c9ff","other":"d9169f80683a2552475d60e6982f70c6605f9b40300f2b60895aa21cdc42db89","up":false},{"one":"d7f956d0f6445202699bf63700da408237fdcecd27e2e8ad969e51d7e8e61483","other":"d382e36db7f13180cca7b169e8511c9b920bb481a340d8a61dd9e4969a5605b6","up":false},{"one":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","other":"db596f4ff3f8c25835f9f10176e87a8823eaf1d39a513882f1ceab931dabfed9","up":true},{"one":"96b723de0840f656c914674ad20204da830c04d4d6956c32e20154cbcc40f9ef","other":"d382e36db7f13180cca7b169e8511c9b920bb481a340d8a61dd9e4969a5605b6","up":false},{"one":"311fc84bce90b1bc1d1983c78e45b1e82a703cdb7cfbe54d51e9bc40cfe04103","other":"6c1f82c3a7bc3274cfdac710caec4fc814c9f5a6797674457414dd94be577a9b","up":true},{"one":"2a2bcb40404a427a50538ad9d1bfff1c5b1a7673990cbd20d830787fd96a00e5","other":"a12e73ee9cf7369825c1423608ce079cf70df75d0f0bbc56d07d484169fca26d","up":true},{"one":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","other":"d9169f80683a2552475d60e6982f70c6605f9b40300f2b60895aa21cdc42db89","up":true},{"one":"d3864c160e6d8fcb3f540b864e6ee11d91ca708f2cbe1e5fd04beb2b6c1d8930","other":"e3c3f763b77a0bb72932d5d3dfca016e28d27b1c377690d2b074881b1a2a3259","up":true},{"one":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","other":"a60b45dbab9c1de18783fed9eed8af6dabafbc33fdc5b3fef1ef4569bc6c70d9","up":true},{"one":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","other":"7f620d306e05bac0f07838b7de6a2db69aa6494df3db09f26624ed167318a75a","up":false},{"one":"2a2bcb40404a427a50538ad9d1bfff1c5b1a7673990cbd20d830787fd96a00e5","other":"a8ba308efec0ed318d68bd3561a41fbb0af84383e3f5333e2642ac9d60005ab9","up":true},{"one":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","other":"8bf59dc9ce97e6e2f2be364380aca38f98ddec248e730422f581f5bb56171e34","up":true},{"one":"d3864c160e6d8fcb3f540b864e6ee11d91ca708f2cbe1e5fd04beb2b6c1d8930","other":"8e317152297d5d048ca4512f733e63e48c3dc66910bad541d33f3cfc76828cb9","up":true},{"one":"d3864c160e6d8fcb3f540b864e6ee11d91ca708f2cbe1e5fd04beb2b6c1d8930","other":"811d75e7ee506a20ed40f8451066f5d9f6d63c5774004aa26bd30540137ffd30","up":true},{"one":"fba89f80c35e5f6ffbe6e5521f2011caec985d820fe42759ef1aaa36d4902f37","other":"feb3455336b26365b81dfc8cba2a390690ce996e07fcb7f1226720d263205adf","up":false},{"one":"0400b7958d9f0270dbe3146b80fa821dbae8b0cf70d11c9a3d3d14b14012fe4a","other":"06db6e244213745e02175cca1101e72799c09236838affd8a020a05e5b88e2bd","up":false},{"one":"f07c1ee0f9cadf2971a0cb0022d1c67fa82c79f63cb98769b31ccfb04d05c0a8","other":"f2b809e97d3c3a652c4ddce19ca12b01c1de25f2ce007d561fd5606e85540d64","up":false},{"one":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","other":"a12e73ee9cf7369825c1423608ce079cf70df75d0f0bbc56d07d484169fca26d","up":false},{"one":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","other":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","up":true},{"one":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","other":"d87f983aefd8f732c29718a95f4dfc54a10640d5c9fdd73b772c3c30df269d88","up":true},{"one":"6aef7e94602e6e74ab4cdb57668fb751fa20807751f786d0232870da0ae32cfd","other":"a12e73ee9cf7369825c1423608ce079cf70df75d0f0bbc56d07d484169fca26d","up":true},{"one":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","other":"500f2135db1adafe22d8a62af63f1659d6f4538f490ee8151e9db970fa24389b","up":true},{"one":"2a2bcb40404a427a50538ad9d1bfff1c5b1a7673990cbd20d830787fd96a00e5","other":"311fc84bce90b1bc1d1983c78e45b1e82a703cdb7cfbe54d51e9bc40cfe04103","up":true},{"one":"6838b88ff71c5419d3fd22a4c007a7b822405b1842d53427a9c075d093c13047","other":"44540a152f9fc924f10fefc013059d9ad228e74f921a67866ef28758f1886320","up":true},{"one":"92e2f8ec301e5858f7168b785040955d28bffae0c98e5d3f59ff9fa8636fe423","other":"a60b45dbab9c1de18783fed9eed8af6dabafbc33fdc5b3fef1ef4569bc6c70d9","up":false},{"one":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","other":"6ac71d55e984abe323c74ea85ac1aed1b06e56632c07c389e780268274a23810","up":true},{"one":"6aef7e94602e6e74ab4cdb57668fb751fa20807751f786d0232870da0ae32cfd","other":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","up":true},{"one":"d80b59b187b7765439b0e4c940f766ae71a903f599ac41a84d972e8703f7c9ff","other":"d68e095e77246c3c08df2eaec294965bca2d01fa2819d9cd06c6ef8730c59ab4","up":false},{"one":"feb3455336b26365b81dfc8cba2a390690ce996e07fcb7f1226720d263205adf","other":"f3a1438cfc8b09a3a9c1fc8455ead9041e9532ef3e9a77ffee8e5ef62af2670c","up":false},{"one":"96b723de0840f656c914674ad20204da830c04d4d6956c32e20154cbcc40f9ef","other":"92e2f8ec301e5858f7168b785040955d28bffae0c98e5d3f59ff9fa8636fe423","up":false},{"one":"dc2ae81b708a14f8b6e134986075835994c71d284b9bd9fa19d2efa73346f35e","other":"7628bcf64532ca3ec293d26f60791a2a62542b2f5f3f1c782799075383ccde00","up":true},{"one":"dc2ae81b708a14f8b6e134986075835994c71d284b9bd9fa19d2efa73346f35e","other":"b22078d9b5f3d57dec3fb1796599d5f04fcb32d4288939431ecea8516be76b58","up":true},{"one":"2dc2914f76ec9b291b2085bea6d2e0f6d45beb55bba5e3a90059b96e83010318","other":"fba89f80c35e5f6ffbe6e5521f2011caec985d820fe42759ef1aaa36d4902f37","up":true},{"one":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","other":"fa62b685ae8a9aac669d245102ce68b030f0f270d95d7c88969bfdc9978f9070","up":true},{"one":"6632a8b23d42dfc55a103d625a3644f0067abaae554eb75a78b1094ad7b24c16","other":"1f2a162a4e78c8935ed17eee206a8512ef37e126bb7ad9ff744f7b46c4b19aee","up":true},{"one":"7f5f9f955e368c906746d42953e30e781a8eb73428af5aa608af96af9bf4f60a","other":"18f6cc40773a42ddad3e3e6c5fd865a652cbb42e9054f0e46dfaed17a41d4f4f","up":true},{"one":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","other":"5d6da3260504f8063af24960ac901bdc0c54a126b82d415656a083c496c8b6b8","up":false},{"one":"6632a8b23d42dfc55a103d625a3644f0067abaae554eb75a78b1094ad7b24c16","other":"06db6e244213745e02175cca1101e72799c09236838affd8a020a05e5b88e2bd","up":true},{"one":"78bfaf122d10ee5e5304fdd247434c02b32de577bb410dc04d8ffe1906294fdb","other":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","up":true},{"one":"8bf59dc9ce97e6e2f2be364380aca38f98ddec248e730422f581f5bb56171e34","other":"8e317152297d5d048ca4512f733e63e48c3dc66910bad541d33f3cfc76828cb9","up":false},{"one":"4ae614490d8739205773d16c8027c46b67d93bd336c33071d8707fea6ac1acf3","other":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","up":true},{"one":"ebf9a6425b47a78df1c99fe4d62a48c26d2f4ef966a8592802d5b5e95e6c12eb","other":"d7f956d0f6445202699bf63700da408237fdcecd27e2e8ad969e51d7e8e61483","up":true},{"one":"954a22c6fd7d63fce47d8685d32bb892bcc0feab4209d5dc7c3601efc28e7d6d","other":"f836b3ca82f75bc0901d83b2c02e24174bee78c5eab9b55af9c492406942637a","up":true},{"one":"78bfaf122d10ee5e5304fdd247434c02b32de577bb410dc04d8ffe1906294fdb","other":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","up":true},{"one":"d80b59b187b7765439b0e4c940f766ae71a903f599ac41a84d972e8703f7c9ff","other":"d7f956d0f6445202699bf63700da408237fdcecd27e2e8ad969e51d7e8e61483","up":false},{"one":"981be0d21bdb11ac807dc29937c6a3ac2ae3c2f45269c0edc21172d5550f4da9","other":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","up":true},{"one":"ebf9a6425b47a78df1c99fe4d62a48c26d2f4ef966a8592802d5b5e95e6c12eb","other":"c243003b111259b3cdfdd4acc3610ac8da65bf58288d51133d03e680a42d7034","up":true},{"one":"954a22c6fd7d63fce47d8685d32bb892bcc0feab4209d5dc7c3601efc28e7d6d","other":"d87f983aefd8f732c29718a95f4dfc54a10640d5c9fdd73b772c3c30df269d88","up":true},{"one":"981be0d21bdb11ac807dc29937c6a3ac2ae3c2f45269c0edc21172d5550f4da9","other":"cac97c7026184a2d6ba2b17fcd0c5440bec538961af836a60f2fadb78a310a77","up":true},{"one":"97a5959b95662f6e820a1b011a7e87568e0efd2257a73bf91868c98c46e5a571","other":"96b723de0840f656c914674ad20204da830c04d4d6956c32e20154cbcc40f9ef","up":false},{"one":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","other":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","up":true},{"one":"8e317152297d5d048ca4512f733e63e48c3dc66910bad541d33f3cfc76828cb9","other":"8c5b95bf1743e2d8a97210c5b64b6732f3615c4369fa3a52e426f93e168ae16f","up":true},{"one":"97a5959b95662f6e820a1b011a7e87568e0efd2257a73bf91868c98c46e5a571","other":"954a22c6fd7d63fce47d8685d32bb892bcc0feab4209d5dc7c3601efc28e7d6d","up":true},{"one":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","other":"7e65067fa5db52d40c0a162432200ca6d0d3917b25cad15ce498fa6b5375bc8d","up":false},{"one":"f2b809e97d3c3a652c4ddce19ca12b01c1de25f2ce007d561fd5606e85540d64","other":"3f1ee2912d5446e4cdf7a42582f2e30a24bcddd6422a9b6a61f287da256d0bde","up":false},{"one":"2dc2914f76ec9b291b2085bea6d2e0f6d45beb55bba5e3a90059b96e83010318","other":"18f6cc40773a42ddad3e3e6c5fd865a652cbb42e9054f0e46dfaed17a41d4f4f","up":false},{"one":"ebf9a6425b47a78df1c99fe4d62a48c26d2f4ef966a8592802d5b5e95e6c12eb","other":"981be0d21bdb11ac807dc29937c6a3ac2ae3c2f45269c0edc21172d5550f4da9","up":true},{"one":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","other":"981be0d21bdb11ac807dc29937c6a3ac2ae3c2f45269c0edc21172d5550f4da9","up":true},{"one":"f2b809e97d3c3a652c4ddce19ca12b01c1de25f2ce007d561fd5606e85540d64","other":"dc2ae81b708a14f8b6e134986075835994c71d284b9bd9fa19d2efa73346f35e","up":false},{"one":"7e65067fa5db52d40c0a162432200ca6d0d3917b25cad15ce498fa6b5375bc8d","other":"7f5f9f955e368c906746d42953e30e781a8eb73428af5aa608af96af9bf4f60a","up":false},{"one":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","other":"57dfed651ba7f0ab6f9e1d5d2d4ae18fee51a200f50925a88c861982bb4769cb","up":false},{"one":"ebf9a6425b47a78df1c99fe4d62a48c26d2f4ef966a8592802d5b5e95e6c12eb","other":"8fe212fc9af12821f59b5f364f86f78b545be501933b926fc47b6d7deafca941","up":false},{"one":"8ec663c22eff8ed185b8194c1240ff58df131c5f310df0a91b9790ec166e4346","other":"8e317152297d5d048ca4512f733e63e48c3dc66910bad541d33f3cfc76828cb9","up":false},{"one":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","other":"78bfaf122d10ee5e5304fdd247434c02b32de577bb410dc04d8ffe1906294fdb","up":false},{"one":"6aef7e94602e6e74ab4cdb57668fb751fa20807751f786d0232870da0ae32cfd","other":"6e8315dc552c8112dcb01e9302b5e47fef7fbdc81fbe7a1d0b27e0cf80a65ca4","up":false},{"one":"f2b809e97d3c3a652c4ddce19ca12b01c1de25f2ce007d561fd5606e85540d64","other":"d55213d3dc27542a780472564a4a8c1ce47668e05710be178baf3238ca8886b6","up":false},{"one":"4ae614490d8739205773d16c8027c46b67d93bd336c33071d8707fea6ac1acf3","other":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","up":false},{"one":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","other":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","up":false},{"one":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","other":"5b36b0ab4872865a4f86f2aa137ba8bb4273090e57fde30ec27a58acb158c22f","up":false},{"one":"f2b809e97d3c3a652c4ddce19ca12b01c1de25f2ce007d561fd5606e85540d64","other":"d9169f80683a2552475d60e6982f70c6605f9b40300f2b60895aa21cdc42db89","up":false},{"one":"ebf9a6425b47a78df1c99fe4d62a48c26d2f4ef966a8592802d5b5e95e6c12eb","other":"92e2f8ec301e5858f7168b785040955d28bffae0c98e5d3f59ff9fa8636fe423","up":false},{"one":"f2b809e97d3c3a652c4ddce19ca12b01c1de25f2ce007d561fd5606e85540d64","other":"d80b59b187b7765439b0e4c940f766ae71a903f599ac41a84d972e8703f7c9ff","up":false},{"one":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","other":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","up":false},{"one":"ebf9a6425b47a78df1c99fe4d62a48c26d2f4ef966a8592802d5b5e95e6c12eb","other":"931a36980ca0788616cb1c9851f6069e44d94671a2a056e12a7d7cafe597dee5","up":false},{"one":"8bf59dc9ce97e6e2f2be364380aca38f98ddec248e730422f581f5bb56171e34","other":"8c5b95bf1743e2d8a97210c5b64b6732f3615c4369fa3a52e426f93e168ae16f","up":false},{"one":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","other":"954a22c6fd7d63fce47d8685d32bb892bcc0feab4209d5dc7c3601efc28e7d6d","up":false},{"one":"d87f983aefd8f732c29718a95f4dfc54a10640d5c9fdd73b772c3c30df269d88","other":"dc2ae81b708a14f8b6e134986075835994c71d284b9bd9fa19d2efa73346f35e","up":false},{"one":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","other":"e5cd769dbcc429587e3a92b192575965c0ac18e88c9a54b9f75936d929c43c62","up":false},{"one":"6a57cd355d641d1506957a988d93b2d3f9a613a7354c24130ad97dbc4c8296ce","other":"6838b88ff71c5419d3fd22a4c007a7b822405b1842d53427a9c075d093c13047","up":false},{"one":"954a22c6fd7d63fce47d8685d32bb892bcc0feab4209d5dc7c3601efc28e7d6d","other":"92e2f8ec301e5858f7168b785040955d28bffae0c98e5d3f59ff9fa8636fe423","up":false},{"one":"96b723de0840f656c914674ad20204da830c04d4d6956c32e20154cbcc40f9ef","other":"9cbc96aad8fbdde0c2f084f3cef79b56c150ec7fc30dcc2d8535efe9b337f4c2","up":false},{"one":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","other":"e4254cda903275296f17b47e5dcabbf16d1fa7432b64e0e6e0562fcd89de534d","up":false},{"one":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","other":"4309c2d963d91e65f7e8e8ab42df52e75f8d377d4eb07d1a048c0082af4f501e","up":false},{"one":"265040d12d0b79c8609232060b3ddd6ac16fb895b52bb7f3fee5c6e8b63a4d79","other":"2dc2914f76ec9b291b2085bea6d2e0f6d45beb55bba5e3a90059b96e83010318","up":false},{"one":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","other":"931a36980ca0788616cb1c9851f6069e44d94671a2a056e12a7d7cafe597dee5","up":false},{"one":"78bfaf122d10ee5e5304fdd247434c02b32de577bb410dc04d8ffe1906294fdb","other":"7e65067fa5db52d40c0a162432200ca6d0d3917b25cad15ce498fa6b5375bc8d","up":false},{"one":"6a57cd355d641d1506957a988d93b2d3f9a613a7354c24130ad97dbc4c8296ce","other":"6e8315dc552c8112dcb01e9302b5e47fef7fbdc81fbe7a1d0b27e0cf80a65ca4","up":false},{"one":"6632a8b23d42dfc55a103d625a3644f0067abaae554eb75a78b1094ad7b24c16","other":"61207f4fc13c899b88e9b727eb202a8bab00868bd07d945100b2e56dbcd67683","up":false},{"one":"2dc2914f76ec9b291b2085bea6d2e0f6d45beb55bba5e3a90059b96e83010318","other":"06db6e244213745e02175cca1101e72799c09236838affd8a020a05e5b88e2bd","up":false},{"one":"6a57cd355d641d1506957a988d93b2d3f9a613a7354c24130ad97dbc4c8296ce","other":"6aef7e94602e6e74ab4cdb57668fb751fa20807751f786d0232870da0ae32cfd","up":false},{"one":"96e99adfb8d8d5386d57334975f9525de0abd4418e528f2a8c1a61cc21871423","other":"96b723de0840f656c914674ad20204da830c04d4d6956c32e20154cbcc40f9ef","up":false},{"one":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","other":"e0ac6b34d04bc036026767288a8f0f96720f4a3efa5b52b3b86439fba562bcf4","up":false},{"one":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","other":"425959a8050805baa98c05ccd640261edd3f26e6cf3205b4a79d2b086c070339","up":false},{"one":"48a19dea03eb8068e8925407c582a0913fd24127a4f76b38360171c5b0b0c545","other":"500f2135db1adafe22d8a62af63f1659d6f4538f490ee8151e9db970fa24389b","up":false},{"one":"2dc2914f76ec9b291b2085bea6d2e0f6d45beb55bba5e3a90059b96e83010318","other":"1f2a162a4e78c8935ed17eee206a8512ef37e126bb7ad9ff744f7b46c4b19aee","up":false},{"one":"78bfaf122d10ee5e5304fdd247434c02b32de577bb410dc04d8ffe1906294fdb","other":"7c766b57273777cc67f74870560449c9542cfec734b58b167aef99e09aeae962","up":false},{"one":"a3fcef8ff8bc702c6452b0c4a7b899dfc551bb8293b17f2f6133cc78d66581d8","other":"a2504bc38c94b7912bfafc42c7a4531cd27775473071e7a26173c4db932bf9ec","up":false},{"one":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","other":"4ae614490d8739205773d16c8027c46b67d93bd336c33071d8707fea6ac1acf3","up":false},{"one":"7bcfc9285f6a2ce0d3c3cc4455c026a00e96ed6363016e09ed62ec03a2c9f681","other":"7f5f9f955e368c906746d42953e30e781a8eb73428af5aa608af96af9bf4f60a","up":false},{"one":"059a2d0e5db2c0204c10b66ef04bacc96d64453073efeee81ba7e30d43061a61","other":"2dc2914f76ec9b291b2085bea6d2e0f6d45beb55bba5e3a90059b96e83010318","up":false},{"one":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","other":"ebf9a6425b47a78df1c99fe4d62a48c26d2f4ef966a8592802d5b5e95e6c12eb","up":false},{"one":"059a2d0e5db2c0204c10b66ef04bacc96d64453073efeee81ba7e30d43061a61","other":"18f6cc40773a42ddad3e3e6c5fd865a652cbb42e9054f0e46dfaed17a41d4f4f","up":false},{"one":"a2504bc38c94b7912bfafc42c7a4531cd27775473071e7a26173c4db932bf9ec","other":"aec520f507eedd5d752b6ba8a0b509f14105e5e457eef0b6537015e39569762b","up":false},{"one":"954a22c6fd7d63fce47d8685d32bb892bcc0feab4209d5dc7c3601efc28e7d6d","other":"9f9700ca395f36c84a38838c11f6889d00015d97aac7943c27cf74393e3fc22f","up":false},{"one":"ea9490f17813e0e5f39e8065f4c3954163f3bca6e80afca7a2c76d5ca3116e53","other":"ec3b5ea0bc43c1ef1ae2744ea747887dc58ef3526ea8601856f1e18e7f01a197","up":false},{"one":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","other":"4778cff42bfc5ecf6a87e7a4e5a632839d30b8fb10e62f81eb75d915e6f457ae","up":false},{"one":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","other":"42365cb134e329e7205559838f924affea893c832ea9d86c4e1b19ec2c6ce5b1","up":false},{"one":"265040d12d0b79c8609232060b3ddd6ac16fb895b52bb7f3fee5c6e8b63a4d79","other":"2a2bcb40404a427a50538ad9d1bfff1c5b1a7673990cbd20d830787fd96a00e5","up":false},{"one":"059a2d0e5db2c0204c10b66ef04bacc96d64453073efeee81ba7e30d43061a61","other":"237b54aa7ef077972fc65fb222ce70df0129f8c8d4d35505fcbdc281bb7d28ce","up":false},{"one":"dc2ae81b708a14f8b6e134986075835994c71d284b9bd9fa19d2efa73346f35e","other":"d80b59b187b7765439b0e4c940f766ae71a903f599ac41a84d972e8703f7c9ff","up":false},{"one":"059a2d0e5db2c0204c10b66ef04bacc96d64453073efeee81ba7e30d43061a61","other":"265040d12d0b79c8609232060b3ddd6ac16fb895b52bb7f3fee5c6e8b63a4d79","up":false},{"one":"a2504bc38c94b7912bfafc42c7a4531cd27775473071e7a26173c4db932bf9ec","other":"ac23190ee9ee30dd793bf3edef355450522d3f56973ab6b1acbb6dfd9d64137a","up":false},{"one":"2a2bcb40404a427a50538ad9d1bfff1c5b1a7673990cbd20d830787fd96a00e5","other":"2dc2914f76ec9b291b2085bea6d2e0f6d45beb55bba5e3a90059b96e83010318","up":false},{"one":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","other":"53eaa90913134a5bdaa780d18bcd33e85b1932d9b15bcc1096fc4658ea5ea7f8","up":false},{"one":"48274a353dee6c042537c999f89ccdddd7a9c139fa8b824750496bd7cf2726b2","other":"436c8c2a56cbc03310237c646c7226f8e4bd672f68251ac8cc9678d3126e7aa8","up":false}]} \ No newline at end of file diff --git a/swarm/network/stream/testing/snapshot_16.json b/swarm/network/stream/testing/snapshot_16.json deleted file mode 100644 index e818da05257c..000000000000 --- a/swarm/network/stream/testing/snapshot_16.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes":[{"node":{"config":{"id":"a3790562c8209b7e96e7abef9ad28de2e470a01927234e4848966e0cd15652e9","private_key":"3ff3a0c7dc8b63cac9255233d84cccad53d61f8c9e47539329c07e4f248fc6dd","name":"node_279f0a191892115b8db4e2689cc5e1bc19019e8f5228f4f093f35c48fa2f606fd00a2ec5cf0fa4657ad3606e14643c3be8d49350ad2673f2ad12e17c0174b0d8","services":["streamer"],"enable_msg_events":true,"port":62851},"up":true}},{"node":{"config":{"id":"4ce65548fc8490c02e4c6c0549be7329a8a05c090d62870ad1075e52e204a3b5","private_key":"1ab5fd3f1885661af1829b42683ca1379ca90b1b2c5a0132027346c74001a154","name":"node_2c3d17a3b019d9d32922f06aef03ac03f67777e382f0b86c6e4dac07e590f2dbf4cbd1cc7ed136b52bba897e7163dee926caa627111db6d948581ad1b2edbc3f","services":["streamer"],"enable_msg_events":true,"port":62852},"up":true}},{"node":{"config":{"id":"8bf97ec29b6b3ca638ed82fa30d03d0d8309c9e8f1db2071f4a37de805327108","private_key":"a90fea5197ac84a162db7a06ec739e075ee334026e792d83a3269526888c8b5d","name":"node_6e327c2d9d3ffdae707ed067d15b83ad0ea81b96bb2a07331436f01c1e194ed90f630ef0eb6511f4849ede30c403e3c4ff3c2cb8021317de97f3ed5b1d990f49","services":["streamer"],"enable_msg_events":true,"port":62853},"up":true}},{"node":{"config":{"id":"9121c3fea7ccc99775fb5ade460f7a0cb76003231d13d44ed3e4f3cca1947227","private_key":"76b6f09c28f5568e7724e9850116bdbd6356152fefedc4b0a9747361d712964b","name":"node_9aa1df8ddddd035e81ffc950f063714c3b6be24013108f8b99c32c25fc3a8582904f68c73fe63ebfb96143a12ab398f36c4070a3055c039491ca2f20be6774c2","services":["streamer"],"enable_msg_events":true,"port":62854},"up":true}},{"node":{"config":{"id":"fb555885b96d5742a10e688bd7c1bb842f0434c319bf076d07f545e4c0550601","private_key":"f7e7d60abb7d43d973131804e684ec99e77e81bf38165dda29f3ae026eb169f1","name":"node_a1226991909b92dd4f22150b798e2b6312ffc62bad7a47d250e811c7e60cf5ceb6a87998f295e0bd392c0f5c15213115000781e498dd7eae79b67c7e7e19cf81","services":["streamer"],"enable_msg_events":true,"port":62855},"up":true}},{"node":{"config":{"id":"f64d3106d2bb99e69e26c07de168f7fceaf85672859f7c1c038fd4dd6b6d7588","private_key":"f88cd88c2c219d13b699ba7c7cf9adf16fc10be5d8e1f35b2ea260e7987ab76e","name":"node_1dcbfc79418f9a665e3154e07663b296ce086e54c23458b7113a011dca62e635245cab144d392716e5e90b60336b60927270b72bb4a1c5c85fbf959060fb3ee4","services":["streamer"],"enable_msg_events":true,"port":62856},"up":true}},{"node":{"config":{"id":"3c4287b7722f71eb03a3d54fb6b18aeb0bf8495cffbe8c103fe90229f08392ca","private_key":"8fa1673a72b2ab000a4a7c8ad4540484d5286b69c93a5c608ef0977f783b7c4c","name":"node_571afb6ce94d28cb7d5cd49b3af35d2b1e44c917a9d3d207ab6c7ae960eb625377334e62317f6c69b3004e2eef92682649df828f4a645a75309ee7014ee4a940","services":["streamer"],"enable_msg_events":true,"port":62857},"up":true}},{"node":{"config":{"id":"615c7fcf7a80635e42719c0ae1fb924ba703ee39e021898be56b81ef0f575f62","private_key":"daa9c74fbb0b897acdf20f1b955a608b086ce5b45dc8e2b76a44f277d25e3377","name":"node_f1e669bb6e5b89c04e07e3dcdc445d498ac54c8ab2e9a62e622a492b6f9781bfa27f75ef7448d06caa28d9afa40c1c089066358cbcca40018bc0f903eab9508e","services":["streamer"],"enable_msg_events":true,"port":62858},"up":true}},{"node":{"config":{"id":"84842bc52628c0bed88539ee40f211de307b3e728ab5c0ca78032b86028b49db","private_key":"eb94b600be5949e3ca545e81b4d0a5fff3ae9f51f63c722d10f5098f9e1a883a","name":"node_526d25e0aa47e64d8f681255348a3d3c94fc68bedcb7111532f4ed50d47ef064b1e97e3a6bcd13ac8eaf44ce676b22d2b343a94dfa38b48cf964bfc3f338d4bd","services":["streamer"],"enable_msg_events":true,"port":62859},"up":true}},{"node":{"config":{"id":"91129c7135bff2b7cb1e1162bdd5399192790bdb4fc2eaa5b4cea53fe7a69d8c","private_key":"4e5d074bc46efbd0d18310e6a74e72e98b92080329d9c4e2b5ae99eb2fcfaf89","name":"node_fa81c440829adcbb3eba9196dc8255e319a976e53db0aceedcda25c2605f21b8778bf8c89e08744f7455c909568855f2a5be7bc88646796d21598ddda810390a","services":["streamer"],"enable_msg_events":true,"port":62860},"up":true}},{"node":{"config":{"id":"6d8b6881d36bc5f4a4a82f5d707807af2eef2eaf4527eb6b16f62cd75a92bb8c","private_key":"73c752d26f3438acdc54e9eaae6b1194d3706c65932939a593f1c8c5d451daf6","name":"node_5702eeccd7cb8c18dd25e5700919a544fb5e15fd53c5d4b56226021a9031a2206a694eb3b7334e4bd24fc510f7c1ea4f7a98e17e3394b4bd8d356a4d048b47dc","services":["streamer"],"enable_msg_events":true,"port":62861},"up":true}},{"node":{"config":{"id":"55cc9bd08390eec7bc050924e3011b251c6c4e088ac2adda27493064b75f0ab8","private_key":"2e6321d9dda81fc0f863f09868b280dea3300105cf9d224294aa138caf10b4a6","name":"node_6311f5a830849b47094d84129fd987fcd3d511d000bf0a8b888c9a457545950fdae6ae10a2a85b47721a1d1ece8c1c62d866a26577380048d30f205c4e7cd7e8","services":["streamer"],"enable_msg_events":true,"port":62862},"up":true}},{"node":{"config":{"id":"a873e971042dfd5aaeb56d7d9089e35bc8dd410451a66740ebe7ea0752870120","private_key":"200b7caaf8a33b8ea47947eb842fb8d8aac90951c70bb2f555f902380eb7f1ee","name":"node_dbef367277df652c8d3380efcf5f9d445d77fa6e7957b50b2467f90b7190e70d4eb4febbbffa6b3fc2b04bbf74dc7191e2b98923c3a62ecb80ba5336195994c7","services":["streamer"],"enable_msg_events":true,"port":62863},"up":true}},{"node":{"config":{"id":"9fcdef2b818e8a4fcc07fedb706d9f88cc59703230af4f52043c84f9bd37cc57","private_key":"b58a24805bb0c417e5666f2bf3f3342b8359d3c0d30a631c0104fcdfb6d0809b","name":"node_a4cdf723b3d7ead36bcb1bba582c8758271d5f08651694e1def0ae2ceca079d4fca81730a84a14749a81bc3ea41a66f1320788310823de5d1ceecd608ed5b88e","services":["streamer"],"enable_msg_events":true,"port":62864},"up":true}},{"node":{"config":{"id":"fc373cf4e892501073ed87b98231f0d001e6db70a1a08148035fbdfa2dffb3d3","private_key":"422d3b2c1b37b851ad524c8f26700fb5eec0d9a2fff1cb157ae931baa52d9521","name":"node_67cc8be0fd092c82e53efc53c418db257eff1135ad0058fd10e3eb77b205cb3e6fa87dfed4ec008c371f77546d4c117d8c34734bfe594da4c96fc08e0d2ede32","services":["streamer"],"enable_msg_events":true,"port":62865},"up":true}},{"node":{"config":{"id":"af99d46c187735ff037e1264f2dbca2985694d906de0e2c9e52d5513a6feb331","private_key":"7871010c8b88b9594c71b9f43aecfefdca39da348e31f47ef2645c20c100d072","name":"node_4da7d1c9577afb52a5fe1de9f99824ceff314828c581caed8a3a33168df699d7b3befb4e888419264a9eeb3c9e5e9dac468e01edd71eb6f0ef41d15f7651042d","services":["streamer"],"enable_msg_events":true,"port":62866},"up":true}}],"conns":[{"one":"af99d46c187735ff037e1264f2dbca2985694d906de0e2c9e52d5513a6feb331","other":"a3790562c8209b7e96e7abef9ad28de2e470a01927234e4848966e0cd15652e9","up":true},{"one":"f64d3106d2bb99e69e26c07de168f7fceaf85672859f7c1c038fd4dd6b6d7588","other":"3c4287b7722f71eb03a3d54fb6b18aeb0bf8495cffbe8c103fe90229f08392ca","up":true},{"one":"a3790562c8209b7e96e7abef9ad28de2e470a01927234e4848966e0cd15652e9","other":"4ce65548fc8490c02e4c6c0549be7329a8a05c090d62870ad1075e52e204a3b5","up":true},{"one":"4ce65548fc8490c02e4c6c0549be7329a8a05c090d62870ad1075e52e204a3b5","other":"8bf97ec29b6b3ca638ed82fa30d03d0d8309c9e8f1db2071f4a37de805327108","up":true},{"one":"8bf97ec29b6b3ca638ed82fa30d03d0d8309c9e8f1db2071f4a37de805327108","other":"9121c3fea7ccc99775fb5ade460f7a0cb76003231d13d44ed3e4f3cca1947227","up":true},{"one":"9121c3fea7ccc99775fb5ade460f7a0cb76003231d13d44ed3e4f3cca1947227","other":"fb555885b96d5742a10e688bd7c1bb842f0434c319bf076d07f545e4c0550601","up":true},{"one":"fb555885b96d5742a10e688bd7c1bb842f0434c319bf076d07f545e4c0550601","other":"f64d3106d2bb99e69e26c07de168f7fceaf85672859f7c1c038fd4dd6b6d7588","up":true},{"one":"6d8b6881d36bc5f4a4a82f5d707807af2eef2eaf4527eb6b16f62cd75a92bb8c","other":"55cc9bd08390eec7bc050924e3011b251c6c4e088ac2adda27493064b75f0ab8","up":true},{"one":"3c4287b7722f71eb03a3d54fb6b18aeb0bf8495cffbe8c103fe90229f08392ca","other":"615c7fcf7a80635e42719c0ae1fb924ba703ee39e021898be56b81ef0f575f62","up":true},{"one":"615c7fcf7a80635e42719c0ae1fb924ba703ee39e021898be56b81ef0f575f62","other":"84842bc52628c0bed88539ee40f211de307b3e728ab5c0ca78032b86028b49db","up":true},{"one":"84842bc52628c0bed88539ee40f211de307b3e728ab5c0ca78032b86028b49db","other":"91129c7135bff2b7cb1e1162bdd5399192790bdb4fc2eaa5b4cea53fe7a69d8c","up":true},{"one":"91129c7135bff2b7cb1e1162bdd5399192790bdb4fc2eaa5b4cea53fe7a69d8c","other":"6d8b6881d36bc5f4a4a82f5d707807af2eef2eaf4527eb6b16f62cd75a92bb8c","up":true},{"one":"a873e971042dfd5aaeb56d7d9089e35bc8dd410451a66740ebe7ea0752870120","other":"9fcdef2b818e8a4fcc07fedb706d9f88cc59703230af4f52043c84f9bd37cc57","up":true},{"one":"55cc9bd08390eec7bc050924e3011b251c6c4e088ac2adda27493064b75f0ab8","other":"a873e971042dfd5aaeb56d7d9089e35bc8dd410451a66740ebe7ea0752870120","up":true},{"one":"9fcdef2b818e8a4fcc07fedb706d9f88cc59703230af4f52043c84f9bd37cc57","other":"fc373cf4e892501073ed87b98231f0d001e6db70a1a08148035fbdfa2dffb3d3","up":true},{"one":"fc373cf4e892501073ed87b98231f0d001e6db70a1a08148035fbdfa2dffb3d3","other":"af99d46c187735ff037e1264f2dbca2985694d906de0e2c9e52d5513a6feb331","up":true},{"one":"a3790562c8209b7e96e7abef9ad28de2e470a01927234e4848966e0cd15652e9","other":"a873e971042dfd5aaeb56d7d9089e35bc8dd410451a66740ebe7ea0752870120","up":true},{"one":"9121c3fea7ccc99775fb5ade460f7a0cb76003231d13d44ed3e4f3cca1947227","other":"9fcdef2b818e8a4fcc07fedb706d9f88cc59703230af4f52043c84f9bd37cc57","up":true},{"one":"fb555885b96d5742a10e688bd7c1bb842f0434c319bf076d07f545e4c0550601","other":"fc373cf4e892501073ed87b98231f0d001e6db70a1a08148035fbdfa2dffb3d3","up":true},{"one":"a873e971042dfd5aaeb56d7d9089e35bc8dd410451a66740ebe7ea0752870120","other":"af99d46c187735ff037e1264f2dbca2985694d906de0e2c9e52d5513a6feb331","up":true},{"one":"9fcdef2b818e8a4fcc07fedb706d9f88cc59703230af4f52043c84f9bd37cc57","other":"8bf97ec29b6b3ca638ed82fa30d03d0d8309c9e8f1db2071f4a37de805327108","up":true},{"one":"af99d46c187735ff037e1264f2dbca2985694d906de0e2c9e52d5513a6feb331","other":"3c4287b7722f71eb03a3d54fb6b18aeb0bf8495cffbe8c103fe90229f08392ca","up":true},{"one":"91129c7135bff2b7cb1e1162bdd5399192790bdb4fc2eaa5b4cea53fe7a69d8c","other":"9121c3fea7ccc99775fb5ade460f7a0cb76003231d13d44ed3e4f3cca1947227","up":true},{"one":"f64d3106d2bb99e69e26c07de168f7fceaf85672859f7c1c038fd4dd6b6d7588","other":"fc373cf4e892501073ed87b98231f0d001e6db70a1a08148035fbdfa2dffb3d3","up":true},{"one":"a3790562c8209b7e96e7abef9ad28de2e470a01927234e4848966e0cd15652e9","other":"f64d3106d2bb99e69e26c07de168f7fceaf85672859f7c1c038fd4dd6b6d7588","up":true},{"one":"9121c3fea7ccc99775fb5ade460f7a0cb76003231d13d44ed3e4f3cca1947227","other":"3c4287b7722f71eb03a3d54fb6b18aeb0bf8495cffbe8c103fe90229f08392ca","up":true},{"one":"fb555885b96d5742a10e688bd7c1bb842f0434c319bf076d07f545e4c0550601","other":"3c4287b7722f71eb03a3d54fb6b18aeb0bf8495cffbe8c103fe90229f08392ca","up":true},{"one":"91129c7135bff2b7cb1e1162bdd5399192790bdb4fc2eaa5b4cea53fe7a69d8c","other":"9fcdef2b818e8a4fcc07fedb706d9f88cc59703230af4f52043c84f9bd37cc57","up":true},{"one":"af99d46c187735ff037e1264f2dbca2985694d906de0e2c9e52d5513a6feb331","other":"8bf97ec29b6b3ca638ed82fa30d03d0d8309c9e8f1db2071f4a37de805327108","up":true},{"one":"9fcdef2b818e8a4fcc07fedb706d9f88cc59703230af4f52043c84f9bd37cc57","other":"3c4287b7722f71eb03a3d54fb6b18aeb0bf8495cffbe8c103fe90229f08392ca","up":true},{"one":"a873e971042dfd5aaeb56d7d9089e35bc8dd410451a66740ebe7ea0752870120","other":"f64d3106d2bb99e69e26c07de168f7fceaf85672859f7c1c038fd4dd6b6d7588","up":true},{"one":"3c4287b7722f71eb03a3d54fb6b18aeb0bf8495cffbe8c103fe90229f08392ca","other":"6d8b6881d36bc5f4a4a82f5d707807af2eef2eaf4527eb6b16f62cd75a92bb8c","up":true},{"one":"84842bc52628c0bed88539ee40f211de307b3e728ab5c0ca78032b86028b49db","other":"8bf97ec29b6b3ca638ed82fa30d03d0d8309c9e8f1db2071f4a37de805327108","up":true},{"one":"55cc9bd08390eec7bc050924e3011b251c6c4e088ac2adda27493064b75f0ab8","other":"4ce65548fc8490c02e4c6c0549be7329a8a05c090d62870ad1075e52e204a3b5","up":true},{"one":"615c7fcf7a80635e42719c0ae1fb924ba703ee39e021898be56b81ef0f575f62","other":"6d8b6881d36bc5f4a4a82f5d707807af2eef2eaf4527eb6b16f62cd75a92bb8c","up":true},{"one":"a3790562c8209b7e96e7abef9ad28de2e470a01927234e4848966e0cd15652e9","other":"84842bc52628c0bed88539ee40f211de307b3e728ab5c0ca78032b86028b49db","up":true},{"one":"9121c3fea7ccc99775fb5ade460f7a0cb76003231d13d44ed3e4f3cca1947227","other":"af99d46c187735ff037e1264f2dbca2985694d906de0e2c9e52d5513a6feb331","up":true},{"one":"3c4287b7722f71eb03a3d54fb6b18aeb0bf8495cffbe8c103fe90229f08392ca","other":"55cc9bd08390eec7bc050924e3011b251c6c4e088ac2adda27493064b75f0ab8","up":true},{"one":"615c7fcf7a80635e42719c0ae1fb924ba703ee39e021898be56b81ef0f575f62","other":"55cc9bd08390eec7bc050924e3011b251c6c4e088ac2adda27493064b75f0ab8","up":true},{"one":"84842bc52628c0bed88539ee40f211de307b3e728ab5c0ca78032b86028b49db","other":"9121c3fea7ccc99775fb5ade460f7a0cb76003231d13d44ed3e4f3cca1947227","up":true},{"one":"91129c7135bff2b7cb1e1162bdd5399192790bdb4fc2eaa5b4cea53fe7a69d8c","other":"f64d3106d2bb99e69e26c07de168f7fceaf85672859f7c1c038fd4dd6b6d7588","up":true},{"one":"6d8b6881d36bc5f4a4a82f5d707807af2eef2eaf4527eb6b16f62cd75a92bb8c","other":"4ce65548fc8490c02e4c6c0549be7329a8a05c090d62870ad1075e52e204a3b5","up":true},{"one":"fc373cf4e892501073ed87b98231f0d001e6db70a1a08148035fbdfa2dffb3d3","other":"3c4287b7722f71eb03a3d54fb6b18aeb0bf8495cffbe8c103fe90229f08392ca","up":true},{"one":"3c4287b7722f71eb03a3d54fb6b18aeb0bf8495cffbe8c103fe90229f08392ca","other":"4ce65548fc8490c02e4c6c0549be7329a8a05c090d62870ad1075e52e204a3b5","up":true},{"one":"615c7fcf7a80635e42719c0ae1fb924ba703ee39e021898be56b81ef0f575f62","other":"4ce65548fc8490c02e4c6c0549be7329a8a05c090d62870ad1075e52e204a3b5","up":true},{"one":"84842bc52628c0bed88539ee40f211de307b3e728ab5c0ca78032b86028b49db","other":"9fcdef2b818e8a4fcc07fedb706d9f88cc59703230af4f52043c84f9bd37cc57","up":true},{"one":"91129c7135bff2b7cb1e1162bdd5399192790bdb4fc2eaa5b4cea53fe7a69d8c","other":"a3790562c8209b7e96e7abef9ad28de2e470a01927234e4848966e0cd15652e9","up":true},{"one":"8bf97ec29b6b3ca638ed82fa30d03d0d8309c9e8f1db2071f4a37de805327108","other":"91129c7135bff2b7cb1e1162bdd5399192790bdb4fc2eaa5b4cea53fe7a69d8c","up":true},{"one":"8bf97ec29b6b3ca638ed82fa30d03d0d8309c9e8f1db2071f4a37de805327108","other":"fb555885b96d5742a10e688bd7c1bb842f0434c319bf076d07f545e4c0550601","up":true},{"one":"84842bc52628c0bed88539ee40f211de307b3e728ab5c0ca78032b86028b49db","other":"fb555885b96d5742a10e688bd7c1bb842f0434c319bf076d07f545e4c0550601","up":true}]} \ No newline at end of file diff --git a/swarm/network/stream/testing/snapshot_256.json b/swarm/network/stream/testing/snapshot_256.json deleted file mode 100644 index df5f7f4e6e8e..000000000000 --- a/swarm/network/stream/testing/snapshot_256.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes":[{"node":{"config":{"id":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","private_key":"859f1f6b352bfdd6f8b7a7a80ef60a7fa41a514a091d5c19d0ea7ab44c24c614","name":"node_57fb1bb32bb2b1a87e44c6a7557e9c47c2032d107bcb0e34acd9ffd54d5d3df5701232db16f62f7549ba2721021898b03160b1dc5201e79606648c1dbc4a373b","services":["streamer"],"enable_msg_events":true,"port":63226},"up":true}},{"node":{"config":{"id":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","private_key":"681d7bb10f2022ae6f74a5afa7e0d42dfa6efd2e55abe22a395db640b6f5c70b","name":"node_7f357b8d81bc2e5c6fcb081f23a42b3e2439f050d45c45851994a268d1c71fcb1f39f8c3f75ce921d3fbf80fc0e73f0b9609d72001dc4a6b65b526d312a4f43a","services":["streamer"],"enable_msg_events":true,"port":63227},"up":true}},{"node":{"config":{"id":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","private_key":"fe0d4b827c015eddc1928a6f9691bb26ba2100a6232168890d11ebc7f7222891","name":"node_cb94a8270d0050378116b5815ff17dafbe20f5303839c4fd57b981d4687a4b145e4bccec7e9af9b9475005930148ffbfa97781a0762c8e9f352dc95565fc8732","services":["streamer"],"enable_msg_events":true,"port":63228},"up":true}},{"node":{"config":{"id":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","private_key":"0172e330c8b3ba8151689716c455034d43e9600eafceaffa6c9b162c36920109","name":"node_76cbf9fc0f3517fb615fa96104780218e85d064ee5a1d518f3b78f4dbebf144df38c427445fdd6c9b436489c00ff9497d747a8fd70f380f95601d59e52e021e8","services":["streamer"],"enable_msg_events":true,"port":63229},"up":true}},{"node":{"config":{"id":"34e48bd6562a308ccf6a4a0257f253e908e97d16defe2f1a31e046d5558a4769","private_key":"2e9b4ad5021be5fdbcdd374c9569d467f370261662cb56e23b98ff5588c8d99d","name":"node_e023a51c5bd26e1754d6461345143e94baeac799947579ec7adeccd105de39f85afbceeb5037cfc0b820b9f4446433f034d9e7de7326d45df5a967a8099dbb10","services":["streamer"],"enable_msg_events":true,"port":63230},"up":true}},{"node":{"config":{"id":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","private_key":"059dc1a60c68b91d75f771391bae103fb3d7dc30c5dfdc9498980591bc09e0df","name":"node_68cc8b9a995eef6170abf88bd5fedca06ffcc8d75f247f6041c13e7dfd292727ebda1ff86b776e33b6634355c55e0f8c22fcdb5f9a6e2f22fe1af7854c3b4273","services":["streamer"],"enable_msg_events":true,"port":63231},"up":true}},{"node":{"config":{"id":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","private_key":"bf0aa2cb855d1702f8353aaf7cf03b846cbe1bdc6a0b66b18dfa8f03ab7dee43","name":"node_54b3c57a4adf4dbaf31b85ed1b8dccd6249faa16dd96dd230189f47f57970caeed36627cc8e1b0794707418d580f5bd6001c07be6a79216695f1253db24926bb","services":["streamer"],"enable_msg_events":true,"port":63232},"up":true}},{"node":{"config":{"id":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","private_key":"8b9685b4c43d0de26290fc8646737787fe8ff7e6c32c082060380b41fe2c5f3c","name":"node_df4c3505e09e1baa88470117b964e758c94f77a4546489ab5ab7f4aebfdce424729ace8867475715fb68f05c66c9674a5767549e266c4fadd1c703ddd735908f","services":["streamer"],"enable_msg_events":true,"port":63233},"up":true}},{"node":{"config":{"id":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","private_key":"a5bf3b5b166cc21128321759e5be62f691455e382d8ed35e48805cbb2d884a97","name":"node_24662a43944228e4ff74736bcf0a0a4f79c87ecfdb739225b2fc45110c11bfc38aaf2d09d98e5817477848e553f187ac5408bdb864518dfa7f5df542768828db","services":["streamer"],"enable_msg_events":true,"port":63234},"up":true}},{"node":{"config":{"id":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","private_key":"b64868a78b7e20e3914999f841765a445e99246e67a0d8706639a2e940c1dfef","name":"node_27d9048707a89f1a61e44fdae523c0128f2a7050308bd3cfefa6c3e03040a0e22f11ab41275f22b8706e6695ab93320e2af35179d772581db5f49f960750f2f7","services":["streamer"],"enable_msg_events":true,"port":63235},"up":true}},{"node":{"config":{"id":"9a09aa168f7f4ebd90652d6900ace1ba88b8785290b2687783d4a23e9bdb90ee","private_key":"f72fdd8aca59748c35831f451f405cbd0aa950ce62699f1f7c9c10d76cf1e588","name":"node_ea57884dd42aec5a76eab43483bf7c4ae2c19eebf365013731de64494d10a3b4cfe34c86c79437f9012bc8cf6bd761435eefadec17600f4b9e4bc234532d6282","services":["streamer"],"enable_msg_events":true,"port":63236},"up":true}},{"node":{"config":{"id":"5b308b25cfe30d0b68784dc73be464dfa85a8e49ddd6f6271ff0ee2b63798928","private_key":"9a8b5a154d6b376a8d8a1c5357272f164206a01554ae4fcdf479ffa3e80ec4a6","name":"node_3b570117addeec03c2694baf28e246f91907268fe665c6c90356120332794258ee57a60853c19e9fe25d042ca42a5be7fe0797d84199174de8132f1ef4a9349b","services":["streamer"],"enable_msg_events":true,"port":63237},"up":true}},{"node":{"config":{"id":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","private_key":"5007a8f6bdeaf7620240976e1d41aca23e841378ec3a157b199d5fea27b1e0c7","name":"node_0cacda5560faff9d20e29a0625d3cd340ec075f198a50ab8b79338cee0e0db84c7c7e71042fd8c8f73e507b570de5d26c39473136b575f0167625a1168309ec5","services":["streamer"],"enable_msg_events":true,"port":63238},"up":true}},{"node":{"config":{"id":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","private_key":"a7d2d7f12b632dd6c409c8e7cd5da6f2009d1ed9d27eff101c0e850934695ee6","name":"node_88d5c5fe61ee1e6fa5878e0eaa13bdabaa0ec3d38a5d25b95a467da7627424f25fb5d30535aa0a5ad01c16fe2598dbf570b78cc152a7f4ef66d46fc9ae43592f","services":["streamer"],"enable_msg_events":true,"port":63239},"up":true}},{"node":{"config":{"id":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","private_key":"9e7d9771bc8ee20b1bf0743ba1e8cf1501c54606fcf9e174930cf61de03ba696","name":"node_80d997b87856099d844187d3d1fa40846ded652a62c1611b35919c2543915b64ff62476636488556f678e436ea26fcc40db7c6b6bbcb78f590d61c1ff0e424b5","services":["streamer"],"enable_msg_events":true,"port":63240},"up":true}},{"node":{"config":{"id":"307c36a947f5c4c597075d8346a5db6133fbd2c14da099f85c1d2b7d6d8331f2","private_key":"f28a26c4cb4589d2ffe6731d30ce96e197684de24e886f252b6cda9eb4bca962","name":"node_6bf9fb6a25521c16c2ebeb6203d046d7dd40f29cc2ddb067c0222ef56d7012d2ee5d037f71f6668ea56767610b2a54c144aed74b7f68d06993cbd52fa1dc7630","services":["streamer"],"enable_msg_events":true,"port":63241},"up":true}},{"node":{"config":{"id":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","private_key":"03c65c150995cc6ea4d0a925bbeb304f8b289d695855b69197bd9284c537e8e5","name":"node_23f9bdc82efbf57d3d40a1f2617d0311678fb4a3d6a50cb7e608c649bb618124a76c7ffd17387de963e1cd28d2bd5a61c98613f6e009a8a2a90d989827b35de0","services":["streamer"],"enable_msg_events":true,"port":63242},"up":true}},{"node":{"config":{"id":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","private_key":"0af9f76e6251f7612eb7fde7f20ebf9dc8334ae1139a6e2dd068a97a896860ba","name":"node_f6ca39b3ad803e99f4e4e9cf759fff6abdf06e6972bdbe524f6eb930c669d6f0e9b91f3de9df394b0d661b068273861391bdbac948ad911351c48cf02f874db6","services":["streamer"],"enable_msg_events":true,"port":63243},"up":true}},{"node":{"config":{"id":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","private_key":"1058a6e2e08d811ed1ab6b6eb927323ad28292591a70269330779d52491c6d64","name":"node_385168340139236a0e0f537fa693122db9fa02a377c96f31aae54718e92624fc0288164877e63616651565e055c53f7074ab2fdd18a837da6faf644941305bf0","services":["streamer"],"enable_msg_events":true,"port":63244},"up":true}},{"node":{"config":{"id":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","private_key":"85e1a2673b4ec4f876778b6fb175fae0e36e9e6456c8d8fa2a11bca2dcc8b376","name":"node_9194a3a4c8f3235203e48bdb10902d958d26c342805ea6365af9711213536509719d917c694f9a4fbdde6889a4c92299a5301f0275bda4dbcd2541b5bff5b271","services":["streamer"],"enable_msg_events":true,"port":63245},"up":true}},{"node":{"config":{"id":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","private_key":"54906c0600d9da9afc0e64bb94c7902ff60042840c9143900f2ae8fe9ba69c63","name":"node_c2705ee7238ef9305e4da196653cde707649435a1e8b5b797d164e5ca06184a77af5bd7fa938c995fdfcbce724fa7eca3d908ee34891ab62cc6dfd411c3fa1da","services":["streamer"],"enable_msg_events":true,"port":63246},"up":true}},{"node":{"config":{"id":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","private_key":"b798233dd61ca3bf4bcf5729b06de9bee5447b3c0c4f604eb12e856ff1363492","name":"node_bf4ff51cc3d9a9d8cefd7e346ce1384d06450228a6086fe12fb0c60fec54bb6478f090bb638fbdbf8f41f7bc6420cd8a5c8b1b8566229b6851167375ef412f46","services":["streamer"],"enable_msg_events":true,"port":63247},"up":true}},{"node":{"config":{"id":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","private_key":"529b3a0af71a5e49aeaa2a7ec622d5a1fb3a920fa64f8d200af8f96f0c2d5d21","name":"node_f33d1f3e460574706ac1c7fc9ebb4683b173496ce3e80017d1c3521bca03d5a41a9c7a6cde8347d2238917b7cc58cab000ce7ccf6bb408561015a2ecbe613408","services":["streamer"],"enable_msg_events":true,"port":63248},"up":true}},{"node":{"config":{"id":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","private_key":"d6dd6c5f414a03e6e909bb7447653cbdc2b5adf6bcb2a4643c2cc7572e574a11","name":"node_083e67bb19e30d14e4df6d2483367d6659d9b56744d53d9cf7fd15954c327e1bc88a5d0db8833634e1dad7209164451f8934b59f76b5990059261abfd650519a","services":["streamer"],"enable_msg_events":true,"port":63249},"up":true}},{"node":{"config":{"id":"3388123bad3bf1debf51c5921c09d06f7627d05f6115af39d92dbe9b2f2313dc","private_key":"ad02d38ce01c0291ad949b92338b10b0427a30de149df0a3c7457c2b2340a963","name":"node_0c14dd51a56130c51009e3ce47c96b2afef26b7e86cfca65d8a9bae1abef13b3a7a192b422d894c39e24ab00be412d166a3571cc259ff4462d4ad8925a153766","services":["streamer"],"enable_msg_events":true,"port":63250},"up":true}},{"node":{"config":{"id":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","private_key":"5f33ccc756728e5919332381312dc8eb9f6009e9a407654eb70b995c12338ee7","name":"node_855bdf057f4a6b095e2000ada199447b5b4f2f8d78d2ecbaa0ff1fbf761788870ee999266ad3b26efa02be1a46a4d92ac0debb85a2ceab3442394ee7d99e5355","services":["streamer"],"enable_msg_events":true,"port":63251},"up":true}},{"node":{"config":{"id":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","private_key":"7645086a0c4af854131d0ff4a95ef3a1d8ed9bb6620c934708a2b4f317c6f964","name":"node_b53078000796f1cdb04dfeac424d5e3e37e30f00bd5bebf9a9ea0525c62917612b3a8f508df6bea59e3c7e4cd1aa530841f9a8af54e44f5617a13dee2c6ff007","services":["streamer"],"enable_msg_events":true,"port":63252},"up":true}},{"node":{"config":{"id":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","private_key":"96bc72358e15d649f871e17f6fbb7ddec911aaeb3b5bbd55929512756cee6a67","name":"node_5526d86ba9df651091d88460a9bedb9cedb957d452a635e1db46b9fed78082022adec0047cd7485956a4ecbb0e18107595de0de3c35242527cd302bf74a19260","services":["streamer"],"enable_msg_events":true,"port":63253},"up":true}},{"node":{"config":{"id":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","private_key":"3348eec47933810a23d583715b1a18b2a8332e8cd45bdc57bcc5232c6b5c0164","name":"node_a7378ee34876af17c6cd94a69b6b741ec7891ff5375bc4241d48608d047455ed48e6baae80ee88d4710c69ae615cf195cee26c94b54de1bb935628ce1e3a723a","services":["streamer"],"enable_msg_events":true,"port":63254},"up":true}},{"node":{"config":{"id":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","private_key":"553bb09d30fcd504ead604d830aeb0e3d0a5edfa58c94ec3da1508184f7c059c","name":"node_3e488a7208ce0e097ce47fc6651a13e95cae137d1a054a09b04d2f59d45faa2594a8c3b395e77ad1a3c2edcd827dda73c0c97dbaa6d9519a79184a0aca01ac4b","services":["streamer"],"enable_msg_events":true,"port":63255},"up":true}},{"node":{"config":{"id":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","private_key":"60b600c6cca2712771a98d797a46fbeb7be68a054d94d8eaabb8ecac6d3520a2","name":"node_07f25fdeab2b44b2e92fc80fca7de9e4a8547d27a98a54593b3395c270a9ff3f2a5da62a2e8531ce86359f0cdc5b6513b773dfd64cce1b83e47065d9760a2c7e","services":["streamer"],"enable_msg_events":true,"port":63256},"up":true}},{"node":{"config":{"id":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","private_key":"4bb86dfcc439dc83032e6189165d986a8f315558aa7fe1b6bb2f91e521a837b8","name":"node_0f9f7e2dce4ca266c3ac0ec3a8854200b54487f5308e3db418461aeb5f67c81d332619b6441b468e480941e63100ed1b263e7adc20689dc3dcda578bfca538fa","services":["streamer"],"enable_msg_events":true,"port":63257},"up":true}},{"node":{"config":{"id":"8ea97f6abd40d70717688c5dd38daf6f6b12005df21ae397dfb700fbfe3447ac","private_key":"ff9eae2bb1fe93a164f54d3ac907b4c4ec79fa41eece5a44aeb70c083ad80ce6","name":"node_a1560ce7c1f1d5972f027a8e9cff820611d84764aa067434a75b63887bc4268a259101486d34b7fe83dd735e799789986cd3e9b156c86ede598bd86af4454924","services":["streamer"],"enable_msg_events":true,"port":63258},"up":true}},{"node":{"config":{"id":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","private_key":"f23f43541bab9bd56083d663c140d382c5a9461060c285f1c1b1c6621d8e2e1b","name":"node_e06a789a7f6988e1ba5e8358b6cd4292555c7f9f7f0dda6fc01cc26d24e49a8c1ad694423ad74aec6d80f4b99e2bedc49b6a5f2670744cedee3a3e1848e6dd07","services":["streamer"],"enable_msg_events":true,"port":63259},"up":true}},{"node":{"config":{"id":"0020768d96fca0cf47a9632ca25ff794c996042f07a7242ec7c70fbc75e0e45e","private_key":"e3853d7bcb9111bc645eaaec38093b38a0b16e80774deb42bbf5431e78e9ae9a","name":"node_57b47f692f4d69a5d2c9c193680eb4ccb5a6c574662df0138504a549f3ea74f20c5b866ac4d9e76c4b49c83f5fd692ead928ea20dfcf643a11a1c1e841c0ec42","services":["streamer"],"enable_msg_events":true,"port":63260},"up":true}},{"node":{"config":{"id":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","private_key":"cf8c79a1a70472ac10e1dcd59e8a498cce13cc78f13a28a1b31ef8070e8201af","name":"node_2ddd49fae11d8bd2e7e75a481be267fa846f51cc1160bc326aeea51c542323ff444909de11e2731e835140bc0b1b32020e1e73fa652511a381222bf0aebc4081","services":["streamer"],"enable_msg_events":true,"port":63261},"up":true}},{"node":{"config":{"id":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","private_key":"872f89492c87094f84d4db636ac19e5c87ac57cce008931d2db6d7aca72dc552","name":"node_420b9c90584651c5a3fe3c16aa377444a321dca934f8a097d01a616554067feabebf2fdc8d2a25797f29a8fef30d5e2c82f8e85b20f509a880367b9b0ccdea47","services":["streamer"],"enable_msg_events":true,"port":63262},"up":true}},{"node":{"config":{"id":"07a6873c7eadb651196a76ab30241cc1c984549c5086f1c3bb1be7bd493346be","private_key":"9c7cd9b75a3b821c0ec74e08d8727fa6ce5aa184479038762b1ae4f3c07b3782","name":"node_5bf29c02f07faf7ae8e46389e1b98df4f5e88c9316b5151685e6ae7b4e622cf171ff09f996aaa8ad846643b3bfcbd1ad96cdc249afccce439bb5c55246b95801","services":["streamer"],"enable_msg_events":true,"port":63263},"up":true}},{"node":{"config":{"id":"020f57c369b6521552f7498e444aed21ac57832ed3ffaa7b45f607793ea1a86c","private_key":"ea03297fa8e550df89d0595b3026c5367bd7b0076c7a9fd5c6071f0944373202","name":"node_e3c8a97a2d95dd543f9e2b41d9dce6256e284296f866f4f92e9925f8c7519dea23fd6d1a11153b93c10d6a32295e7b9bcc16024e690e4a0dc84fd25ee0f31b2c","services":["streamer"],"enable_msg_events":true,"port":63264},"up":true}},{"node":{"config":{"id":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","private_key":"dc424f98922ec5d19a5a8d5ee2acaf077778ed0fd92db276f88bda3cc4fdb5a6","name":"node_7aaf7817b93aa6d2f50513791f9ad7629a901ed0dcb151189f44763f83423446b20ec79a48fa260cd9bc2907d5f0ad06b2c4c5e6d812f3d8876c8020ebe41019","services":["streamer"],"enable_msg_events":true,"port":63265},"up":true}},{"node":{"config":{"id":"03059077d3fadf62794756f6947fc32ed00d82162dbe3e6ebcd32952bd421321","private_key":"baad13cad07e0edde7209f5e70451c82d07d1c4d4bd5ad6f85acefd48d14a253","name":"node_062e0ca43a1885c4a7ab2a5169790f9081e3e9a2dfa286b8cc592d5ccbf13d7a82b72d23361a19026d04fcf41b752ea320d98359d770aedfcee626e21c3494b3","services":["streamer"],"enable_msg_events":true,"port":63266},"up":true}},{"node":{"config":{"id":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","private_key":"613f528e83e5bfe55db16791b917891c139ea8ab7599058a204c7836f71feb95","name":"node_f9ffe87c52ff4c22bd9696af32e0a9d34bb81fb84d9d8bf5aafe2ef9696e7b3d6460bf2a93aecb066133f53416bde841a02d6967bd06e3d4a714f3b2c123a947","services":["streamer"],"enable_msg_events":true,"port":63267},"up":true}},{"node":{"config":{"id":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","private_key":"873d0f70715479bbed36c4b74c9391b723fce386d150bbbd613d33eaf08cf257","name":"node_bcf8e831821a5bca359fb824c7dc4d66906de82f3663a0a853d96880a8ca0bdddcceb1b66dd1f13f75ee20fde50fc9de545bc25767f228baee910e0dbfa8cc27","services":["streamer"],"enable_msg_events":true,"port":63268},"up":true}},{"node":{"config":{"id":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","private_key":"48e7cd643d45488597f25de324e8b7553c26e1cf8e8d44261d610ee9041c898d","name":"node_3d2df188704eeac09c8b291bf275a73888199a231c832c51dbc3d746968d4b51a7d5eae0c7c9b8c8e19f5e28d38c375e01abefa343f31dbe221deb837ab8a0df","services":["streamer"],"enable_msg_events":true,"port":63269},"up":true}},{"node":{"config":{"id":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","private_key":"f88a54bfc74bebed6bc8bf6d7b98aad4ea4f8b8aea6cb1514aecf398bee41960","name":"node_8f88c7ec044f758f29bd8d12a5170bd973aec8f6cddbf512b910c4b75a0fec09c5e198bcd65622037f897759db68da2328b4a7ca93585dc9cb62e46d2a6c0864","services":["streamer"],"enable_msg_events":true,"port":63270},"up":true}},{"node":{"config":{"id":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","private_key":"a5b02d5d5ff64f5330758019a5ba0583f2b74b423be4695b4ffd94231ffd58c8","name":"node_4314b0b04331973ed29a58843951f144ac3d09bd5d57b6cd9aaff9b73a7b2ec81e344a3e4ece462bd2081b837a06310e787ef3bb889dabad46fe7efd394c8342","services":["streamer"],"enable_msg_events":true,"port":63271},"up":true}},{"node":{"config":{"id":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","private_key":"9e96b56ea6abe86960b688d477b9c689309a7f304c5e4bd108459144e68448c4","name":"node_3612863ce0456c91785a829bf5c9d2b2fb97dda083a1847e5a4ebaa6fce1260faf4d1eb374c2225431a82774163835fb59c8cb0fa9fc0969eab5410c71109cc2","services":["streamer"],"enable_msg_events":true,"port":63272},"up":true}},{"node":{"config":{"id":"0a3fe6516edff33cde6afc46c6174a8562f17454b6f3ad41be8c12ebf1b0a07d","private_key":"5bec215e5024e8ce65d46fbf9b69e1718c1173478f8654d2ef451b6217ac6db3","name":"node_2ac522ebfa49d37f86c6270ad4cdad0e766493f0e463b18c9906c70fbd2e5c6ff6b0d5879de6e3894418370636963a78b0c0cf087a8684c2ca30c9c736e49228","services":["streamer"],"enable_msg_events":true,"port":63273},"up":true}},{"node":{"config":{"id":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","private_key":"00dd6637412fc5209eeee1423b4eae49fbba97d8d5c37c9ae6819f7afc53569e","name":"node_62666b24d1026345349edb6a878cdc27d6fee8254e83f79a298ce2805260d97aa46423deae440f86ced5cd99b30e970c8e1ea1046752b17d8c80dc68d9a49b60","services":["streamer"],"enable_msg_events":true,"port":63274},"up":true}},{"node":{"config":{"id":"e1909ff70b17f666d74c73e8d9803df1e6e20733e4aeecdc78e444b6632d92c9","private_key":"88ceb50dc965b4e3279c8e7729db14ea34238126f1ab694c621d5ab9183acb52","name":"node_9ce6b9decb69d66075993e903a0727ec109a04335634616097c0e60b08f94f764634fa00891b1070f6ea1ce896bc7d11d3aea92a00f281f902d960894cef296f","services":["streamer"],"enable_msg_events":true,"port":63275},"up":true}},{"node":{"config":{"id":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","private_key":"638176a3cb343c140131c887892f37f9ad69e9c87c0a256dbbf2fbf8dd2a6b08","name":"node_b870935867183bbfb84add16b9f56c02c834b7042fc24ce6bcb2a7f174e6d2239357a00be4272c1ef05c7f9baf5a9f16b53bc18328a9b7aca8540c2916805b57","services":["streamer"],"enable_msg_events":true,"port":63276},"up":true}},{"node":{"config":{"id":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","private_key":"4da9834c8665dcfed17ee5eb89c5bfa3c0d1cc4340088a4038f746dae4cc23e6","name":"node_c83985973a9564ade7d90aa54911ac57077600676a1d27292d98e058bf1315887bad84ccd708cb7f52a695a1405fb7d491d7ca2ae9caa3ff58a5ad78ce4dc9d9","services":["streamer"],"enable_msg_events":true,"port":63277},"up":true}},{"node":{"config":{"id":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","private_key":"05eb5751d81653e8ce409be43c9da42ff90f90c6e68981e69cd03a867d93b800","name":"node_acb4088c27ee2ee130ece3506596432afdb9e9757ec11ebdd35c5a6e87239545c48466913a646980104c8fd7a9a335e177f04efffe9dc952dfd7cd5c1bb9aac6","services":["streamer"],"enable_msg_events":true,"port":63278},"up":true}},{"node":{"config":{"id":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","private_key":"846035be661387d6163b5472ea225ecc9d3851349f480fb901d2e31bb8ffe858","name":"node_b4fd11306b1416a9bcf4719578976188e104b8c5028b9abd3214d257446b97b70a22e6c00934d80caddde15e8e4d16af03bb399583047983fba373d8313226b7","services":["streamer"],"enable_msg_events":true,"port":63279},"up":true}},{"node":{"config":{"id":"3ef6bb1dcd37360fe4a463b70add6cd4ba112a293112911b2304ab05b4c1b543","private_key":"b34c69cee56a834b8a36ae964f1847d1825821a21d104396039175a8dce5ac73","name":"node_7b35c856916eb3fc1b1f451667e240b95aff8f4731cc0f6743740923081ff1ddf2eec81bdccf4691a7bcc053d23eacfc15809c384a73448f618317bf605bd1cd","services":["streamer"],"enable_msg_events":true,"port":63280},"up":true}},{"node":{"config":{"id":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","private_key":"3d1ea95ac16f7d903cb8630a8c06052560a974b0abd3410d9f8b2968e7cf653d","name":"node_2ab9513899c3c829b6284e56bf2b9862dd0156ec052b58615dba77d450dac642e534ec36ae5d3234262aff54e19f99856e908b9d8c1da660a7834a445610715b","services":["streamer"],"enable_msg_events":true,"port":63281},"up":true}},{"node":{"config":{"id":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","private_key":"70a577ae497edece947ef065da29014e45dcc1d942969597600a64b55e8bf718","name":"node_e087230634f556b818a416b66ba3f1d1dbe3e0dd0698bbb26fa86a2499acd169466f564dc18adf2f9be4d1ada0e86c11549754097601e10aff4f3a57ddffa24a","services":["streamer"],"enable_msg_events":true,"port":63282},"up":true}},{"node":{"config":{"id":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","private_key":"28ee397105a113c5c4e7d7cb1b3dd17b6db66c3cd8f3b621df0271e544fb446c","name":"node_66beee3968dbcf3addc6feaf070a4e3e82b5bb164fb72f40add89f732c12e8d0a31a7ee1f1e24a4674e4dfc9a4912b02c17ce3c224d257d78b7156544d6507dc","services":["streamer"],"enable_msg_events":true,"port":63283},"up":true}},{"node":{"config":{"id":"67ee67b8f4d3ee50d8cdd027ba96e4b2f8ef9017bac217bd5a2d9dca35325519","private_key":"b6feae1f667895ada3116380a2cabb0c702b44b24d45415edd143d27dfe5643d","name":"node_a08e47137bf611dfbd20308a22bdd3ca00121386f121d6d712ade86e9a1601a4237766887cd91c8516e20b6e323d2ef49c1c7e177a0afb4c813f0f344daf779b","services":["streamer"],"enable_msg_events":true,"port":63284},"up":true}},{"node":{"config":{"id":"7aef1625409f479ee1abad565f8d7e73b17714e77f54d1ac1b2fe80382a058cc","private_key":"a643e845285aa039d6f84cb124b618bac45c970fe0b15fdbbbdef834300b4857","name":"node_990b1db3ab1390219e8866d39fa4441bb5bb89ad786b3e255771869fcd08906dcf63f34007481769c7462edbcc665acc0a552e3733360b8d8c310c9321a5550f","services":["streamer"],"enable_msg_events":true,"port":63285},"up":true}},{"node":{"config":{"id":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","private_key":"1b31bc34d4ea49b0c1df457ad06513d0f47c56ab106b0f0ac97c43ccdc64db31","name":"node_7865b82e44add2cea6ebcbaba9c152184331a2940fe73f6f9bb940bda8a59ab4cf4760894b9cadcaa54e36fbb712ad619346a49a85e32cdf2b53cda1ac1e0658","services":["streamer"],"enable_msg_events":true,"port":63286},"up":true}},{"node":{"config":{"id":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","private_key":"b380737ac46f320eb5884ca8a60e1693caba904545b68396e33fb92cd19aa18e","name":"node_292c78c8e41606e98428096bc6f4609a7aa48e5c4d17eecc64d65faae4a7270b0c15f0cd74274015688ea0a9aff34f215c29118222e362a29c8d637963973a55","services":["streamer"],"enable_msg_events":true,"port":63287},"up":true}},{"node":{"config":{"id":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","private_key":"d18c1536cd7d9771bc9b64595184285ed499519ef810b3f013fca2fc01018048","name":"node_ee36d1d5dd69fd30242d7da2e941b34ade5581c7164fcc43288ecfc52937a98f1b31ade5d1e4dbf611cbe1c7875b2840c89435081cde471c271d7bc60e9ef211","services":["streamer"],"enable_msg_events":true,"port":63288},"up":true}},{"node":{"config":{"id":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","private_key":"186743aa8dc07042c40bb95b2efe30149139f3c2ea4a4bd193d3be68e8f4485a","name":"node_bd1a7c63fca9e1fcc67d7fd62670beefe2bce483cf1dc1233ee85eb90db607917cd9a5d7170b10e80b925a20e2af2c54d286b01805ac34967410859f4f03da09","services":["streamer"],"enable_msg_events":true,"port":63289},"up":true}},{"node":{"config":{"id":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","private_key":"2e960819700f3330b00d2d58461403843026ffc7629b2514568c6b571fc99488","name":"node_9db1f94d74a00d1e5e2a391f1bcddf16b6d0670a2c6d91d946d38112c58634444b6fea8e8c05ab4b73bacdd62a0b575f6fb8729978edc216c7842b177b9f9cd9","services":["streamer"],"enable_msg_events":true,"port":63290},"up":true}},{"node":{"config":{"id":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","private_key":"50c27493a6d9ddee175040dafa1f40c6ad503b55590479001ec085212c5cc08b","name":"node_934a5fe1b91c1891ef4df7f67c027e0eb115d4c2ca4a1b76a135ebd4315c4fb835d6625223194e6a134407b38b0d3928fbc7bc924a3c6a538989a6cf200bf4bd","services":["streamer"],"enable_msg_events":true,"port":63291},"up":true}},{"node":{"config":{"id":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","private_key":"4c67752b215e2cfc06120964f06976fce2a1dba599c0f32c0f4393b9d77ef872","name":"node_485825745d97654cbb92a14e0cd1078691d3c28f4d82b1046284b676a42b44ca58eb43474e45e415d3bd3f3c383f9a1a3365821ed7ba6a9f2603b29d58031dc7","services":["streamer"],"enable_msg_events":true,"port":63292},"up":true}},{"node":{"config":{"id":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","private_key":"21acc46816414d3a1d76bca30d2c2e1e55d420e9ce704d0720696f7f7fa0bf33","name":"node_5bae82d4832092c17bd11474f7748015b8b69b896ab8f683a7c0024fc98740cecbb9e31c6275269f9000e5bfe51d7f45cd5dcd4433e0c87b5741685b40fe3983","services":["streamer"],"enable_msg_events":true,"port":63293},"up":true}},{"node":{"config":{"id":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","private_key":"4542e37f43858e396004d8d8801bae1905d2496a3ab44f75cc29bd2c2a472f94","name":"node_732393b99c490c76fa45e3e404ee095f2fa445fcc123af7ac8ab1d2fdd7cf2483f5b82bb818bd8a82b1a3b73e63bcc2c83d8f24d9aad9afe36503735e7967be7","services":["streamer"],"enable_msg_events":true,"port":63294},"up":true}},{"node":{"config":{"id":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","private_key":"a8e588af7acfd6024b3d71be7be630345ca80708a69214fd31e3b4485f039c90","name":"node_4de378164d77a28c90cf162dd1b465e0282ee17aa7d3bb3179252466f72da33a36c9f298f7c9498e38dba4018e7ce3e5fe631c83842cd9059e23a7fece366d87","services":["streamer"],"enable_msg_events":true,"port":63295},"up":true}},{"node":{"config":{"id":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","private_key":"e3d1a9d4621d7f77d9c2fa224f0d8c8211c2bf461554a1d50afa8f34a82a4754","name":"node_155e4879643a472420012e040a87dbff776fc1976814c0c6cd0d5e157f0d0baac04aa7f2535786c09acc2700547383d2119b64a6f62115f1aff1708738844d8c","services":["streamer"],"enable_msg_events":true,"port":63296},"up":true}},{"node":{"config":{"id":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","private_key":"6522d077629e8c313d832a7a87e5dce9e989618cd1eff1b1eb3c8b63756159e5","name":"node_81288bb0584b7ffc1546043e052e1df10fd5371b35dedd0bb42ec5b30ce69b741b2f7857af5984d4754b39d7253575fde9298504999b78380ef602d391c4bb1d","services":["streamer"],"enable_msg_events":true,"port":63297},"up":true}},{"node":{"config":{"id":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","private_key":"f90016392c8768d222055de5cd1c3a512d3c46b7c7be34a93cbd016aea9ce753","name":"node_109556abac422a912ecffec0d8796f8f16e92e8f95cecf72dc89e97a8bf1fd37aad3c66aa3cf134acade6d065c31821f977171d5c5463305b57ce3eee99a7ab4","services":["streamer"],"enable_msg_events":true,"port":63298},"up":true}},{"node":{"config":{"id":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","private_key":"49f3b1f88009b94bc7beef8d891816672e9075f3015f5954a9ae9d13c8c07290","name":"node_759f46661056bcbb30c7706c9fb87e22a30feb523bd53ae5d18db043efa45b49a2b8982abebdd598fe0912a05896390ec2eabe5e160cbafc0771990708ad8c28","services":["streamer"],"enable_msg_events":true,"port":63299},"up":true}},{"node":{"config":{"id":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","private_key":"65f5fa03f2989a02e37bb664102504038f14adb483b2116b07355296346912f7","name":"node_baa5924e1547c177011285d292aa161a15e5ff282da2cd0afb83c9caffb07bdbb3662c227fa7b26e6dc8be9b8863e647eb5f8ce81640532f8f5c9a7f36557e63","services":["streamer"],"enable_msg_events":true,"port":63300},"up":true}},{"node":{"config":{"id":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","private_key":"87514536801d0d46fcca5c10334c9a6a34ad982fbbcf65b2f881e99a0c8ab27e","name":"node_deb7af800cd20a647335f0feff5bfb3e182f99f4c2d1ade10da36ecfdbac3b5fba4e8f0a4595443fe395b44af3a95617669850944655def85d57cc81c5682a3b","services":["streamer"],"enable_msg_events":true,"port":63301},"up":true}},{"node":{"config":{"id":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","private_key":"c69b71039aa3f28c158e5e639d35069ee66cc88565ae886e75651d88fae8aacf","name":"node_17917fa4da06068b587c17899569dcc768b9cd2c27117a4ed67aab26a7e70cf8d32b881db258061ffd89a332a3b29bfd963d8e171a06c3e208ba0de1c42a2b39","services":["streamer"],"enable_msg_events":true,"port":63302},"up":true}},{"node":{"config":{"id":"1a8ecbde88be22a9bbb5f3ef19be5377923d655fd9cd838f9d08a442614742c3","private_key":"76998bb1133735934b6d8cbebf029318feaf9b103165cd460a4a9ae04ad26c5d","name":"node_032b9bf2f59f15571821611d3ee541aa748c86d47fbe5f049fd0069be53e5d7c41991d82957e9c727654c3aa73ec94997c03d8a6c1d2007c9bb1a703f6215158","services":["streamer"],"enable_msg_events":true,"port":63303},"up":true}},{"node":{"config":{"id":"05a4778e1be7da949c1a7bffa3408020b66b4ee3c98e1f62e35d212c77e83ada","private_key":"424a19356346001136b4df8a6119a510d526b508d0378eb5b67ffbc471757c08","name":"node_be026dfb24847cac2d00f483104edad06ca8ecb9e1229c479ef5650c7b427cd5dc34a1cf06172c17c9d0406ce17009712e82446394743d4cd9bbc41c537ebc14","services":["streamer"],"enable_msg_events":true,"port":63304},"up":true}},{"node":{"config":{"id":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","private_key":"06aade0419624ec355a3e8e47144b1efd1853293246628e0d3ce856bf3bb64be","name":"node_389b5c1a805f87427e6d647ec5488342a3f7d700615943d5ad98ca5c111f33c7ee007970b4d3e475892095c0d5a79b7ed52d12598398818159000d4b9b719126","services":["streamer"],"enable_msg_events":true,"port":63305},"up":true}},{"node":{"config":{"id":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","private_key":"903c726d393f3a34e861aee6a1b42ea078da78c23d1dcdbd0fd8c30e80b65bf1","name":"node_c57ea182bdb42ba9b0870b5c870e467f52693e5c0df752fb6eb0dfbacce7763f73caee10db40f4fd90f19f504ee586eaf9da41f5ee93ee515d885fec99374116","services":["streamer"],"enable_msg_events":true,"port":63306},"up":true}},{"node":{"config":{"id":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","private_key":"29f2f00196b61b834638899c2e440c57b99668fbd93d7eed220c4556a76cf89e","name":"node_2ac93c31937ca205d61cc5c2a1b9037871cac55e9b306b999b28e19566b3316f6539455e014840ea50c2db8a7c97d78804d3a2dcd7cd9c1f32ace0d36e5dd339","services":["streamer"],"enable_msg_events":true,"port":63307},"up":true}},{"node":{"config":{"id":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","private_key":"682a2f9130cdf75915282000edc627563ea4e9de499ddc8d55efad472a94091b","name":"node_ca1aff0d847c6fdacb623ad9078a0885b3208614fea20d5ff11779779b486bf5e9c794aacf848d3c2d58cfcc1eda8356df82f5fbdd0a5236beffe8e6042848c4","services":["streamer"],"enable_msg_events":true,"port":63308},"up":true}},{"node":{"config":{"id":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","private_key":"d1b4b3adc1b17a79626152a1c7362bddd7c47886990759955595a1de27f7b70b","name":"node_2e409baefbd821485a151993356730e5989810d581e68190fbaa142799c9335bcf98310ad188fed10ae6eb6e2e789730393e509962ec5c0fef0342d72ed088d9","services":["streamer"],"enable_msg_events":true,"port":63309},"up":true}},{"node":{"config":{"id":"d60644fb69edf1d360a5b3d480030df97600a3d94a189da98b58c43758c98331","private_key":"34a5057b2a2c507010da7a091827d28ddba4f59c9b5f15f7cb7bf0578e4141c3","name":"node_7b5c0e81c7f4e3ae29d745f6028cfa10b29a16a9c261b65fb8dd05d6eccc634612ad5d3029c8e9f39863a2cdb2682c25f090139bf5c4950e25647e39dcb40e63","services":["streamer"],"enable_msg_events":true,"port":63310},"up":true}},{"node":{"config":{"id":"69d8801f9f19fec51b636cc27fca2c93e67dac8f3650aea35259d01293d5c9e6","private_key":"7b608c298477c4687c50adba466567e19c08492da2684417c129d730caef7594","name":"node_663535b22018a4ef0b151572ef286fa66c22c6e9b2913a69c4a735d4b0149bdf6c84bd83d710f8aa2627e8993b7f020750fbc6f56a3365ce4c840e2b3de1a6c1","services":["streamer"],"enable_msg_events":true,"port":63311},"up":true}},{"node":{"config":{"id":"96419a246f5ca44f2796d91ccc55b3ac477e1754e74d202cdd133e6b5b96051b","private_key":"d9d17f1baee1ccba3fea764f5f5797475ebb9f206d735105ba8fa2c32fe02f0d","name":"node_7b1e20217f594f87350542df31ff5ae8a635679bac7962d49c97e02190c644b673d03e7eba2626b45ca7ebf98d8a972dadbba02c18a8d2cbfc3e7a4cced5b4b7","services":["streamer"],"enable_msg_events":true,"port":63312},"up":true}},{"node":{"config":{"id":"e2554d1bf145ec0e7027ca8e8ba0c85fc697bc4b54e36a6cdc53f1efa37a6634","private_key":"cea9931b34475ad432aecae3d385d895f0ab3e93cdf1d9fdbb356753ec9c7a59","name":"node_84abcbc7b5598ecfd91ceace0927921a5a3404fdb274fc2a2549abbede5f8e4f62c15688a4dc9a2d80262c45970c88898da245c6eb779f327a81d53a17bbc74c","services":["streamer"],"enable_msg_events":true,"port":63313},"up":true}},{"node":{"config":{"id":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","private_key":"362c6622af8998cc3c5cf88ad71f308b632f8f7d0e8ea7ae341f8c8ad44cda5e","name":"node_eab86c521f8f1ac67c015ab713f1236a08b0200757a96bf87ab8301e7967ae62361f0e14da14ca294fc8cb3b4726b52f4773101e21b032977c6a647241b59c73","services":["streamer"],"enable_msg_events":true,"port":63314},"up":true}},{"node":{"config":{"id":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","private_key":"9b28fbe1dfa137bce975e179d3acfe046e5d148e1f9b1aeb16bdbc2cf7abd071","name":"node_e6779e3bbecf81dde8b27d652ed2726c45c205db0d0405c4580014a4c91d739830276f77079108c98ac516ffa00194e77a0795f7140bdf36069b6475e58213f9","services":["streamer"],"enable_msg_events":true,"port":63315},"up":true}},{"node":{"config":{"id":"0c728c174a25e11bc171d7d35eaaa3974859a4dbf7a64e1d9c9da6b3ad63b21a","private_key":"85e559f97ba5553b0773479fed666bd92a928924af8164f41e92e8acfd51e896","name":"node_9133b0b3c8f680286eae90c3beebc3099a13de38775a3ff2abdeced62d05eb2ced5ac85eac0870156be03101024da5e1d9fbf3e1066ddb3f088221e64149acec","services":["streamer"],"enable_msg_events":true,"port":63316},"up":true}},{"node":{"config":{"id":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","private_key":"652705ca7c47c641c033e01212d96759539f317508df53dcf7397dcee0e20422","name":"node_e1ecad3beeb6c19dce551e4a603c7b440b4498c44e298dc67bfaf4ed2c75bbea95df48b1cd1318e2ccb962cbae7c30ff258e73f8c3fc6b7e9ae7a47f7cf8e53c","services":["streamer"],"enable_msg_events":true,"port":63317},"up":true}},{"node":{"config":{"id":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","private_key":"2578458d6da6ee18aceaf48f57a0f211f9b0a0a49149cc71356d1ccb989b55d8","name":"node_1d0244a13affe60996fe3cf51ba624adc746de0c7ea68ec5a31311a36077c5b5495e3d95c9642875db44c7ecae6ffed22e45f2e2d024d486760be2af9f4cf2cd","services":["streamer"],"enable_msg_events":true,"port":63318},"up":true}},{"node":{"config":{"id":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","private_key":"35b91179894d9195f220f768d849bcef7b2752c1d63a4d373be189c4e35b68a4","name":"node_d9bbcdfd949010e6b2cbf6d0f34b27593ae410bdc1838876aeca35679571fe431d040a5ab085c0a77f111688bebed891da3587277cd794702437cb20d6aa4256","services":["streamer"],"enable_msg_events":true,"port":63319},"up":true}},{"node":{"config":{"id":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","private_key":"62fbe89ba6bc5d8f608f65baf5a0a87c9cffd1f428ecacaadcc777a117a88fa2","name":"node_0a4eec7b0e05da799aca5b36d9e2530f2a89d1426b1fcb7d8f9a662df8a8b5b763767a20db3c9c63e0bd13c2a6e16d3ba8f03baf84f5e5d24917b3f211d9bcfc","services":["streamer"],"enable_msg_events":true,"port":63320},"up":true}},{"node":{"config":{"id":"f2f5e82cf81f07f8cf82c7171c5ee5a42b7963619fafd4295137357cea1b7bb0","private_key":"85b477146d09b052deacbb0f7cc22f61fe3093095e7677ce89c086db68c8c939","name":"node_9bbbfac8be4a000f51a4ce8d024d2595b5d491afec0138922dddfde5ef62c664040c2e9304cc6b7bc688233b5d0c4644b8c8793227c078285a3ab894ef0497a3","services":["streamer"],"enable_msg_events":true,"port":63321},"up":true}},{"node":{"config":{"id":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","private_key":"b3caf8e1271504098be33349fd983395e57f6cc7f4c0d74df8dec7643c0cd09b","name":"node_016f8f4619a16a07fbde5a18e362158d696e4d9f104a77787f26e32a25cd7d278256f8e0db865c488a99511ec27a5eddd5ebbd1c1397fa7f8db111062e5fe959","services":["streamer"],"enable_msg_events":true,"port":63322},"up":true}},{"node":{"config":{"id":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","private_key":"a61a6a7469d5c9abf20daf3df75109b9139cf36c1194d9bc00046d1359c44823","name":"node_7acddc7fb6ac587b7e343804c768e4291cd28dd2b2b9207dea6ee82360d848a52d84f94e4c49e9c421f4f90e2d24f6291e2358783ab0230e6ecde82e69b4ac52","services":["streamer"],"enable_msg_events":true,"port":63323},"up":true}},{"node":{"config":{"id":"1953df7e29813b00b0137118623b6ce34113d17d4f9aaa608b81f2ee58ea3fe3","private_key":"629bf1b9c1de816ee9d1abd91d2cef359ce96216dc1d798c7f246ecc5bba8ed4","name":"node_9b59b76dc61223c7b37083b0ebdda22d3599563b3e3986f5db9d5b12f8c806cbdeb2345454efe957cbfa11eea9c7896aefd9a461210524805452ca100a9ea431","services":["streamer"],"enable_msg_events":true,"port":63324},"up":true}},{"node":{"config":{"id":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","private_key":"880c61b8c84f87c55e85aa9b07944500eed52b76b64b6d15c93d8e7c23000042","name":"node_e2e0419a41bdfc5bd76deae2ddce6ac865f886d764c9bd18799b79fa3b2b6ba66d765d26da2037e98317e1d7e8232a61e63c1d0cfa98c9137424151b46e07171","services":["streamer"],"enable_msg_events":true,"port":63325},"up":true}},{"node":{"config":{"id":"1254b1e5c9e80b367520bfefc35cf32a60aff56c48315107c4b07aa1a7866af9","private_key":"dc74709b0972c1ec957a2c056059ce117c9291997ea47107228e80d28af70acd","name":"node_5df58ba12334a1b4f983d642fe435901261767cd81f8f37c43e359f5584664e4d287e8c2275995604a3b3a4823418e35cd899a3db2a7db31570e19512f375481","services":["streamer"],"enable_msg_events":true,"port":63326},"up":true}},{"node":{"config":{"id":"1a85e13e2ad82c8ffa10ad52b382947e638036120188ae611fb1fb333cb5232b","private_key":"52e14edd67adc3154d0d5a91f91e042773a79af41c5cce9120e2dc03318a7fdd","name":"node_5e86f39cf8338ba098a85d41e601a303fde311662b216ad32a2b7bfaf2b2b6dd1af3775784b39f2d85ac5fac96f05f3abdd8a2bd28ba16d939725fd6a33ae1b6","services":["streamer"],"enable_msg_events":true,"port":63327},"up":true}},{"node":{"config":{"id":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","private_key":"ba201a301c109cac7dc4f762066312478f6a3b92b80495c19d238b8e728e3f63","name":"node_df54af3bae257d437c5657905484e0d72e9560c46f8ba6b3437730234e05259b404cca40b0831e911605ff8a68a19e8fd7e958069ec8b40595928892f4fa220c","services":["streamer"],"enable_msg_events":true,"port":63328},"up":true}},{"node":{"config":{"id":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","private_key":"4f8dbb09790674afc8ae90210897ff3196d82a7d8cacd67ab970ab02fd61136e","name":"node_4ee37acec2178ef120166265aaa2b64b09b315fb53861405ef46f2503c273eb9692e9cb4c2e67a8652254d430fd6e02dd3fe4cded20bb231a9fb834d292b0b24","services":["streamer"],"enable_msg_events":true,"port":63329},"up":true}},{"node":{"config":{"id":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","private_key":"fd9060fd74b7db28d5b02541febcaab4e1706cb1ae50b22d20a7f619fbc774cb","name":"node_abd8f5a4d700c06ec00684b23d2d25c4df1a9554fcfc57b0ddc3fa476e6a699ba75e1213449f900803f83ab8194c6746664b547202047f35b8b3e216c7147bf8","services":["streamer"],"enable_msg_events":true,"port":63330},"up":true}},{"node":{"config":{"id":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","private_key":"3034bb94560e7e8d146cf275664f91426fbeadbea78cc96662a0e762f5491d3c","name":"node_cb321c9b0fe2eb3617b8e2461aab170a9dad2c9b04a10eead4e37e551197ea0a7500eb1cb7b155efe1886486ef3d252d8ff0d309f2906a1126ca011d037da70a","services":["streamer"],"enable_msg_events":true,"port":63331},"up":true}},{"node":{"config":{"id":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","private_key":"1e3e9ddcbac955dd113066c5a41e5b3b16da5cd7bfa036ee1066ff3ceb69d7c9","name":"node_c665a6b1b7ad39b86bd4b1fe38b7ee95a0fbb1db15e91381ec9e5a235e33b69ffdc92c60dafd74b7b4dafca1b000e9274bf045abe5b93b1a9c2b31ae071a9898","services":["streamer"],"enable_msg_events":true,"port":63332},"up":true}},{"node":{"config":{"id":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","private_key":"9dcf01557c9cd252eba6d06bab3e12ad039e64f5d33b732bae4afdee4f4c63af","name":"node_53df9cce9b0169e58ced53a123d1d8f6e8fdcfb4c2d781ecbb91bb7c8eba36946dd9d21c749259769825d8a6b3faa6bb87c8c6085a522f8a3e376061f59144d9","services":["streamer"],"enable_msg_events":true,"port":63333},"up":true}},{"node":{"config":{"id":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","private_key":"290d07cdf29c35f369b7053b54ad174e4db589026d3d4b0553bcccc0a0e4145d","name":"node_d7a319f69013b8375e64d19f015547462c84565b882337998f9e669c5fadd3d2ecf6374109903003e09f5d453424b5c7fdf52163d2cc8084cf81f94844946b63","services":["streamer"],"enable_msg_events":true,"port":63334},"up":true}},{"node":{"config":{"id":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","private_key":"2a549958a13f2e346b477edf55e1056a1c7c1051b3cb277aba17bd7db522aba6","name":"node_f38a9d792173a29c61389f5d84c005d78eac6e750659aad42e2d6df883f04eb3126b2c38fac95076eeed455b2031aaf694143922aa93a2d50412a2454dc56e28","services":["streamer"],"enable_msg_events":true,"port":63335},"up":true}},{"node":{"config":{"id":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","private_key":"72cba007b6bddfa0e57451e0f28d74cd7936955babeefb16b4c3de62400c8ef2","name":"node_4f5ab12b9c5127c16739ad0d4f51adce90ba6736cd096eb185ea632789f4b26f1ebabdea4fd3b39dda5409ece6c2d92fd469cf4aecf9d29453e3e042c19b893b","services":["streamer"],"enable_msg_events":true,"port":63336},"up":true}},{"node":{"config":{"id":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","private_key":"5a603b46da0d4b5d7b48ca9ee9f08ca4cde411e8e279454ac25624f0a81580d5","name":"node_11d560045536e9d1b0b1b9e8944e00f0ca7f1e6788c9fdb51061820bbb3008ead92bb7836621bf58da7035c87a28328faecb4cb2e3cdb9d7fa4f45790e6bf352","services":["streamer"],"enable_msg_events":true,"port":63337},"up":true}},{"node":{"config":{"id":"57657e7b7d2642c6e5daefd27000d0da41982491486fd1aee57d0f99f072a471","private_key":"955c0d00b5c9a641e3e9958a032163ac01fd30e25660ec50db255cf9349573cf","name":"node_94088fe273f76e62ca4e43c095ad12215b4ae4c59942328b3985416fb19bbda0cacc143a49eff3fb644ad53a19d3f043260e43dbb70a0aa9c15829c21cecd3c7","services":["streamer"],"enable_msg_events":true,"port":63338},"up":true}},{"node":{"config":{"id":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","private_key":"3a0fa647e1aa93088fd931cceb89e4426c10861ebbddde0f14a58e2a3969c252","name":"node_535f93057d46c4aae0902546550880be0b5ba67dca12350117d471d67f4f2b3f7bdff29bdc918767974c465df8fa1afa6bd6c29c0a36d51f71c4183673d906f3","services":["streamer"],"enable_msg_events":true,"port":63339},"up":true}},{"node":{"config":{"id":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","private_key":"22c2b405d07e440cdef6e1e9d93738f3bf3add71f54493b68b2fa368c3187755","name":"node_7ac5b07eb555af3bb4ca3f0f4b45204d43cc3798c244435909b97d9a32fc3abf0b50666e260e046bce826a7a102a92d389f8f39c5311e83bb34beddac29d50e3","services":["streamer"],"enable_msg_events":true,"port":63340},"up":true}},{"node":{"config":{"id":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","private_key":"98f9b63dc3d1c346c60957731df1731c8b5f7cbfa63ec1e4de06d1b9f70f7597","name":"node_1944f98d4db32c56a5efda5a79aa5f276bfab1e084d6611fec0e735b8bc0f45b457cdb74a23fc83b2377821fa8316123c9877acd6db7c41058ca5d3cfedaecf9","services":["streamer"],"enable_msg_events":true,"port":63341},"up":true}},{"node":{"config":{"id":"41b2b713dba3a11683ad83d1cb4beb9ff4f5aaf49f2ad1ac2247c84a85d40a56","private_key":"fef764b306a01b333cb965c1faf6970b6bf206dd19c10f666d0188e532d27a7c","name":"node_1c2f7ce55a6e38487f3b3fe6083a79f914cd6d7e4aee8f5d56b4a61b3eb584e128c56998db0e13e775edee3adacf54f09960a041dab3daa80a4f8a3b42760c1c","services":["streamer"],"enable_msg_events":true,"port":63342},"up":true}},{"node":{"config":{"id":"574101500f64e9a1bfbf31f4f379a60a0ea9bee449af1cd29962214dd960b3c6","private_key":"aa54e42fb6be9a738ce973816a814b29759c40f5ae5c57ef972c8957be9d1cdf","name":"node_6ab6ec15635b2fda22542c99df0a3313ad4583ba0fb3f31b8f314e3ecad446d3fb0fdfcd2be9b08374b1a7e049fb68e69e8dacdd13f1df5007a656f6b11adc5b","services":["streamer"],"enable_msg_events":true,"port":63343},"up":true}},{"node":{"config":{"id":"d0443f24523af0eec6c20b21d84b2d54cb0ba8d4d1a205b41328bb22e8d3cd63","private_key":"c0a44ebed32208583420c7444e83ac3526001946e4f7f6b561c24d98f96cc0a6","name":"node_74d84cfdaf32cd4cc67771f29878f1365120bd65cc14848008d5386a81f495bf88eb4132fa38a2cc109ae1e7f02c9b97597ab9d1a9c19c945e710ab9df07cdcd","services":["streamer"],"enable_msg_events":true,"port":63344},"up":true}},{"node":{"config":{"id":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","private_key":"d38eae2be9d262c3b37bbd8afb82bc0cd431e4c484ae92747ad064e4138e9f33","name":"node_6b4e376e9c94242b6d42688c22aa9ab418cb6dfb1cbca1c69de0feceec8d3af29fb005017aaa6e503cc0c03cf0dc5bcab55e01cd6f64b9efa02ed87c03020003","services":["streamer"],"enable_msg_events":true,"port":63345},"up":true}},{"node":{"config":{"id":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","private_key":"e345c696901db291ce988a19c0ad31ca0a38628ebc03c0bc87307d2c8f031346","name":"node_92d6ef729992f0de886076e5f8cdc42cbbd70b29e79c25e0b3d22b6575d3d2508d47b42f581e335455483d43625bce4e55f88cab9ff2073ee8dab746357864fd","services":["streamer"],"enable_msg_events":true,"port":63346},"up":true}},{"node":{"config":{"id":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","private_key":"7149ecac9f7173c182b233a94bfcf854714955592618e1d9500c622a08fc3faf","name":"node_57cd4c0048b459192ee46e19ec7df777e600f256e0dc8c3019431eb7bec3c020b9504ec7aea3dba8188658ca5b830ec40af6a8540ce10e04badc6c695d009271","services":["streamer"],"enable_msg_events":true,"port":63347},"up":true}},{"node":{"config":{"id":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","private_key":"cf6da8ba09884f084c279f1f25340712041f3c36c9b6be1c0fec270bdf17a875","name":"node_831cde5b651ba327b059da4c7f84b184bdd6bb99c6690ae816affe25d42c6c3dbe5b59e2b6bf23e9d0f6c485bd341ea57ba1a8220d18b0a40e156b77d84f79ef","services":["streamer"],"enable_msg_events":true,"port":63348},"up":true}},{"node":{"config":{"id":"c95a481b19583fc85e9b079fa0b5cf51eb46e71b9f57fc6df2e3b78389d8de2f","private_key":"2acdb11a79e469d5e7e20a391253ad6909ecab541c9931d5f7422de792574b6b","name":"node_71f89efcf939294bb94ac669c8afd00def58c8dfad519c226adfa068692d588ef797116d42aa146b9d34f3be35ec7c014022640ab3c0f1f9034e44ab1a26e246","services":["streamer"],"enable_msg_events":true,"port":63349},"up":true}},{"node":{"config":{"id":"91088ea79cd8f27da8879760dd21c363aa9be1135437c2b87464426874b597e5","private_key":"63cfea5298455b0cf7952a47db686e5c91ca2f2e16610167e5c15337e67feeab","name":"node_4ff66e9b5d3fd345990bd7605bdc222b78946f0c13b72bf80352812b2426d8f1d36c93e6c8713566004615e8a78ecab12ab0cfba254c0967eeecf8875b25bdae","services":["streamer"],"enable_msg_events":true,"port":63350},"up":true}},{"node":{"config":{"id":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","private_key":"28315ffa74cadce008f22997503621aee7c6df8dd478536421715a17ebb97a74","name":"node_9bd4f0adb9a966cc5ba632f1b540fe9cd6c8e39fe1a041b315697679cfb04b735952062ce057d456677cc34a84301f26bb727a4ab2646fee734f80a22c620746","services":["streamer"],"enable_msg_events":true,"port":63351},"up":true}},{"node":{"config":{"id":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","private_key":"1177b8e7b7ed31f6a54030f8b4341593d3a1d72aae40f9156861650da1746f2e","name":"node_58cfb20d3da3641e0ad529b9edbd4b590a8d1dc9d1ed7b44ed3187dba9260bd7c732fc10981f5b9bffd31d362cb05150173618aa36138984431d94a323a9b313","services":["streamer"],"enable_msg_events":true,"port":63352},"up":true}},{"node":{"config":{"id":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","private_key":"d007b46d1a1f6a2d085b54b2073f46bebdad50a651920b7083908d388be5e5ef","name":"node_094f9d0fd3bacb17ac67a2edb8ed394fbbd1c8d478233d3269cf217e7b788e21042374936b885bf23ee4f9e03babbe4e08db283dfc2a6e052a8a5ba133717dd5","services":["streamer"],"enable_msg_events":true,"port":63353},"up":true}},{"node":{"config":{"id":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","private_key":"260581578bd37f4523054c95aee9f6b4beea4ed6b39edfb8b9506264d93337d7","name":"node_7646d08b3e24183e3814de130235c41eb88a5a2abff23eaff3c91954ad3ef55fe27da422dac5b894312276bf1a2f1aebb8b636a8d4a0c93d9b016d441df3fa19","services":["streamer"],"enable_msg_events":true,"port":63354},"up":true}},{"node":{"config":{"id":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","private_key":"49be559b16c8e23e2031acd1d93ffd182c0f09e54f7893335d04febec1b48e99","name":"node_9b618ad34d0dfe0fccbd5b8af365cbcdbb43e802df5f23e91c20d21f3ead6169d52bf0db7e5e111abef630c9853419fb33a88cf111d8507b8708208b5d65e35e","services":["streamer"],"enable_msg_events":true,"port":63355},"up":true}},{"node":{"config":{"id":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","private_key":"7b742610b213ed9b0492c97d62574eafc92dd73cb24ba74c4f1023fb4d8843ae","name":"node_4a030ebcb1a59230759d25a3c87daed22906b520d55a78eab2083bb52358ddc3a5d2d7c3265e6c54a990bf32d0dacb85905a4173d72ac18fcd74c43ecb8635c5","services":["streamer"],"enable_msg_events":true,"port":63356},"up":true}},{"node":{"config":{"id":"bd39cdc673c4184193347f0efd74bb894f7f36a994c154ffe3dadcd456efe460","private_key":"c90aa4ff988d9d5aa9ed033d3c8076c073ebcdcb0131604299ca721f38d363ad","name":"node_e0f78c59a61f2a953553cd5742c58f4a82e38e35ac7eaa237a6ffc9fb0a3a26a2d1d1afae249e611d1320f2cb25804bdbdfb6d77336449716b0f4beb3298b8aa","services":["streamer"],"enable_msg_events":true,"port":63357},"up":true}},{"node":{"config":{"id":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","private_key":"77bd908de80bb222465d000554b9467681aef696c9e1a81e51dba688310dc7af","name":"node_84dfd4e4d9f1204893f190eacc4c3999e175c433f70de86e335d27477c097a0552f211a21764f01a708da4c9e9e6552191a61d3e48f41d0e84f01e7ae3beb34e","services":["streamer"],"enable_msg_events":true,"port":63358},"up":true}},{"node":{"config":{"id":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","private_key":"529ac85f80c2ef25f0cca3417a1745d3573eef2b8cd611eab45c6bf857c87158","name":"node_ba21a373781966135d6225adae024a3f3e421b852f72f8b0569cb92fcff87c33f7e5b799a509b8eff6ec6dc46a9b17d5bae1e91ec7c54c0c712d893d54b140fa","services":["streamer"],"enable_msg_events":true,"port":63359},"up":true}},{"node":{"config":{"id":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","private_key":"46d69d2cb90a6ff16facb7994569557da8b0c76f74d238381a2a53cadbd1d8de","name":"node_fd4968df03d767a223bf7e4dbe26b5c27048298df40e81cad50b344fb3e6837965f2646dab7ffb2891a5c507343ed84bb8bbe403287b51dae538d58fcb6e7623","services":["streamer"],"enable_msg_events":true,"port":63360},"up":true}},{"node":{"config":{"id":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","private_key":"668577a2160c0a19ddd60a032636f42ef4b0f8d7f8b15129b39bc693b04951b3","name":"node_07c63ca5d0309e30d1ab12e7fc142e6d37904b552639cfa97850161491f28f20d87e0304be29357bcfc2ff52fc52fd0d4f6e6011ebb239cced8b5c8dad7abf45","services":["streamer"],"enable_msg_events":true,"port":63361},"up":true}},{"node":{"config":{"id":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","private_key":"ccd33f1981701e030d7880879453393ccb90b02d71fc85a89d82f903d1a81066","name":"node_920a301186bdf665a721676ece7c180413369ea6eff7537f42049d9eb409f28cc864099590116e885f57368959fb2cff7003697d9b1515e68bf3fa7aee0278ef","services":["streamer"],"enable_msg_events":true,"port":63362},"up":true}},{"node":{"config":{"id":"294066bf056b1530308d096871b08a046086abcbc7ed7a0a97d9fb46cccd6003","private_key":"1859e8cc62440d9e1d1c35f842b8bab336798e87932bc4f2456c327fdc777a7d","name":"node_89a5b92a9ca045f12c35a70084e6f41f9775a25d6f105f8c9349c7b5e107179d8a96bdbb113b2680edf5183e9a42fd64bd7b29d0dc7797c583b833f869313699","services":["streamer"],"enable_msg_events":true,"port":63363},"up":true}},{"node":{"config":{"id":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","private_key":"45dad25ea664452913d4fb09debe8987c94b88ef752b058a7bf352f144a9c6ea","name":"node_beec648d79437a5d7b4140ced45d82d035c6f64f9c67c58386703811d97c3846787eaf274d2298df809475f0b7519df696563cb8e9a5fb0a9cc419ab13fa8d41","services":["streamer"],"enable_msg_events":true,"port":63364},"up":true}},{"node":{"config":{"id":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","private_key":"042b5ccd68b8dd6c125ecd889c304299fa18c22841c95b5fae01a22fa98ce96d","name":"node_c73d088ade8756c74832c73542bcdb3e52f670f7a74ad3a7eac2a9e3ece6bd081f782a8e1d3f9a40b9aa9201509110f37fc57fcb10ee1ef3ef07b7eab019782e","services":["streamer"],"enable_msg_events":true,"port":63365},"up":true}},{"node":{"config":{"id":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","private_key":"1a8c7f2f97bcaa5711578ec1de04829d7735f81ba5761087e79a977405ec1801","name":"node_62dab8b21640e1b44dea452dd7e7de93f27a44840da72d56b11aa96e8463f3a4bb15e75a954d19fc897ae0030b16181d271b79d8198b7cffcddaa9ffcfdcce73","services":["streamer"],"enable_msg_events":true,"port":63366},"up":true}},{"node":{"config":{"id":"7ec4083eca4749caa706ac08afc0c91cdcec42f18e4d360f18695a79de362115","private_key":"d7a4980a0fc96842e8e570863a3b8e078baefe4ca8ab2ddf85c7c5d8f541fabb","name":"node_9e621c3eb534855ca292efa27a3fbc89b4f05c80a4bc65bcff2f14ff28ef43cbe14137d2fddc6fe2658bb06947d918476b479dde71469b8680e5d8a7a01619b5","services":["streamer"],"enable_msg_events":true,"port":63367},"up":true}},{"node":{"config":{"id":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","private_key":"d2e98b0004a9b7bdb030d5e729716e7065aa1a8c48efacf471718bc5da8ec990","name":"node_afe5b217dc512eee45575c189826ee5640d11a0ac9e8e4b03e9e62e6cf6df9de500bc8a36cb7d82a2280ce184409164f1772cc5c9e59ad04361522d2b8c364e0","services":["streamer"],"enable_msg_events":true,"port":63368},"up":true}},{"node":{"config":{"id":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","private_key":"bd3302dac250b294208d6ed14b0a14a6d60d75be1f68ce5cd1e250a47032fb3d","name":"node_db8ba71afeaea1f2964e8978a6eceeb9efa3958424424022213c8193443f8d5afd18490afa954c76360c681404809c7cfaf7e4ebd8bd1a42d403f7f1915b0d29","services":["streamer"],"enable_msg_events":true,"port":63369},"up":true}},{"node":{"config":{"id":"d56025bb110210216fdc8f86ad205607c6fb4744d3d444931aa17512a854356a","private_key":"f5832a55177daebfda3bcaf8b5ebc11eb44f4d5207b8384fd35999944d2fcab0","name":"node_2f1967b4410ea2ea8fbaf46771ba82c2846d00aecdb6f873aceae33ab38f72d0a2c2dc6046d3034f514efd090d42011d48e9ab0cb4e3caa2b49a96ef732215a1","services":["streamer"],"enable_msg_events":true,"port":63370},"up":true}},{"node":{"config":{"id":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","private_key":"8285330088dc95f441c68f12765ee99f065fc41f665b54a257172d9b4ff6e017","name":"node_a99852c5bf08ea4713af9cade5343d1259bfb90401aca7fcff18c58cd56a98804dd16c61fea71039c0ac31b9bbbb9dc5aeaf0c2f074d3365e88d17031802d636","services":["streamer"],"enable_msg_events":true,"port":63371},"up":true}},{"node":{"config":{"id":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","private_key":"96a76a56912e05cbd480fde154743a65f4bdaba5395cf685c22681fa403807b3","name":"node_4e68553db7be307b8379e8bec383b5c92e4b23debcefca575d24504e2062618f74c26e60e0d9e47f096997a7f841e2c32c994124f495ef3352ef964b33fd3a51","services":["streamer"],"enable_msg_events":true,"port":63372},"up":true}},{"node":{"config":{"id":"b40da884b5e0ab429b3608ce6030c0f7f542d008713ed1a444c70d9c00989ecf","private_key":"bf0a6c406d390baa5a33fc469dd8b3ec25e406364bafa8ad3d9b422dd58b4a87","name":"node_a608a746f46d4b2e9267c65283380d6d6cb8885a64428fb77df9934bcf4dd1723587ee7c8f4a642dce266a12a6a4cf4e5c6db7fa1024158425a6753e7ab7264c","services":["streamer"],"enable_msg_events":true,"port":63373},"up":true}},{"node":{"config":{"id":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","private_key":"4010f083fe123b4e1e1091a054b7af104d76038c1313c71470b38aa16a05688b","name":"node_fde7705f5013c3b6b76a35505c1e3d57c033a337f2a71e912ec3a3d2da8e113bd7c36d94908357e9f7654660621f647ad8c66b0126dfe91dbfa3b8c7bd2183d3","services":["streamer"],"enable_msg_events":true,"port":63374},"up":true}},{"node":{"config":{"id":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","private_key":"e2894e5fef7fae6c4a6969fdf39bb99e8fb16849881a5a7d45cfde92ab16232f","name":"node_3dfd733dde9414af916625ab981777ff1ad7ca0f6d1e4e7d9e2739969e21182fd13d85e281868653e7325b20069356b11524b54143c47fd3955a911cf7325ad4","services":["streamer"],"enable_msg_events":true,"port":63375},"up":true}},{"node":{"config":{"id":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","private_key":"1273dc5cdf253e8042d52ca10f80d25481d40b5cc80b37b3e128edafde8fabec","name":"node_5142eea687de4248b2184e9896b245563117648e7a156255d9a7ae730313d5d383db3be1125b5accd7e743c2a5d8c3289ec559ef7cbb803b9e82cd49bb213d9f","services":["streamer"],"enable_msg_events":true,"port":63376},"up":true}},{"node":{"config":{"id":"aa7876d43dd0ce40f421f68c0d13c8c718d7a7a5e7c567dd20d87d19934cdc05","private_key":"082369e49a61588917d594d0a90c2d53968db7910a1fa9a978da33f53d8f5166","name":"node_b597c9ba4da20c49fc4ba019571321d2579588636cdaceb9f225706574970adbf4771dcf186b1decd4e14cd5d257e30a36ca829829598c1775902201ad9793e2","services":["streamer"],"enable_msg_events":true,"port":63377},"up":true}},{"node":{"config":{"id":"715b38eca0c630aad73e5f7d65453c6666f459994e3cc4bba586af75d78e8fe4","private_key":"5ac4501287d61d0083f3143a6184fca450c07d149279fdb3a19ae3dbcf456732","name":"node_72e30d4562673c5c8fb03709d93f73322747858d723b7eb5ef2fbb88e0331252946b7e93ffe434fcb599c4fff57800afb29e6c60a719f18f49dba541c3591c6e","services":["streamer"],"enable_msg_events":true,"port":63378},"up":true}},{"node":{"config":{"id":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","private_key":"cbc289ddf69a20e78f479b1587fe2955c2ee7a0bc7a743ce61c13c07139b33c1","name":"node_eaca77d20c4ad6b87649eba21b750a4b1a2c575e06bc42679d18fce16fea15212e9a7408c16f3b24d8dfd1e397314637e72e292845d09534a39035bd9203ee9e","services":["streamer"],"enable_msg_events":true,"port":63379},"up":true}},{"node":{"config":{"id":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","private_key":"dce8d0367de94896d0c24049979fda6a48f77b8e98974d66472666a5a4365865","name":"node_d3105ede6c21bc643d523afcd1309a5dcf03f70797a92f167c3a19f86ae98882fe1a9d4094d7a3dee4a0833b8075b6c3e9aaa7f4879dc47bb388c3ae85522288","services":["streamer"],"enable_msg_events":true,"port":63380},"up":true}},{"node":{"config":{"id":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","private_key":"138e8952e22f78dbf4ea1aad84cc393a1553192402f5cb23a8d15459d9254240","name":"node_6f60ef501b41f9049419b033ae8d793a18067bdcd60347537b0dc3873bdd9a2d10d24909cca7c39781765746128c2165596f6537a94f58279523faee71f9c51d","services":["streamer"],"enable_msg_events":true,"port":63381},"up":true}},{"node":{"config":{"id":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","private_key":"b50b26bf10abf56d51a891c4449915de09ce392d6c5a8170ae5ea5eb3a082d8f","name":"node_453779010ed7c59761da47ccd8869d3fa8349795e57edb9eea416c0712301ea622c7683858e309d19ab137ddb0edef161d0d00a361abeecf0628c3ffabc629ea","services":["streamer"],"enable_msg_events":true,"port":63382},"up":true}},{"node":{"config":{"id":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","private_key":"269797dfe5cc0dab8d44b114878dd8ec081d6551214db901b361817184a58bcc","name":"node_e2a27dcdee1f89fa362a85c4397840cd8be4ad8d1b9ccb23188c303bcab7619f38772f71385922d090390973b62fb8249efc103f71bc761041bcb3880ec0b935","services":["streamer"],"enable_msg_events":true,"port":63383},"up":true}},{"node":{"config":{"id":"6701b38360d0a72ddc355c17385af34b7ed9eeec00bc3867e162b5571af69e3f","private_key":"09082c0128f28346808a35b0b607d4e7d6c9ab7e894b5166958bb2eba5f4c0b3","name":"node_97981f4b825620985b746c85824d7a4d307ee25d60a72c2b7de4b1d4f3b1c002652adc3045842d350f849789e410a0357a0b42fa637a1b4fc9d39e44f8a674d7","services":["streamer"],"enable_msg_events":true,"port":63384},"up":true}},{"node":{"config":{"id":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","private_key":"ca130a8083cac47d964d46dbbded845892b326aa4fa12489d21578f9f018d45b","name":"node_fe62314839c93d2f6e3aee984d5f0b321897c6f39e66800d3de8b773baf6f19e0a03cae025df512aea8d5f1b667b6dbb15c192a90a76d3031c14e33e5c16c4f7","services":["streamer"],"enable_msg_events":true,"port":63385},"up":true}},{"node":{"config":{"id":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","private_key":"5d649fb189f6b462f678a083e7ad8d874c6bdeaafe0817cf4b361448024d9428","name":"node_c903c5ee3d9cd65bea3ea777e39282c33e1349d945411fb2f16321bf582c2f400956a98fbafe5873fc5f6f6a9a051df5da7e94cbd340f935abe80f91e79ef875","services":["streamer"],"enable_msg_events":true,"port":63386},"up":true}},{"node":{"config":{"id":"e4e95f8381568c1cba7e8f64dd710554b923241a1c988dddb42a2cd96434705f","private_key":"74fcb757f856401d62426a3e62cff6870099b0b261afb281d8d9e7dcf355a87b","name":"node_5420da16f7ed5de6b498dbc9a83b7e92e1f9900a6d04ef7351a0bfdfd442b98a5634f28fcea32ba3f3166d21c8ff5a39be70895b32acbdf7592d1d616a345a21","services":["streamer"],"enable_msg_events":true,"port":63387},"up":true}},{"node":{"config":{"id":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","private_key":"3e1263efdc899dcd32f1fd206de2370195ae82fccb81eb354dd9cb52fc5994e6","name":"node_f54335964d9b4eb9813eb05f40ef1c523bbf0faac5a04c4737467d590fb6b55c7fa1fc5d4d32c5491d8766b9be6fdfd3ebdb25c5ffe1719f36c86ddf87ae3a85","services":["streamer"],"enable_msg_events":true,"port":63388},"up":true}},{"node":{"config":{"id":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","private_key":"a7cb8f80391de65478acf12a59fd0a0e9c5f04f0e4bc1da34b9acd35c586fe98","name":"node_40bdac599b00e9c41ad6476ee415c33d9c17c5aa672692c2404e523b29dc315da929a86874bed87fd9009871805d1dbf3098e7a817193e3b08a6eee66346da79","services":["streamer"],"enable_msg_events":true,"port":63389},"up":true}},{"node":{"config":{"id":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","private_key":"dbbe33233150cc109e4ec1b6d6bf5f14bb573d87551440db2407ec75821b4ee6","name":"node_b26f7c0113a5ae885f7c0e608aea7022a9c8bfc40c5643e60b2ba7343ff09c5be0bc08be34dde3c6b85fea192a46d4764770c12cbaa233df4231a1b99627ef9d","services":["streamer"],"enable_msg_events":true,"port":63390},"up":true}},{"node":{"config":{"id":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","private_key":"1058ecb8bc4354469a7fbef819dd8efc60c72db3101ff2f4d3d8089876ae94d1","name":"node_829b074b70d757da6aa00f7bace3cf1b5d2d4eeadb7f370c1a85374a01a70b54ac53d747267e386f3b41e6d790fecdd6e74b8f89745501ee82439a66ea54443a","services":["streamer"],"enable_msg_events":true,"port":63391},"up":true}},{"node":{"config":{"id":"af8fb611627063b408d2b96c1ab68fafb223a86d947de3dac3a59649cd13e0cf","private_key":"7a22f9270103c5214bec5fccac665913b7e9ba442667ca7ca7f1bead2d44e594","name":"node_302c37f28c69fdcf00b39ce0c0223de0d94cebc2c0b068315b95b76b5432573ead0656569441029ba9054d8d3d959fd8735e73913af5ce3f1132f8271fd437ef","services":["streamer"],"enable_msg_events":true,"port":63392},"up":true}},{"node":{"config":{"id":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","private_key":"32e05e400e6938d31aa93e7666905a5a12e2b4b706354eae5f34259cf6880e60","name":"node_4e95fb8943ef6502fa470870a7ace4ca11e18589bd2073167543a922317b35e40b5068934d07e56c8fdd23354dfad51fd2899db854f189841f02b5914cd97749","services":["streamer"],"enable_msg_events":true,"port":63393},"up":true}},{"node":{"config":{"id":"85642219f8bb8ce048cfce5aecddc8acd035afa035a0137c354f5b6fc3e3889d","private_key":"f6b0641261c3c0741d61572a591e72180deb8ba2c9922ea5db694c5b372aef6f","name":"node_807340e5fdf6c21a56ef2fd158c32e92201206ca85fcc147165a408f7a735964afd47c397e663610ac53870ae722c0317291732b0422f8aeedae783d9c730218","services":["streamer"],"enable_msg_events":true,"port":63394},"up":true}},{"node":{"config":{"id":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","private_key":"5809952239bc91701c49a3ca4362fe2f96294405e789a467c4899d1dc94f88ff","name":"node_c9cd9dfb9f90a7948ac5a42c7bd63263f2eef6e1d85547559f7d2ecbf5f9cd1eab051b8e9e661b573c275d84091a44e73f6fc6bcc91dcdb674bd5d939190fc01","services":["streamer"],"enable_msg_events":true,"port":63395},"up":true}},{"node":{"config":{"id":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","private_key":"284f36dbabbb181f068bce762670de591bac534570ddba3da4c91381188cc3a8","name":"node_1bc1d6155ba35d29ea16938994e08df76ebd8f16bd180bd6a9b9d8798d0745889fe72d641dd78bd8ec13526929f7eb5aaa6a388430fa69033d6db581e53c6422","services":["streamer"],"enable_msg_events":true,"port":63396},"up":true}},{"node":{"config":{"id":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","private_key":"c1b4e417ecbce61a39ecf4a30b86e303e82534bd890eea7c30b6ab4d0bdca861","name":"node_e267e71c2dcdbe5a500b23444d2d4037ea15afba8f4f2b1c0e54a274450404fbee6fa5439f7cddf4ba1aa28fa93e434b01c239add58e30a5423ca3a481584d6b","services":["streamer"],"enable_msg_events":true,"port":63397},"up":true}},{"node":{"config":{"id":"413b15ae74e38b8610dd5edd52e05630f8acfa4139a1b361775afa8e748ebbfc","private_key":"eda1f1637d46610c0a885d4e08d8d4e776868f98be4dca745ebfadda65f2a81b","name":"node_e98a08e8e3544727f5ee27f4465375a67ffba490fcdbfe7bfdc909f6b68bd279f776c65d38c2ccfc33079f852f76349c94d68fc6989c0d981f22333f036a4bb5","services":["streamer"],"enable_msg_events":true,"port":63398},"up":true}},{"node":{"config":{"id":"e54d69f3bb061c62dede6e5a8704700bf9b8063edead7c755c9586f4d96754f1","private_key":"51d301cb37e7206eeee2847d753aa67cbf0513b6e7cd41022003c2e7b3ef5581","name":"node_cb310c0e86aa455d7c9af6394ae38561c901da30a51d7ccb7e66a09d9a8f192ae8caeff106043426d56397088f141c4e01e3b98f1ee583ffc7318b5f730f3ac7","services":["streamer"],"enable_msg_events":true,"port":63399},"up":true}},{"node":{"config":{"id":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","private_key":"30d23072d68ccfe357cba2b3294b9584b591e8c7898031f7c4dbaaadfdd05e1b","name":"node_1090e9d89cb278a30063a5396d9ba795d3bbc237c76915a6fb7d23aa1822c8535d2fccc44cc8cd2acec68444c0bf1bdf5195ebb9f3cad8dac3d94a4d9c0e5309","services":["streamer"],"enable_msg_events":true,"port":63400},"up":true}},{"node":{"config":{"id":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","private_key":"90d8283793c2eb15b76d7d9e492b3c3e7136115c74bf027ad9985179380a82b8","name":"node_4b784f072b7c96c30612767425f552e30e45157e70139dfbe0ae4ff86dc31601adec60f8a3ad9747b02b5219073d8813af9b09d59eaf7ddb7ee24856aff6fa8f","services":["streamer"],"enable_msg_events":true,"port":63401},"up":true}},{"node":{"config":{"id":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","private_key":"b9111f0cf5ecf0a5dd7683f07c58f581a175bfbdf307224db008d7792e30c6a3","name":"node_22010c9829626a6892258b09cb3f3edc3f21382fb487b5dbf0dd20e198e37e26134aab850e30337f398d1491b5126839aeb1953ec144bc19aeb889c832f67c6a","services":["streamer"],"enable_msg_events":true,"port":63402},"up":true}},{"node":{"config":{"id":"48ec978577e2dbf0448ab119a3821be5d25dbf5a295fd4f695b9311429538a26","private_key":"7652297b6b2c9a5339f9d375aac2c9b81f0274e3949b4033e513c02802f040bd","name":"node_043fe7085e8c90c18270f84dba5fab138e50216a1cf8d81a3794d156fad7fc81d1422aff169b244b13f91d73eebb5de464f97b1b93b3ad2a58959cff19238842","services":["streamer"],"enable_msg_events":true,"port":63403},"up":true}},{"node":{"config":{"id":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","private_key":"7971b1a36d0caacd64acbd61a149d41c05102d757fc313f2c49c6b2185bc22d9","name":"node_329145e75496cecb5f5c8f00313a52ddd8c0e8cd27ef1b2bc1b2b56e4d4089aaf5a7a3c4d9a40bdb92483fa05bd24d1cd0fd51883e30439dffa0fc855e1a7cd0","services":["streamer"],"enable_msg_events":true,"port":63404},"up":true}},{"node":{"config":{"id":"1587ed904df26ed711e2235fea41e5812a7d33ce5dab2ebb4e88515b18f43d6e","private_key":"62692081cdf8e04e73d8942c5fb423f153ea6d32d16fc0e3b05c10927bcc42c3","name":"node_2a63d875e715c2a43302d32d86d30c7d7faed46c08a67bf488762637216b99dd87333a66df6d9f3a57df863b1f1a219ddbeebb9f3e92e523bd08540a195448ca","services":["streamer"],"enable_msg_events":true,"port":63405},"up":true}},{"node":{"config":{"id":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","private_key":"bff5d02d0e187260b2ac19e027a5a8c72d358aa420a1dd22dcc8d34a921a81f7","name":"node_0deda014fa3c0bc8f37d853e1448f95f56f97048c28797d72258fd3ba2f1f776ae11566197466596356cd172847ab02a89f4cf047e58383249fb2a4cd69b6206","services":["streamer"],"enable_msg_events":true,"port":63406},"up":true}},{"node":{"config":{"id":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","private_key":"e2478cb11c2e4c83e5be5962e0a2e9dd367bc4bed98c7bcd08c211f35010b4f8","name":"node_c332fe1f9b6e25f72c7150631ffa6e0929ff6eeab5d17aa45eaf669bdde869cf404505ae990a529aa3ca797cd0a96f31cc41431a5d4a163132be566f91623c08","services":["streamer"],"enable_msg_events":true,"port":63407},"up":true}},{"node":{"config":{"id":"507060be030344850df6fc76719d503254b38d96d77eb25f49ca9d955944b10a","private_key":"c4dc5aeb69312070307af3c8a24d468a0284733f29b07b5407e0819cb054fbdf","name":"node_5566111b7f14d8ec83ba6456488f3fc5c7e2c86bf406dead33b814e1ee9b569d34015a4d88226b7e28fff7784395c086d106287cbbc80acfe75b05cd6e12903d","services":["streamer"],"enable_msg_events":true,"port":63408},"up":true}},{"node":{"config":{"id":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","private_key":"6832f6f7e27fc6e2c9745c44ea7004ffd4f6bd869b779e15ac0a4a2ffade7999","name":"node_b4a5efa9028b2d196855393892f79993c2812d02b758f98813ddf82c5d943faf6d66eff811cc5a51936c118f4c768c0d13b54902095e040c602761e63c08655e","services":["streamer"],"enable_msg_events":true,"port":63409},"up":true}},{"node":{"config":{"id":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","private_key":"3362a08daff495b41e00fcb4786a68fff7a58eda86b6f6cb61bef90aaa604daa","name":"node_adfba6d883cacbb141cd8c59ee04d7e5d520f1ce31a81e5db2d8b2380bf801c1fe4b265a66b71f9643d13a41cb1f6fb4b83399be786da4b6e171b6784bc56e49","services":["streamer"],"enable_msg_events":true,"port":63410},"up":true}},{"node":{"config":{"id":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","private_key":"1d18aaec8f9b90606b18283e8527016a8122999804aa11d280ab2bb37f14a948","name":"node_2bfba66ec9d63e928d77d455f2e10a14d6213e515099d5db14fa44a8bfc147eb0fa7afa9aaccdc86453c6903b55556059a2e13dd61fc494127d4a3ec173bf9ea","services":["streamer"],"enable_msg_events":true,"port":63411},"up":true}},{"node":{"config":{"id":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","private_key":"757285cfa33b746d1bf24044430d43f5ff7401cef2452217ac9fe5fbce629acb","name":"node_0b7e1e5acabcde503ad827e45724402406114a513f81040a379b0f82df2eab4456b348b27936369c49e3c264d971c6a9e2c2217fdbc5e5b93ac6fc219957834d","services":["streamer"],"enable_msg_events":true,"port":63412},"up":true}},{"node":{"config":{"id":"802f9e88535e40c179cb5f7d2a16500696e2af2115d3f7ca0cac5c2f634109c2","private_key":"cc0bf84e41ddcab9146ec892653a911dce5007786d77ec7a32e490fea0a29687","name":"node_7554993f6efd8d9016ff525ae73c41ee2e0dbd4970c563ba9b61a99c1c8943b433c4b7d48450f6e0fbc9ac198d907698093de20e6c1f7755520fb1e1cbee4eb4","services":["streamer"],"enable_msg_events":true,"port":63413},"up":true}},{"node":{"config":{"id":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","private_key":"d1add26924dcfc37eefccd6271759a9b0a2052607f3698be305e1c9bcb47ea66","name":"node_4fe37f2dfb03707c90a6fe91dbcfd7497db70ffc20043091d50e1cee573210c144e8749340f2c3cb70fe8fc51b317d49e8734dc51ce64b3cdccdd639ae35d1ae","services":["streamer"],"enable_msg_events":true,"port":63414},"up":true}},{"node":{"config":{"id":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","private_key":"fb8563b48e2c07948cc2347d50c953b2b4bcd79a535c2ac597d8f8c7f1ffa8e6","name":"node_a7507aa5665e165ab2e2a07def272f0677f9d6d35bb82268a34214384e7d8ef7635a299368752e63cfd02d4e60231076baf23cfa10d17492e55181b391ecf335","services":["streamer"],"enable_msg_events":true,"port":63415},"up":true}},{"node":{"config":{"id":"a011c924104e072ec415cf72255167582e5d3f189f541066ba88b3ae0041b508","private_key":"f7b5d2c9faba98230f0e0bbd82106d1fb949413a7940eec52e71e9c5c7266d3e","name":"node_7e2b0b4f3b55e97bd7241b984ad117af33bf3baacd22b3ed503a8a59f75828df745fb3ce3be4276d83eb484e64d9aa251103fa994ef119eaddb97a3503b9f292","services":["streamer"],"enable_msg_events":true,"port":63416},"up":true}},{"node":{"config":{"id":"208e648a32b59b77c2abbeac0402c8d009485a19dd4a04a216cd91d0c6713929","private_key":"61a9bec441ae9ada2f3ed71874596db20f2807e921453ec39145770056e3cee5","name":"node_460531928a4b084686343c8464b37c3452bb3566045698d08a4692864de5581a9eb1dc632329e7d3ccb73ec31ee7693787d69df8458efaba9a7105d87ee2e053","services":["streamer"],"enable_msg_events":true,"port":63417},"up":true}},{"node":{"config":{"id":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","private_key":"56e11a31d85d33461e13dd635ea6c0d0be892feb3ccc9dadc327bbd855401d2c","name":"node_70ee3c7e02a123f78345d41c386e413f06b51f1ab346c2c472891eed7dd26a1f14e213a0f4f2d872859b9b535936a6d9b072d2604456f76221a422710411822f","services":["streamer"],"enable_msg_events":true,"port":63418},"up":true}},{"node":{"config":{"id":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","private_key":"6df50913c97d23ce9e4ed894465ae4eb0f562d595e46d694b47e72d31c8bc8fd","name":"node_049a2b0ab4d49789dbbe1837e2d2ee5220a9401729ea87ab29385b24d79cdce255c3d2b5de6484d49ca6ce07bd41832598260c0a8c290e01d133d77f066f2f99","services":["streamer"],"enable_msg_events":true,"port":63419},"up":true}},{"node":{"config":{"id":"df52d5de0f5e69e7e4c9e0ffcec1f9df86d38044bb57e5a0cb18bde354ecb66d","private_key":"164a5b537bdf9173b59b281a68d7da48819c2367e5b5faa1c94c74453a413015","name":"node_1f6d803a4ca186463772850ca0901fb920af7e74fdd2480c3c368fa707838ee91bbcfb57d5e3510c802b9b85f2d954f04c9b675414274bdaebae6488dcf94c10","services":["streamer"],"enable_msg_events":true,"port":63420},"up":true}},{"node":{"config":{"id":"4b8acc021ddea1fff036b4b0e5939c38507ef792490701f7b341e4ff10a78567","private_key":"0b14673d7790bcbb1b3f0d042dd9b22953002e6dd901486b6f203063bb8b5a79","name":"node_979838eaefcff456cf1cd9c47ea46854704f5757d8d7ab907019bc422b01af4d62252579bf045d143fab491b0bab823190e2782a457fbf51aa99ae17ec9e145f","services":["streamer"],"enable_msg_events":true,"port":63421},"up":true}},{"node":{"config":{"id":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","private_key":"5d6c015866ee2afba25f1435274fb61e0b11b93829309bccad6cb662e5a4c8fb","name":"node_ed6c793eb4565aa9d883a76fad6c97ad0b878b9ef0235e11e89db1af25a9653205f725c6ea258b84bde134f1a6437c6d109b859d17509e5016c4ab98be59032a","services":["streamer"],"enable_msg_events":true,"port":63422},"up":true}},{"node":{"config":{"id":"d777e45233aa4d8b723f6a6aa9cbf5de4c0b19f6fd00af6d538b67ec01b74ab5","private_key":"acbf964dfcd41c86715a5ecb9c03818b577cbb0755692cde879ff1e4b09e5df6","name":"node_6c143960dbe3ba4c95e8debc1fda5f420a2a673eb46f7ff7d9a30f4cd2067e67ef8926bf17f0c99b16c79b6dc066b52409a11d47117d4fda4211636b1b74f96c","services":["streamer"],"enable_msg_events":true,"port":63423},"up":true}},{"node":{"config":{"id":"d96e430326c7fc91fcd19c4e85b472a41ba24317ff09e5bd8eb2bd7a306499be","private_key":"0b325cafe8524b8c126e860a6295fdeff6cf26da5e8eb426b86b25ba049c942b","name":"node_7bbb3f3ed51cf84c077806fbd6d3578e105bc32aa6043da188793f066ad6df383a9f7cc0bd3124f4c887704bd188d480ce264fcdf178905bbda3b270927ea138","services":["streamer"],"enable_msg_events":true,"port":63424},"up":true}},{"node":{"config":{"id":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","private_key":"e03e1d397bdd121257b1cd781c14847cf220f23fa837a4ba7b48f7b0b51cdc3c","name":"node_2b70e5ffcc0bb1c4f8acc09dd1016e67c9c96c92d091ad481efbb71bd8b2a5a5869c6d668617a9b2cbfe91baf5edfe150c3824c84d90899a974099071d171441","services":["streamer"],"enable_msg_events":true,"port":63425},"up":true}},{"node":{"config":{"id":"bb3198bb61f3dc96365a99b7141c0b85720614de4640afa76458d27484e1353a","private_key":"8e1b0bcbebda90c6f0ba8edadd9133b28b77e49b584580e1219654aeeb9e3a2c","name":"node_f5c1215a84a56d52f92ca0dc3a32df98c98a9c2ea2e9a34560210c16de1b440d20d043c8c25955dda8c22e55e92ddb49fd82e8b9b84072aabaa755a229bb19f4","services":["streamer"],"enable_msg_events":true,"port":63426},"up":true}},{"node":{"config":{"id":"f1ad1843560ead294e9d60c9bfc99d8a827f2cfe557e0f9fe7ebf82aec56926d","private_key":"ec0e383740a40976e4b9195b6a89e9497e9562729d196b3febbfea319be5dbcb","name":"node_8c6d77833bf1a98beabe7a92d985d13d52fb2e50768719ffbc6fce21ea676802a074b6e620b3ef80e12fad04f94a3c288d91df9716a6cfae09ef073b9dc1f2cb","services":["streamer"],"enable_msg_events":true,"port":63427},"up":true}},{"node":{"config":{"id":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","private_key":"a8c69a5c41f2362bc5de8589f3686f3c59b02b58cb7139ba2325f1a7ff62c39e","name":"node_b3316f4fd7dec218a52331f25d17a2faf0f2a417808d7dbb80038154abfbcd2d78da074837b648cff28675f4aa0d65ddb7d15abf3d7e0391d401e313a3ef24a2","services":["streamer"],"enable_msg_events":true,"port":63428},"up":true}},{"node":{"config":{"id":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","private_key":"65ded6c2ad477ee077e81c1aa90fba5d4e0d794ea8dd5241a1545869c895185e","name":"node_60725e8d0e9333636e598dd666f459b4be898d9abc095ee2c1db289673e7e969b50e2093956fef5a2f1d23e2040159bbabaaeab17fc6dbbac3f6acab766e9e60","services":["streamer"],"enable_msg_events":true,"port":63429},"up":true}},{"node":{"config":{"id":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","private_key":"9e522b8e7f94aa50d51d7ac396a12701677f86d0f9ad7111b329ba8229880db1","name":"node_48a5e01e06e297dc1c640ac9601171495bd70fea461c60e7e6e2532695e45fcf68183c2e58b5e6931a6c1c094f5331efbd7f2d93d5f84efdec5a35afa2124438","services":["streamer"],"enable_msg_events":true,"port":63430},"up":true}},{"node":{"config":{"id":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","private_key":"2325b0e86e0abaef149e6063ff789995d1f6d863de595a5abcebffdad494ebb9","name":"node_5dc69635f6d573c824ea83b18593a47f9f21a6f91ce18d2bf977c97d21f8d2a8ce88829244d12a07d949bbd52fe8a2035bc89685fe1bdd4ee20d766d75d3deee","services":["streamer"],"enable_msg_events":true,"port":63431},"up":true}},{"node":{"config":{"id":"d0b76e826a6eaeabdc060a0588bd1becc1901b60a8eb4e8adbe61107883a2987","private_key":"dbdf629720f7cd6d4a8e26a5bb6da882212d6cbf43ddcda4712539c3db89bbf5","name":"node_ae4e21e1587a8475afd299e00ca7085c7bf2a44c2c64a3068f12223d9202b0125094385319034691ec26b497ec9cf18fe8c0839224bac1b84279410241cef979","services":["streamer"],"enable_msg_events":true,"port":63432},"up":true}},{"node":{"config":{"id":"f0b803dcfec89e1d68739ca05cf0c9c04e80a2b1cd47496a83168a71184fd4cf","private_key":"315e74a348a3f8e172319c29da33577435f6415466e8cc289af2dc7e142987d7","name":"node_0b30b36e155db04c448b662c4df695068b2331cdbe2772aae5fda84159e977515a5ff2e156bc225a9281fef2383926ba8cb11e9c1846fb40db241e524ac14e29","services":["streamer"],"enable_msg_events":true,"port":63433},"up":true}},{"node":{"config":{"id":"48df69314a57a6ab2fc31108104aae2242ad2a941d2f87119a151250344aee02","private_key":"510061145b0b868f1fb7e83b850d0901227724f4559e912c0561ac310308ea80","name":"node_17a79cb279306b6bbd35219278f6bba03111079d6c4fadcd3c5e57b09678e111bda5f8e36ca3d223e51e255d6e857363b7741e438b32ec6e6c0dc1b224071081","services":["streamer"],"enable_msg_events":true,"port":63434},"up":true}},{"node":{"config":{"id":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","private_key":"b4c3b4d3b3fb735ed7f1b4dbfa53fb08ca505f7570d376ca8ff9ba10c108fea8","name":"node_617595a230e0a74a3db3b47cd27b941e02bdee9a84cdfa8eaa63a55941eb5caa79e20fb69726a4d46a0e5aeed24039f8c5a20679a692df0bc140f42558ca9a2b","services":["streamer"],"enable_msg_events":true,"port":63435},"up":true}},{"node":{"config":{"id":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","private_key":"0ceaf25d118fd30dfa73bfe157c4bfdbdd7bcdc44e699e71e2e397eff0988045","name":"node_7808fb798d22e78fb1d39b87ddf99a503f820993bc64149eca9e81d9d58fc1adea60d8de14a4f06cb89f3190c0ef0c5e182621a14c9361ccb7e0cee667599cc3","services":["streamer"],"enable_msg_events":true,"port":63436},"up":true}},{"node":{"config":{"id":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","private_key":"b8dcd04f9dd7c7d9522112f1d10c32738ce2283cb755c14b83b3e7d8d645c4f1","name":"node_f039df8a3206e6e17eee8e95010038c450cb4cc0b5580ed5795d4c21802e1e9d360f4528bdca794f457932b1d6a818862ec847357a861c488332ef6d9480daa6","services":["streamer"],"enable_msg_events":true,"port":63437},"up":true}},{"node":{"config":{"id":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","private_key":"e0f1d278d1346984b7b19789e39ae0ee607d17f0fc7538e8dc8537b44c09e142","name":"node_ce0a09bb6fe4c9c93c3dbd800ca9c9ac0714f26a8f8dac2149b6ce7a9011912a5621ea2e45de35c4d31dfe5add00558fdba5dd21acf2d81ba391e82b5b3a5804","services":["streamer"],"enable_msg_events":true,"port":63438},"up":true}},{"node":{"config":{"id":"6923d78400f277fe29c35ba38a416edd7c960cc75d94c35aa3446b4d5e2f6c3b","private_key":"6c9376463118cd8a2c68423af478150a6e0a1b811b922aa7d35974ec65943ad1","name":"node_b2538d4edfa736bfe2cc21326a44ce62f716b3480eac93584a2233b69c98883a2bb255f246218a58cecab1d0d176e6d1fbba9782fd70d4603e01742299b9ea9c","services":["streamer"],"enable_msg_events":true,"port":63439},"up":true}},{"node":{"config":{"id":"b820b318a3a12a7a7f45d06fff7f5d7e3ca2ca8097c396c1d76c735e1c915cc0","private_key":"d391fbaa56954f68499674edef05d07d58c5512ae18166b141c961a4dea175fa","name":"node_aa9a94d6e4eaaed20113c21752c224e3a052abd3769bd6e832759dbaf5154a943cf094eacdcb0727c2fe51a11bd7fc0793d7d5e3312b0aba89d428a00e1fde0c","services":["streamer"],"enable_msg_events":true,"port":63440},"up":true}},{"node":{"config":{"id":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","private_key":"199542f222419c9f399da0655278169815321c4420c8e39a377faf1446b6da1f","name":"node_65e954e60837bda10654480b227fa3bcc6e63356c52079002a27e836cd82ae2934ba77da60080e4b9f2d9324b982b24959bfd9624302e6c330408381054b43e9","services":["streamer"],"enable_msg_events":true,"port":63441},"up":true}},{"node":{"config":{"id":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","private_key":"ff50f9bc8f6ce42cff12b697c1d4880461773518aaf5f25e5c8a03a8af128fa5","name":"node_8ca3b63cb84f1e9883d82809a9b6777ec1029ecda96fe93b4d494ee9cbefcad13f381b3e480136e542e01054fe1ede66c65db84a0009871ce49383db461c8438","services":["streamer"],"enable_msg_events":true,"port":63442},"up":true}},{"node":{"config":{"id":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","private_key":"2c15cd49d4a7fc8bf1acbb4f86987efe85c77ba2712f82531bf893084ef4654a","name":"node_caaf695c76cf7c047d3f3bfe019d01618e4871cee8ff3f347913da815bbcde145771486f916a44d03f59ea9bc217f169fd0c0765fda3f9b9b28d02a522e180b4","services":["streamer"],"enable_msg_events":true,"port":63443},"up":true}},{"node":{"config":{"id":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","private_key":"4c9c04de05a7a437923df855213efcc8c9489ba08352e267097b6bca9e840fba","name":"node_b080891d4ba6d3ab224984360597f9c555bd1367630f5420bfaf230419a05b2be58bebf1554e53eb4c0da81cfb66d4f7697dd65143f11841ff62d8a0d82b6b67","services":["streamer"],"enable_msg_events":true,"port":63444},"up":true}},{"node":{"config":{"id":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","private_key":"5b756c056cbcd8e0a9a926f6ebf3360d7587152f836459cd22eafd76c3984af8","name":"node_2ca05ffc0ad771e1e045a47f1c0a79ce374a25e3c0e53c2a80d67e7479677003f62880251a83a5074f1c9651d828f1ff7e352935a9df141c1eb75a0c59b00a8b","services":["streamer"],"enable_msg_events":true,"port":63445},"up":true}},{"node":{"config":{"id":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","private_key":"fd26d9072de53e5f0a3d88d7c32e80650d289f661906c9837a7a0a003f3520ac","name":"node_8d4920d6e3a6717699b5a3af068cbdfc16858d7e393d158d41b1b5f345b47129eef89e1d055386c518f400b134678c21590f0317dea3436d4cef60f45455cf12","services":["streamer"],"enable_msg_events":true,"port":63446},"up":true}},{"node":{"config":{"id":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","private_key":"3e2d345fa05f2ec2c8c4ae80fa808f0acaa098d08a2a0e7be1a2749f7d01f0e7","name":"node_b6a998dea59194cc60ce97c7c9702368411889cb6905516b23f41cc5116fba8d0f237d418318eb0c333cad18003c0e9a6bd8c99080520771f2bad640e2e5ec1c","services":["streamer"],"enable_msg_events":true,"port":63447},"up":true}},{"node":{"config":{"id":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","private_key":"69afb2f43d398d53995f3880ed20e91b1d92307e4f7b40cc63a785ebe01f5e9c","name":"node_ff6f94005d1df7677600069764855c1c2aa0543cbe9f480f8feefec76c49d9e2c7bb2d8e38c89e9430f53f8b593d93f143c45985a56a5a32c736128214c95eda","services":["streamer"],"enable_msg_events":true,"port":63448},"up":true}},{"node":{"config":{"id":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","private_key":"30cd694a144038e53930a6b5e32f95f2c9caa886829d241c282f385107d7b824","name":"node_315b8a1711f680c2eaa607e35ef7a9e19c2b61721af8a7e08f1325dde1fe33cf1f47b3cc4acf12d541631041cf1b42754875fd1c9b66ee32797b72b9f7711256","services":["streamer"],"enable_msg_events":true,"port":63449},"up":true}},{"node":{"config":{"id":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","private_key":"c1480926443179b2e410d0303830b51d36dcce107b45da1494d87d2bcfb4f2dc","name":"node_396c703d27bfdddf91761f134cbf665fe61e0b3a5d24036a4105a625f72e8c28b821e9200f20d6e8f8b3ed6b95bda6b8accdd840c2f786720b7431e3af2af981","services":["streamer"],"enable_msg_events":true,"port":63450},"up":true}},{"node":{"config":{"id":"b7aa334a49788734068ac73e4b11bc554776217500c24eb76a85fe02f55ef037","private_key":"e91e000d37d0a336fb7eaab8ca7baa4db177108f2140ee86f7bef5959b617e13","name":"node_cd8815d66aee8bd488d4e5e25f0cc9c71cc65349f701ea26b1d6cd9c039d16fcb473dbf270a3b33187720ccf4b0ce4f78a6d6edd05f4fe804772d5b6ec45bbd3","services":["streamer"],"enable_msg_events":true,"port":63451},"up":true}},{"node":{"config":{"id":"83d0ffd43e2469b9c3498862d0186e413cc4d205f7854bf9dd73c4d74379217c","private_key":"991b1ea8ca82edeaa0c2d410b9cc297064d8d6668b9c8fb3277a6e7a934bbdcc","name":"node_7174eca109a1f717653918224c8cfe1b7fbd666405196760913593401eb79f2c7e41003b01f5a458940695b27a4673443c7621a169db7fd968994e552995dbbb","services":["streamer"],"enable_msg_events":true,"port":63452},"up":true}},{"node":{"config":{"id":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","private_key":"0c19513041b301f0745e678aa627a44b7cc58099c6616d5fed0f4e4774c5378e","name":"node_59b53f8a562bee519276e0eeccfcf8c975d3102f5d0cccf5abcce291a8a5bb817c04604f123686177a50a03b9d7559ac469962d0159d1d93e4ce58a304d04b55","services":["streamer"],"enable_msg_events":true,"port":63453},"up":true}},{"node":{"config":{"id":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","private_key":"c4406e8f5a2cc5a62bb4c36a3c52cd7831b3a82f06cc18e2286589a6e76a68eb","name":"node_f915e2398aa5d4370de87504dd76c21a7d0be2581c2ab55b7a47743f1bbaef36384eaacec22760f1e914ceac9d96a10c3d66eb27475ef444e77fee979e6e88cb","services":["streamer"],"enable_msg_events":true,"port":63454},"up":true}},{"node":{"config":{"id":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","private_key":"770f973cf2fda14f46f1e91ae608e0b87e0f06b2f8935bba64a43c6998737b9d","name":"node_1b6cad4f9851cc584c5d1405f976bb51a52e27ef4525847df543dcca65ca5eb1ef229dcc68ccbaaf00ab1e044cb5990dd213c805bdb4adcceb6fa70b5c61fdb4","services":["streamer"],"enable_msg_events":true,"port":63455},"up":true}},{"node":{"config":{"id":"c7fa3f5dd856ac0cd8fc3f2933180cfe1e605b31c630731f3e0a00d392f4097a","private_key":"2e741e89b7e631818e4edeaee9d795f912805724f2eaaa83e73a3a9e660da435","name":"node_ae2a996d3b562cb47a564934037949da83b1bfe194c23754f3dbe2372ec27654f4f3b6cb7f394570f308025589c065870477667e5560dbe5a8ea1625740699cf","services":["streamer"],"enable_msg_events":true,"port":63456},"up":true}},{"node":{"config":{"id":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","private_key":"c8d2153c48467773449a0c055a3527062b7ed043e491b6a81c6285b5bea823fe","name":"node_ddaf3f8478501cda872c056c72063eb64d5b871fc5fff70704872802289dd2d348f73bc342b7f148a90cfaae2448ebf16fea196be9df891dc71a57fb410d59dd","services":["streamer"],"enable_msg_events":true,"port":63457},"up":true}},{"node":{"config":{"id":"510c70916948815cd32759c451515bb024e38e0db8bb128710396978dc705a67","private_key":"784f624bd696dc1d6afb5bb977c5e6e76ab280e55c95949a68349a62fcc32c70","name":"node_5eccdbcb15ef42dc96abcbcd8b8db75d9230de10e3c650620737f6cc04ba4fdbf59ded5daa801901c191be186c329ebcd37ba83bcf794064ad4e11bf10d3f775","services":["streamer"],"enable_msg_events":true,"port":63458},"up":true}},{"node":{"config":{"id":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","private_key":"f4e049b715346f2e7ac1e31341d0df32e7ba9ae1ab6d9165b7407873add289cd","name":"node_8dd8ad560af84bec579cadb33d51711aa61b6ed22705010336df5a71a94591173b25e869457429c4a199b205fce8e73530e28203e18b971c2001715e610f152a","services":["streamer"],"enable_msg_events":true,"port":63459},"up":true}},{"node":{"config":{"id":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","private_key":"98e1264b7003ea79b1e799c87cb3c8e1aab2ea2e68531cf41f093c4b9f6a98a2","name":"node_28d3232c43de54ffdaf08f97b334c012738648b04e2876c7e751a2e352aaf4d550d6efa976c5a8edbfa8e88b9d04a0fca492b81c3756ef673ab74d34182ca711","services":["streamer"],"enable_msg_events":true,"port":63460},"up":true}},{"node":{"config":{"id":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","private_key":"357e1ab3ea555b0928cf10ec54d8842dc9c0b24a90fbe32ad1a819c018b0c26a","name":"node_be514f5f17f3177235acb24112b578edf0e373c469073226e547d18080905fd062d572be18e943f13bf9fcf5871e89ceb630a1b848a0e370ef8ef8c6d997a363","services":["streamer"],"enable_msg_events":true,"port":63461},"up":true}},{"node":{"config":{"id":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","private_key":"ad08dbea349545a36c8160aa460902c7d2744218b8ff5b8af229a17675560755","name":"node_66d0c5da973d9dd65350ae1ec63776872cdb7ded6afb6f51ae82259f6fd35e884375d15de290625f02ed2e5cdc3f591decb45e5fc64d5b0f835a216fd196f5a3","services":["streamer"],"enable_msg_events":true,"port":63462},"up":true}},{"node":{"config":{"id":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","private_key":"8e27f11572dc1832939cc80ada2ce9604fb91d0633410f87cd7bf68e4629b32d","name":"node_2f360209383d6de4ebd3b4abaf77796520b8654270b03f2151eac4a01b4417d7e0668c1f9ad30c20ff49aa8a0ab33dba5e6095619d1cf04b7af841722ab773da","services":["streamer"],"enable_msg_events":true,"port":63463},"up":true}},{"node":{"config":{"id":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","private_key":"4a7cb437d9c60831cf5e8c6d02e0f62205c1a2de83d9879d1d82ecb6e9b99031","name":"node_b35f418c7935cc54278e90978c3ef1521ab91daeaf9f1ebc1cd25211611ade091cac4e1f009d71059279d507322a4536501980ac0dd44caddc52fd4c593879f7","services":["streamer"],"enable_msg_events":true,"port":63464},"up":true}},{"node":{"config":{"id":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","private_key":"18216d0babc8a162ed03e1c866e0e90d0ea5fd5c2c2ea3f411886de38bf58640","name":"node_fe8ac5d33cd8ab6a1dad9567cdfb89b431a5c4f4a584fc608b92e7dfd06f70988e71e2f11f9fda9ee797c9ffba83bc085939fdf2522dde39f28202be92650566","services":["streamer"],"enable_msg_events":true,"port":63465},"up":true}},{"node":{"config":{"id":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","private_key":"bb317b572ed4481d33b6a67a267264366929dc0e2c12ade71a203b8b3159be03","name":"node_352407b42726dcd1aacc46a912ed8124b8b6a3da4e33dab742e40aa2452953d35dc8f42eddc76695ae2317e9f15dde2cd21be4ce826934695ad1847455661d1b","services":["streamer"],"enable_msg_events":true,"port":63466},"up":true}},{"node":{"config":{"id":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","private_key":"bd9c51ff7ece28d9a9808386358197110aa9a2a7d4715cd033a94c4d1d54ee76","name":"node_b137cf2291b530a39516d882200848e7f27a2710e0839b4d25b4f367bea6a0c3aaef8c7c7851c00a2945adcd819ea0508610afc1f453b8bc512b7b4d4faacdf3","services":["streamer"],"enable_msg_events":true,"port":63467},"up":true}},{"node":{"config":{"id":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","private_key":"6113b0494786bde04defd28e36030dad5b4368563b7b6b63c08475c18980a380","name":"node_1b063741c76fe65e62b4eae42751319d0cce66f22dde9f1cf83d6cca249a0e65bc7f6f22597697362fadd226222fb3847f8e146961969d1b4c9f7e475389cd8e","services":["streamer"],"enable_msg_events":true,"port":63468},"up":true}},{"node":{"config":{"id":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","private_key":"9875ab2bbcb806524e7fca84b6aba663cbbbec2d533a47e2df6f3fc85e1d8e55","name":"node_657d5370319128d24d925b41065eb619409d63ec6280975d78877a4028287463f6442f5206dd703569a05b2b542ad8bbb99803da265408208d6356a26d283d08","services":["streamer"],"enable_msg_events":true,"port":63469},"up":true}},{"node":{"config":{"id":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","private_key":"43de756eb52b963762bf62085de3293461cb2c53dd70baced6e3c86981545add","name":"node_a127faa19dcb7da585983d36a8e9d3e9a44a6db68507b60c70da71011f44ce377944802f39cd65f5a705cd8249ac1ef7ed8fd699349098f3f8e23b37592c80ae","services":["streamer"],"enable_msg_events":true,"port":63470},"up":true}},{"node":{"config":{"id":"3c5ae3bb462b109222b4a1ac1cf9b04508fcd53a2d13796880965e99e0216ee8","private_key":"af731618471023aff01cd45c69001d7fa95752c4d42c68df95eab820bb70bff5","name":"node_2e42f845d0393d1682add54ee30b60a953ed283e75d347bc6719ae0e91f6676282e7960f1fd52e5803d6b341fb3582c724b4f9cfd7bce9ce85f13ca480c7e72e","services":["streamer"],"enable_msg_events":true,"port":63471},"up":true}},{"node":{"config":{"id":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","private_key":"3444df2021104f7c577b25a23c3fc0231efb2cae1375cbae67db5602cbbe13d9","name":"node_6b01079463ec2f649db8634cb7a596261c79a8f714d954c772528e0547b017f8ce405e4a0ea9d32270848180e0f77b8c0b022cf95c85e777626edd4d24ea21d2","services":["streamer"],"enable_msg_events":true,"port":63472},"up":true}},{"node":{"config":{"id":"3ab20836f88aab49491e82cfd1f1e8a59b5794cf91644b663d40cb22c5460be4","private_key":"638383a2262ab3e1cb5a1cdf134fb5a0077f21e3bf90118cc03311db43b1cf0a","name":"node_2245f3bf1a3b4e92f912affc75f757cf648b20474c11ffe947884f216b8f24f9dcb6e6a0358a4061b8517478ec15027e185b2f05a777573b4940375bf4aaf2be","services":["streamer"],"enable_msg_events":true,"port":63473},"up":true}},{"node":{"config":{"id":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","private_key":"269f2cacd5854ad6634cd178738e910a093dbfb55bcf42ecd1f1d621f5829499","name":"node_d4b7c787981abec24353c7d496661692233797e3758189bf909a69fe87935bf6da0940daa4f12e81f20c5e88f70b472113688b71a0ae2f91a73ee0e6ff4c8591","services":["streamer"],"enable_msg_events":true,"port":63474},"up":true}},{"node":{"config":{"id":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","private_key":"1f5c7e0be8cb7bacb5c28bf9283884761fcbe8c6b353b5c821d204938da6aab3","name":"node_9c30ad8c85adc7bb52ddcfab2e661cc3a854a689eaa59128d35129d02cd5b0f8da4a44cdd33934a8e2c8fab1760c16d6f2e1c5eb227dc819e85624e63343c069","services":["streamer"],"enable_msg_events":true,"port":63475},"up":true}},{"node":{"config":{"id":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","private_key":"975ccce3cea6896e2dc7fb2fb8399b56deea3eac98f96dfe331602f06386ebe2","name":"node_c8b0dba45104fc7c7dff3ce078a0d3f5ade6198bb4656e2bee8076387929133e5ef2bd293b130a9d4f6a6974ca17e8b4a8f406d1219880f1e0f99c65c63af5a4","services":["streamer"],"enable_msg_events":true,"port":63476},"up":true}},{"node":{"config":{"id":"6f1c48453e1bfb9c474d7f888d8c437c75db569e2c900867c4eaf3e6504847aa","private_key":"381480a705e57052adb222beb50053c41122d1c2c10b111a16abfc01e2314d28","name":"node_ff1e34fd51953ed641b431aa657da21cb4c81d23e4eaa13c9cce526f1f1b34593d41f9b87a0e10387fe400c623638677c0efab15dbd1014a0ccc11f9ec92c417","services":["streamer"],"enable_msg_events":true,"port":63477},"up":true}},{"node":{"config":{"id":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","private_key":"22c94340e76bf6f9d70e933f2b3cc96d24f64282bc3d4a61621712e78500fead","name":"node_a78f18691e0dbf3c08fe9d7dec188e6a05a93833432fb99eb2b3924ab0bde7a44db2a102a65544bbdc4fb61bc3a3d569230d9843a335bf619167fb045d78ab3c","services":["streamer"],"enable_msg_events":true,"port":63478},"up":true}},{"node":{"config":{"id":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","private_key":"9b0568309c2ef3f512d43d9016f801f55741346b5f56da355dfe5dc54d652821","name":"node_e67e495385e7cacfe659b40d1ba94d4b1ff55eada70a4d9740a9e452fbf221672bd0a897b940e2a6bed71e2a16867fe45bf0ea0015435ac6d815690e263b1899","services":["streamer"],"enable_msg_events":true,"port":63479},"up":true}},{"node":{"config":{"id":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","private_key":"8ee8a33f33bc96603d61297c73e11d545ac229d887345e12f9fe834c7532e648","name":"node_aacff366e601e072a2113add9020e19b6612f3e85b6e607fdc8cc1b6991f43be0b1f835ed3dc41d1f2b3e0caf7fac252c8eb4c150667d48207fa92ea5f0a7455","services":["streamer"],"enable_msg_events":true,"port":63480},"up":true}},{"node":{"config":{"id":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","private_key":"105fa3b2d400eefda08235ac7c0db7af48667dd5a51b8d001d39e06753e93872","name":"node_2f903b2fd4f9432b7d91b5c3e4c7ee3cb9c56fe7efc4baff0edd06fc75e4b7492f2c3c929755746df59c9f95d437a12ba40ac138d08f5bc7aa091f49f3b852c2","services":["streamer"],"enable_msg_events":true,"port":63481},"up":true}}],"conns":[{"one":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","other":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","up":false},{"one":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","other":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","up":true},{"one":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","other":"34e48bd6562a308ccf6a4a0257f253e908e97d16defe2f1a31e046d5558a4769","up":true},{"one":"34e48bd6562a308ccf6a4a0257f253e908e97d16defe2f1a31e046d5558a4769","other":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","up":true},{"one":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","other":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","up":true},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","up":false},{"one":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","other":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","up":true},{"one":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","other":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","up":true},{"one":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","other":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","up":true},{"one":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","other":"9a09aa168f7f4ebd90652d6900ace1ba88b8785290b2687783d4a23e9bdb90ee","up":true},{"one":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","other":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","up":true},{"one":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","other":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","up":true},{"one":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","other":"7ec4083eca4749caa706ac08afc0c91cdcec42f18e4d360f18695a79de362115","up":true},{"one":"7ec4083eca4749caa706ac08afc0c91cdcec42f18e4d360f18695a79de362115","other":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","up":false},{"one":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","other":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","up":true},{"one":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","other":"d56025bb110210216fdc8f86ad205607c6fb4744d3d444931aa17512a854356a","up":true},{"one":"d56025bb110210216fdc8f86ad205607c6fb4744d3d444931aa17512a854356a","other":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","up":true},{"one":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","other":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","up":true},{"one":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","other":"b40da884b5e0ab429b3608ce6030c0f7f542d008713ed1a444c70d9c00989ecf","up":true},{"one":"b40da884b5e0ab429b3608ce6030c0f7f542d008713ed1a444c70d9c00989ecf","other":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","up":true},{"one":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","other":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","up":true},{"one":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","other":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","up":true},{"one":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","other":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","up":true},{"one":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","other":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","up":true},{"one":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","other":"307c36a947f5c4c597075d8346a5db6133fbd2c14da099f85c1d2b7d6d8331f2","up":true},{"one":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","other":"aa7876d43dd0ce40f421f68c0d13c8c718d7a7a5e7c567dd20d87d19934cdc05","up":true},{"one":"307c36a947f5c4c597075d8346a5db6133fbd2c14da099f85c1d2b7d6d8331f2","other":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","up":false},{"one":"aa7876d43dd0ce40f421f68c0d13c8c718d7a7a5e7c567dd20d87d19934cdc05","other":"715b38eca0c630aad73e5f7d65453c6666f459994e3cc4bba586af75d78e8fe4","up":true},{"one":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","other":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","up":true},{"one":"715b38eca0c630aad73e5f7d65453c6666f459994e3cc4bba586af75d78e8fe4","other":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","up":false},{"one":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","other":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","up":true},{"one":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","other":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","up":true},{"one":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","other":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","up":true},{"one":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","other":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","up":true},{"one":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","other":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","up":true},{"one":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","other":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","up":false},{"one":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","other":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","up":true},{"one":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","other":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","up":true},{"one":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","other":"3388123bad3bf1debf51c5921c09d06f7627d05f6115af39d92dbe9b2f2313dc","up":true},{"one":"3388123bad3bf1debf51c5921c09d06f7627d05f6115af39d92dbe9b2f2313dc","other":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","up":true},{"one":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","other":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","up":true},{"one":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","other":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","up":false},{"one":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","other":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","up":true},{"one":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","other":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","up":true},{"one":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","other":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","up":true},{"one":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","other":"6701b38360d0a72ddc355c17385af34b7ed9eeec00bc3867e162b5571af69e3f","up":true},{"one":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","other":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","up":true},{"one":"6701b38360d0a72ddc355c17385af34b7ed9eeec00bc3867e162b5571af69e3f","other":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","up":true},{"one":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","other":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","up":true},{"one":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","other":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","up":true},{"one":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","other":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","up":true},{"one":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","other":"e4e95f8381568c1cba7e8f64dd710554b923241a1c988dddb42a2cd96434705f","up":true},{"one":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","other":"8ea97f6abd40d70717688c5dd38daf6f6b12005df21ae397dfb700fbfe3447ac","up":true},{"one":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","other":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","up":true},{"one":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","other":"af8fb611627063b408d2b96c1ab68fafb223a86d947de3dac3a59649cd13e0cf","up":true},{"one":"0020768d96fca0cf47a9632ca25ff794c996042f07a7242ec7c70fbc75e0e45e","other":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","up":true},{"one":"af8fb611627063b408d2b96c1ab68fafb223a86d947de3dac3a59649cd13e0cf","other":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","up":true},{"one":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","other":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","up":true},{"one":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","other":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","up":true},{"one":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","other":"85642219f8bb8ce048cfce5aecddc8acd035afa035a0137c354f5b6fc3e3889d","up":true},{"one":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","other":"07a6873c7eadb651196a76ab30241cc1c984549c5086f1c3bb1be7bd493346be","up":true},{"one":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","other":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","up":true},{"one":"07a6873c7eadb651196a76ab30241cc1c984549c5086f1c3bb1be7bd493346be","other":"020f57c369b6521552f7498e444aed21ac57832ed3ffaa7b45f607793ea1a86c","up":true},{"one":"413b15ae74e38b8610dd5edd52e05630f8acfa4139a1b361775afa8e748ebbfc","other":"e54d69f3bb061c62dede6e5a8704700bf9b8063edead7c755c9586f4d96754f1","up":true},{"one":"020f57c369b6521552f7498e444aed21ac57832ed3ffaa7b45f607793ea1a86c","other":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","up":true},{"one":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","other":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","up":true},{"one":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","other":"03059077d3fadf62794756f6947fc32ed00d82162dbe3e6ebcd32952bd421321","up":true},{"one":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","other":"48ec978577e2dbf0448ab119a3821be5d25dbf5a295fd4f695b9311429538a26","up":true},{"one":"03059077d3fadf62794756f6947fc32ed00d82162dbe3e6ebcd32952bd421321","other":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","up":true},{"one":"48ec978577e2dbf0448ab119a3821be5d25dbf5a295fd4f695b9311429538a26","other":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","up":true},{"one":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","other":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","up":true},{"one":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","other":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","up":true},{"one":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","other":"1587ed904df26ed711e2235fea41e5812a7d33ce5dab2ebb4e88515b18f43d6e","up":true},{"one":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","other":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","up":true},{"one":"e54d69f3bb061c62dede6e5a8704700bf9b8063edead7c755c9586f4d96754f1","other":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","up":true},{"one":"85642219f8bb8ce048cfce5aecddc8acd035afa035a0137c354f5b6fc3e3889d","other":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","up":true},{"one":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","other":"413b15ae74e38b8610dd5edd52e05630f8acfa4139a1b361775afa8e748ebbfc","up":true},{"one":"8ea97f6abd40d70717688c5dd38daf6f6b12005df21ae397dfb700fbfe3447ac","other":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","up":true},{"one":"e4e95f8381568c1cba7e8f64dd710554b923241a1c988dddb42a2cd96434705f","other":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","up":true},{"one":"9a09aa168f7f4ebd90652d6900ace1ba88b8785290b2687783d4a23e9bdb90ee","other":"5b308b25cfe30d0b68784dc73be464dfa85a8e49ddd6f6271ff0ee2b63798928","up":true},{"one":"5b308b25cfe30d0b68784dc73be464dfa85a8e49ddd6f6271ff0ee2b63798928","other":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","up":true},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","up":true},{"one":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","other":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","up":true},{"one":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","other":"0020768d96fca0cf47a9632ca25ff794c996042f07a7242ec7c70fbc75e0e45e","up":true},{"one":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","other":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","up":true},{"one":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","other":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","up":true},{"one":"1587ed904df26ed711e2235fea41e5812a7d33ce5dab2ebb4e88515b18f43d6e","other":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","up":true},{"one":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","other":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","up":true},{"one":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","other":"507060be030344850df6fc76719d503254b38d96d77eb25f49ca9d955944b10a","up":true},{"one":"507060be030344850df6fc76719d503254b38d96d77eb25f49ca9d955944b10a","other":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","up":true},{"one":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","other":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","up":true},{"one":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","other":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","up":true},{"one":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","other":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","up":true},{"one":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","other":"802f9e88535e40c179cb5f7d2a16500696e2af2115d3f7ca0cac5c2f634109c2","up":true},{"one":"802f9e88535e40c179cb5f7d2a16500696e2af2115d3f7ca0cac5c2f634109c2","other":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","up":true},{"one":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","other":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","up":true},{"one":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","other":"a011c924104e072ec415cf72255167582e5d3f189f541066ba88b3ae0041b508","up":true},{"one":"a011c924104e072ec415cf72255167582e5d3f189f541066ba88b3ae0041b508","other":"208e648a32b59b77c2abbeac0402c8d009485a19dd4a04a216cd91d0c6713929","up":true},{"one":"208e648a32b59b77c2abbeac0402c8d009485a19dd4a04a216cd91d0c6713929","other":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","up":true},{"one":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","other":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","up":true},{"one":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","other":"df52d5de0f5e69e7e4c9e0ffcec1f9df86d38044bb57e5a0cb18bde354ecb66d","up":true},{"one":"df52d5de0f5e69e7e4c9e0ffcec1f9df86d38044bb57e5a0cb18bde354ecb66d","other":"4b8acc021ddea1fff036b4b0e5939c38507ef792490701f7b341e4ff10a78567","up":true},{"one":"4b8acc021ddea1fff036b4b0e5939c38507ef792490701f7b341e4ff10a78567","other":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","up":true},{"one":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","other":"d777e45233aa4d8b723f6a6aa9cbf5de4c0b19f6fd00af6d538b67ec01b74ab5","up":true},{"one":"d777e45233aa4d8b723f6a6aa9cbf5de4c0b19f6fd00af6d538b67ec01b74ab5","other":"d96e430326c7fc91fcd19c4e85b472a41ba24317ff09e5bd8eb2bd7a306499be","up":true},{"one":"d96e430326c7fc91fcd19c4e85b472a41ba24317ff09e5bd8eb2bd7a306499be","other":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","up":true},{"one":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","other":"bb3198bb61f3dc96365a99b7141c0b85720614de4640afa76458d27484e1353a","up":true},{"one":"bb3198bb61f3dc96365a99b7141c0b85720614de4640afa76458d27484e1353a","other":"f1ad1843560ead294e9d60c9bfc99d8a827f2cfe557e0f9fe7ebf82aec56926d","up":true},{"one":"f1ad1843560ead294e9d60c9bfc99d8a827f2cfe557e0f9fe7ebf82aec56926d","other":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","up":true},{"one":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","other":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","up":true},{"one":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","other":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","up":true},{"one":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","other":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","up":true},{"one":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","other":"d0b76e826a6eaeabdc060a0588bd1becc1901b60a8eb4e8adbe61107883a2987","up":true},{"one":"d0b76e826a6eaeabdc060a0588bd1becc1901b60a8eb4e8adbe61107883a2987","other":"f0b803dcfec89e1d68739ca05cf0c9c04e80a2b1cd47496a83168a71184fd4cf","up":true},{"one":"f0b803dcfec89e1d68739ca05cf0c9c04e80a2b1cd47496a83168a71184fd4cf","other":"48df69314a57a6ab2fc31108104aae2242ad2a941d2f87119a151250344aee02","up":true},{"one":"48df69314a57a6ab2fc31108104aae2242ad2a941d2f87119a151250344aee02","other":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","up":false},{"one":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","other":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","up":true},{"one":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","other":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","up":true},{"one":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","other":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","up":true},{"one":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","other":"6923d78400f277fe29c35ba38a416edd7c960cc75d94c35aa3446b4d5e2f6c3b","up":true},{"one":"6923d78400f277fe29c35ba38a416edd7c960cc75d94c35aa3446b4d5e2f6c3b","other":"b820b318a3a12a7a7f45d06fff7f5d7e3ca2ca8097c396c1d76c735e1c915cc0","up":true},{"one":"b820b318a3a12a7a7f45d06fff7f5d7e3ca2ca8097c396c1d76c735e1c915cc0","other":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","up":true},{"one":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","other":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","up":true},{"one":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","other":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","up":true},{"one":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","other":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","up":true},{"one":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","other":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","up":true},{"one":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","other":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","up":false},{"one":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","other":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","up":true},{"one":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","other":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","up":true},{"one":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","other":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","up":false},{"one":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","other":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","up":true},{"one":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","other":"b7aa334a49788734068ac73e4b11bc554776217500c24eb76a85fe02f55ef037","up":true},{"one":"b7aa334a49788734068ac73e4b11bc554776217500c24eb76a85fe02f55ef037","other":"83d0ffd43e2469b9c3498862d0186e413cc4d205f7854bf9dd73c4d74379217c","up":true},{"one":"83d0ffd43e2469b9c3498862d0186e413cc4d205f7854bf9dd73c4d74379217c","other":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","up":true},{"one":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","other":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","up":true},{"one":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","other":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","up":true},{"one":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","other":"c7fa3f5dd856ac0cd8fc3f2933180cfe1e605b31c630731f3e0a00d392f4097a","up":true},{"one":"c7fa3f5dd856ac0cd8fc3f2933180cfe1e605b31c630731f3e0a00d392f4097a","other":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","up":true},{"one":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","other":"510c70916948815cd32759c451515bb024e38e0db8bb128710396978dc705a67","up":true},{"one":"510c70916948815cd32759c451515bb024e38e0db8bb128710396978dc705a67","other":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","up":true},{"one":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","other":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","up":true},{"one":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","other":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","up":false},{"one":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","other":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","up":true},{"one":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","other":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","up":true},{"one":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","other":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","up":false},{"one":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","other":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","up":true},{"one":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","other":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","up":false},{"one":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","other":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","up":true},{"one":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","other":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","up":true},{"one":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","other":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","up":true},{"one":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","other":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","up":true},{"one":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","other":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","up":true},{"one":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","other":"3c5ae3bb462b109222b4a1ac1cf9b04508fcd53a2d13796880965e99e0216ee8","up":true},{"one":"3c5ae3bb462b109222b4a1ac1cf9b04508fcd53a2d13796880965e99e0216ee8","other":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","up":true},{"one":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","other":"3ab20836f88aab49491e82cfd1f1e8a59b5794cf91644b663d40cb22c5460be4","up":true},{"one":"3ab20836f88aab49491e82cfd1f1e8a59b5794cf91644b663d40cb22c5460be4","other":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","up":true},{"one":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","other":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","up":true},{"one":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","other":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","up":false},{"one":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","other":"6f1c48453e1bfb9c474d7f888d8c437c75db569e2c900867c4eaf3e6504847aa","up":true},{"one":"6f1c48453e1bfb9c474d7f888d8c437c75db569e2c900867c4eaf3e6504847aa","other":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","up":true},{"one":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","other":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","up":true},{"one":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","other":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","up":true},{"one":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","other":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","up":true},{"one":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","other":"0a3fe6516edff33cde6afc46c6174a8562f17454b6f3ad41be8c12ebf1b0a07d","up":true},{"one":"0a3fe6516edff33cde6afc46c6174a8562f17454b6f3ad41be8c12ebf1b0a07d","other":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","up":false},{"one":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","other":"e1909ff70b17f666d74c73e8d9803df1e6e20733e4aeecdc78e444b6632d92c9","up":true},{"one":"e1909ff70b17f666d74c73e8d9803df1e6e20733e4aeecdc78e444b6632d92c9","other":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","up":true},{"one":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","other":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","up":true},{"one":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","other":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","up":true},{"one":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","other":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","up":true},{"one":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","other":"3ef6bb1dcd37360fe4a463b70add6cd4ba112a293112911b2304ab05b4c1b543","up":true},{"one":"3ef6bb1dcd37360fe4a463b70add6cd4ba112a293112911b2304ab05b4c1b543","other":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","up":true},{"one":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","other":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","up":true},{"one":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","other":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","up":true},{"one":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","other":"67ee67b8f4d3ee50d8cdd027ba96e4b2f8ef9017bac217bd5a2d9dca35325519","up":true},{"one":"67ee67b8f4d3ee50d8cdd027ba96e4b2f8ef9017bac217bd5a2d9dca35325519","other":"7aef1625409f479ee1abad565f8d7e73b17714e77f54d1ac1b2fe80382a058cc","up":true},{"one":"7aef1625409f479ee1abad565f8d7e73b17714e77f54d1ac1b2fe80382a058cc","other":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","up":false},{"one":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","other":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","up":false},{"one":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","other":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","up":false},{"one":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","other":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","up":true},{"one":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","other":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","up":true},{"one":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","other":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","up":true},{"one":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","other":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","up":true},{"one":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","other":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","up":true},{"one":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","other":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","up":true},{"one":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","other":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","up":true},{"one":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","other":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","up":true},{"one":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","other":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","up":true},{"one":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","other":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","up":true},{"one":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","other":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","up":true},{"one":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","other":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","up":true},{"one":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","other":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","up":true},{"one":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","other":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","up":true},{"one":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","other":"1a8ecbde88be22a9bbb5f3ef19be5377923d655fd9cd838f9d08a442614742c3","up":true},{"one":"1a8ecbde88be22a9bbb5f3ef19be5377923d655fd9cd838f9d08a442614742c3","other":"05a4778e1be7da949c1a7bffa3408020b66b4ee3c98e1f62e35d212c77e83ada","up":true},{"one":"05a4778e1be7da949c1a7bffa3408020b66b4ee3c98e1f62e35d212c77e83ada","other":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","up":true},{"one":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","other":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","up":true},{"one":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","other":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","up":true},{"one":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","other":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","up":true},{"one":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","other":"d60644fb69edf1d360a5b3d480030df97600a3d94a189da98b58c43758c98331","up":true},{"one":"d60644fb69edf1d360a5b3d480030df97600a3d94a189da98b58c43758c98331","other":"69d8801f9f19fec51b636cc27fca2c93e67dac8f3650aea35259d01293d5c9e6","up":true},{"one":"69d8801f9f19fec51b636cc27fca2c93e67dac8f3650aea35259d01293d5c9e6","other":"96419a246f5ca44f2796d91ccc55b3ac477e1754e74d202cdd133e6b5b96051b","up":true},{"one":"96419a246f5ca44f2796d91ccc55b3ac477e1754e74d202cdd133e6b5b96051b","other":"e2554d1bf145ec0e7027ca8e8ba0c85fc697bc4b54e36a6cdc53f1efa37a6634","up":true},{"one":"e2554d1bf145ec0e7027ca8e8ba0c85fc697bc4b54e36a6cdc53f1efa37a6634","other":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","up":true},{"one":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","other":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","up":true},{"one":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","other":"0c728c174a25e11bc171d7d35eaaa3974859a4dbf7a64e1d9c9da6b3ad63b21a","up":true},{"one":"0c728c174a25e11bc171d7d35eaaa3974859a4dbf7a64e1d9c9da6b3ad63b21a","other":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","up":true},{"one":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","other":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","up":true},{"one":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","other":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","up":true},{"one":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","other":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","up":true},{"one":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","other":"f2f5e82cf81f07f8cf82c7171c5ee5a42b7963619fafd4295137357cea1b7bb0","up":true},{"one":"f2f5e82cf81f07f8cf82c7171c5ee5a42b7963619fafd4295137357cea1b7bb0","other":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","up":true},{"one":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","other":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","up":true},{"one":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","other":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","up":true},{"one":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","other":"1953df7e29813b00b0137118623b6ce34113d17d4f9aaa608b81f2ee58ea3fe3","up":true},{"one":"1953df7e29813b00b0137118623b6ce34113d17d4f9aaa608b81f2ee58ea3fe3","other":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","up":true},{"one":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","other":"1254b1e5c9e80b367520bfefc35cf32a60aff56c48315107c4b07aa1a7866af9","up":true},{"one":"1254b1e5c9e80b367520bfefc35cf32a60aff56c48315107c4b07aa1a7866af9","other":"1a85e13e2ad82c8ffa10ad52b382947e638036120188ae611fb1fb333cb5232b","up":true},{"one":"1a85e13e2ad82c8ffa10ad52b382947e638036120188ae611fb1fb333cb5232b","other":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","up":true},{"one":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","other":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","up":true},{"one":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","other":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","up":true},{"one":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","other":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","up":true},{"one":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","other":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","up":true},{"one":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","other":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","up":true},{"one":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","other":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","up":true},{"one":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","other":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","up":true},{"one":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","other":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","up":true},{"one":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","other":"57657e7b7d2642c6e5daefd27000d0da41982491486fd1aee57d0f99f072a471","up":true},{"one":"57657e7b7d2642c6e5daefd27000d0da41982491486fd1aee57d0f99f072a471","other":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","up":true},{"one":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","other":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","up":true},{"one":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","other":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","up":true},{"one":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","other":"41b2b713dba3a11683ad83d1cb4beb9ff4f5aaf49f2ad1ac2247c84a85d40a56","up":true},{"one":"41b2b713dba3a11683ad83d1cb4beb9ff4f5aaf49f2ad1ac2247c84a85d40a56","other":"574101500f64e9a1bfbf31f4f379a60a0ea9bee449af1cd29962214dd960b3c6","up":true},{"one":"574101500f64e9a1bfbf31f4f379a60a0ea9bee449af1cd29962214dd960b3c6","other":"d0443f24523af0eec6c20b21d84b2d54cb0ba8d4d1a205b41328bb22e8d3cd63","up":true},{"one":"d0443f24523af0eec6c20b21d84b2d54cb0ba8d4d1a205b41328bb22e8d3cd63","other":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","up":true},{"one":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","other":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","up":true},{"one":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","other":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","up":true},{"one":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","other":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","up":true},{"one":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","other":"c95a481b19583fc85e9b079fa0b5cf51eb46e71b9f57fc6df2e3b78389d8de2f","up":true},{"one":"c95a481b19583fc85e9b079fa0b5cf51eb46e71b9f57fc6df2e3b78389d8de2f","other":"91088ea79cd8f27da8879760dd21c363aa9be1135437c2b87464426874b597e5","up":true},{"one":"91088ea79cd8f27da8879760dd21c363aa9be1135437c2b87464426874b597e5","other":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","up":true},{"one":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","other":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","up":true},{"one":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","other":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","up":true},{"one":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","other":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","up":true},{"one":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","other":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","up":true},{"one":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","other":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","up":true},{"one":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","other":"bd39cdc673c4184193347f0efd74bb894f7f36a994c154ffe3dadcd456efe460","up":true},{"one":"bd39cdc673c4184193347f0efd74bb894f7f36a994c154ffe3dadcd456efe460","other":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","up":true},{"one":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","other":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","up":true},{"one":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","other":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","up":true},{"one":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","other":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","up":true},{"one":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","other":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","up":true},{"one":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","other":"294066bf056b1530308d096871b08a046086abcbc7ed7a0a97d9fb46cccd6003","up":true},{"one":"294066bf056b1530308d096871b08a046086abcbc7ed7a0a97d9fb46cccd6003","other":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","up":true},{"one":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","other":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","up":true},{"one":"aa7876d43dd0ce40f421f68c0d13c8c718d7a7a5e7c567dd20d87d19934cdc05","other":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","up":true},{"one":"af8fb611627063b408d2b96c1ab68fafb223a86d947de3dac3a59649cd13e0cf","other":"85642219f8bb8ce048cfce5aecddc8acd035afa035a0137c354f5b6fc3e3889d","up":true},{"one":"9a09aa168f7f4ebd90652d6900ace1ba88b8785290b2687783d4a23e9bdb90ee","other":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","up":true},{"one":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","other":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","up":true},{"one":"5b308b25cfe30d0b68784dc73be464dfa85a8e49ddd6f6271ff0ee2b63798928","other":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","up":true},{"one":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","other":"5b308b25cfe30d0b68784dc73be464dfa85a8e49ddd6f6271ff0ee2b63798928","up":true},{"one":"e54d69f3bb061c62dede6e5a8704700bf9b8063edead7c755c9586f4d96754f1","other":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","up":true},{"one":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","other":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","up":false},{"one":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","other":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","up":true},{"one":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","other":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","up":true},{"one":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","other":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","up":true},{"one":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","other":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","up":true},{"one":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","other":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","up":true},{"one":"1587ed904df26ed711e2235fea41e5812a7d33ce5dab2ebb4e88515b18f43d6e","other":"48ec978577e2dbf0448ab119a3821be5d25dbf5a295fd4f695b9311429538a26","up":true},{"one":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","other":"f0b803dcfec89e1d68739ca05cf0c9c04e80a2b1cd47496a83168a71184fd4cf","up":true},{"one":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","other":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","up":true},{"one":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","other":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","up":true},{"one":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","other":"8ea97f6abd40d70717688c5dd38daf6f6b12005df21ae397dfb700fbfe3447ac","up":true},{"one":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","other":"802f9e88535e40c179cb5f7d2a16500696e2af2115d3f7ca0cac5c2f634109c2","up":true},{"one":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","other":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","up":true},{"one":"07a6873c7eadb651196a76ab30241cc1c984549c5086f1c3bb1be7bd493346be","other":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","up":true},{"one":"020f57c369b6521552f7498e444aed21ac57832ed3ffaa7b45f607793ea1a86c","other":"03059077d3fadf62794756f6947fc32ed00d82162dbe3e6ebcd32952bd421321","up":true},{"one":"0020768d96fca0cf47a9632ca25ff794c996042f07a7242ec7c70fbc75e0e45e","other":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","up":true},{"one":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","other":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","up":true},{"one":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","other":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","up":true},{"one":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","other":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","up":true},{"one":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","other":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","up":true},{"one":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","other":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","up":true},{"one":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","other":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","up":true},{"one":"413b15ae74e38b8610dd5edd52e05630f8acfa4139a1b361775afa8e748ebbfc","other":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","up":true},{"one":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","other":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","up":true},{"one":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","other":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","up":true},{"one":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","other":"48df69314a57a6ab2fc31108104aae2242ad2a941d2f87119a151250344aee02","up":true},{"one":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","other":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","up":true},{"one":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","other":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","up":true},{"one":"b820b318a3a12a7a7f45d06fff7f5d7e3ca2ca8097c396c1d76c735e1c915cc0","other":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","up":true},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","up":true},{"one":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","other":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","up":true},{"one":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","other":"f1ad1843560ead294e9d60c9bfc99d8a827f2cfe557e0f9fe7ebf82aec56926d","up":true},{"one":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","other":"510c70916948815cd32759c451515bb024e38e0db8bb128710396978dc705a67","up":true},{"one":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","other":"6701b38360d0a72ddc355c17385af34b7ed9eeec00bc3867e162b5571af69e3f","up":false},{"one":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","other":"6701b38360d0a72ddc355c17385af34b7ed9eeec00bc3867e162b5571af69e3f","up":true},{"one":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","other":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","up":false},{"one":"48df69314a57a6ab2fc31108104aae2242ad2a941d2f87119a151250344aee02","other":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","up":true},{"one":"b7aa334a49788734068ac73e4b11bc554776217500c24eb76a85fe02f55ef037","other":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","up":true},{"one":"6701b38360d0a72ddc355c17385af34b7ed9eeec00bc3867e162b5571af69e3f","other":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","up":true},{"one":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","other":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","up":true},{"one":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","other":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","up":true},{"one":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","other":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","up":true},{"one":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","other":"510c70916948815cd32759c451515bb024e38e0db8bb128710396978dc705a67","up":true},{"one":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","other":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","up":true},{"one":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","other":"e4e95f8381568c1cba7e8f64dd710554b923241a1c988dddb42a2cd96434705f","up":true},{"one":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","other":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","up":true},{"one":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","other":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","up":true},{"one":"34e48bd6562a308ccf6a4a0257f253e908e97d16defe2f1a31e046d5558a4769","other":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","up":true},{"one":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","other":"34e48bd6562a308ccf6a4a0257f253e908e97d16defe2f1a31e046d5558a4769","up":true},{"one":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","other":"c7fa3f5dd856ac0cd8fc3f2933180cfe1e605b31c630731f3e0a00d392f4097a","up":true},{"one":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","other":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","up":true},{"one":"e4e95f8381568c1cba7e8f64dd710554b923241a1c988dddb42a2cd96434705f","other":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","up":true},{"one":"d0b76e826a6eaeabdc060a0588bd1becc1901b60a8eb4e8adbe61107883a2987","other":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","up":false},{"one":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","other":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","up":true},{"one":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","other":"3c5ae3bb462b109222b4a1ac1cf9b04508fcd53a2d13796880965e99e0216ee8","up":true},{"one":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","other":"413b15ae74e38b8610dd5edd52e05630f8acfa4139a1b361775afa8e748ebbfc","up":true},{"one":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","other":"3c5ae3bb462b109222b4a1ac1cf9b04508fcd53a2d13796880965e99e0216ee8","up":true},{"one":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","other":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","up":true},{"one":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","other":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","up":false},{"one":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","other":"307c36a947f5c4c597075d8346a5db6133fbd2c14da099f85c1d2b7d6d8331f2","up":true},{"one":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","other":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","up":true},{"one":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","other":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","up":true},{"one":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","other":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","up":true},{"one":"307c36a947f5c4c597075d8346a5db6133fbd2c14da099f85c1d2b7d6d8331f2","other":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","up":true},{"one":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","other":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","up":true},{"one":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","other":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","up":true},{"one":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","other":"3388123bad3bf1debf51c5921c09d06f7627d05f6115af39d92dbe9b2f2313dc","up":true},{"one":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","other":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","up":true},{"one":"a011c924104e072ec415cf72255167582e5d3f189f541066ba88b3ae0041b508","other":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","up":true},{"one":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","other":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","up":true},{"one":"3388123bad3bf1debf51c5921c09d06f7627d05f6115af39d92dbe9b2f2313dc","other":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","up":true},{"one":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","other":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","up":true},{"one":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","other":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","up":true},{"one":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","other":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","up":true},{"one":"83d0ffd43e2469b9c3498862d0186e413cc4d205f7854bf9dd73c4d74379217c","other":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","up":true},{"one":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","other":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","up":true},{"one":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","other":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","up":true},{"one":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","other":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","up":true},{"one":"df52d5de0f5e69e7e4c9e0ffcec1f9df86d38044bb57e5a0cb18bde354ecb66d","other":"d96e430326c7fc91fcd19c4e85b472a41ba24317ff09e5bd8eb2bd7a306499be","up":true},{"one":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","other":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","up":true},{"one":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","other":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","up":true},{"one":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","other":"1587ed904df26ed711e2235fea41e5812a7d33ce5dab2ebb4e88515b18f43d6e","up":true},{"one":"0a3fe6516edff33cde6afc46c6174a8562f17454b6f3ad41be8c12ebf1b0a07d","other":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","up":true},{"one":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","other":"e1909ff70b17f666d74c73e8d9803df1e6e20733e4aeecdc78e444b6632d92c9","up":true},{"one":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","other":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","up":true},{"one":"c7fa3f5dd856ac0cd8fc3f2933180cfe1e605b31c630731f3e0a00d392f4097a","other":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","up":true},{"one":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","other":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","up":true},{"one":"208e648a32b59b77c2abbeac0402c8d009485a19dd4a04a216cd91d0c6713929","other":"4b8acc021ddea1fff036b4b0e5939c38507ef792490701f7b341e4ff10a78567","up":true},{"one":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","other":"e1909ff70b17f666d74c73e8d9803df1e6e20733e4aeecdc78e444b6632d92c9","up":true},{"one":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","other":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","up":true},{"one":"e1909ff70b17f666d74c73e8d9803df1e6e20733e4aeecdc78e444b6632d92c9","other":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","up":true},{"one":"3ef6bb1dcd37360fe4a463b70add6cd4ba112a293112911b2304ab05b4c1b543","other":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","up":true},{"one":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","other":"e1909ff70b17f666d74c73e8d9803df1e6e20733e4aeecdc78e444b6632d92c9","up":true},{"one":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","other":"7aef1625409f479ee1abad565f8d7e73b17714e77f54d1ac1b2fe80382a058cc","up":true},{"one":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","other":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","up":true},{"one":"7ec4083eca4749caa706ac08afc0c91cdcec42f18e4d360f18695a79de362115","other":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","up":true},{"one":"507060be030344850df6fc76719d503254b38d96d77eb25f49ca9d955944b10a","other":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","up":true},{"one":"bb3198bb61f3dc96365a99b7141c0b85720614de4640afa76458d27484e1353a","other":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","up":false},{"one":"d56025bb110210216fdc8f86ad205607c6fb4744d3d444931aa17512a854356a","other":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","up":false},{"one":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","other":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","up":true},{"one":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","other":"7aef1625409f479ee1abad565f8d7e73b17714e77f54d1ac1b2fe80382a058cc","up":true},{"one":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","other":"7aef1625409f479ee1abad565f8d7e73b17714e77f54d1ac1b2fe80382a058cc","up":true},{"one":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","other":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","up":true},{"one":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","other":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","up":true},{"one":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","other":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","up":true},{"one":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","other":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","up":true},{"one":"b40da884b5e0ab429b3608ce6030c0f7f542d008713ed1a444c70d9c00989ecf","other":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","up":true},{"one":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","other":"6923d78400f277fe29c35ba38a416edd7c960cc75d94c35aa3446b4d5e2f6c3b","up":true},{"one":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","other":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","up":true},{"one":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","other":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","up":true},{"one":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","other":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","up":true},{"one":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","other":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","up":true},{"one":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","other":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","up":true},{"one":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","other":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","up":true},{"one":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","other":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","up":true},{"one":"d777e45233aa4d8b723f6a6aa9cbf5de4c0b19f6fd00af6d538b67ec01b74ab5","other":"df52d5de0f5e69e7e4c9e0ffcec1f9df86d38044bb57e5a0cb18bde354ecb66d","up":true},{"one":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","other":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","up":true},{"one":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","other":"41b2b713dba3a11683ad83d1cb4beb9ff4f5aaf49f2ad1ac2247c84a85d40a56","up":true},{"one":"05a4778e1be7da949c1a7bffa3408020b66b4ee3c98e1f62e35d212c77e83ada","other":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","up":true},{"one":"1a8ecbde88be22a9bbb5f3ef19be5377923d655fd9cd838f9d08a442614742c3","other":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","up":true},{"one":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","other":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","up":true},{"one":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","other":"715b38eca0c630aad73e5f7d65453c6666f459994e3cc4bba586af75d78e8fe4","up":true},{"one":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","other":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","up":false},{"one":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","other":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","up":true},{"one":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","other":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","up":true},{"one":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","other":"d96e430326c7fc91fcd19c4e85b472a41ba24317ff09e5bd8eb2bd7a306499be","up":true},{"one":"d60644fb69edf1d360a5b3d480030df97600a3d94a189da98b58c43758c98331","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","other":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","up":true},{"one":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","other":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","up":true},{"one":"69d8801f9f19fec51b636cc27fca2c93e67dac8f3650aea35259d01293d5c9e6","other":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","up":true},{"one":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","other":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","up":true},{"one":"e2554d1bf145ec0e7027ca8e8ba0c85fc697bc4b54e36a6cdc53f1efa37a6634","other":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","up":true},{"one":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","other":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","up":false},{"one":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","other":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","up":false},{"one":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","other":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","up":true},{"one":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","other":"e4e95f8381568c1cba7e8f64dd710554b923241a1c988dddb42a2cd96434705f","up":true},{"one":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","other":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","up":true},{"one":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","other":"574101500f64e9a1bfbf31f4f379a60a0ea9bee449af1cd29962214dd960b3c6","up":true},{"one":"d0443f24523af0eec6c20b21d84b2d54cb0ba8d4d1a205b41328bb22e8d3cd63","other":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","up":true},{"one":"4b8acc021ddea1fff036b4b0e5939c38507ef792490701f7b341e4ff10a78567","other":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","up":true},{"one":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","other":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","up":true},{"one":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","other":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","up":true},{"one":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","other":"1a85e13e2ad82c8ffa10ad52b382947e638036120188ae611fb1fb333cb5232b","up":true},{"one":"1a85e13e2ad82c8ffa10ad52b382947e638036120188ae611fb1fb333cb5232b","other":"1953df7e29813b00b0137118623b6ce34113d17d4f9aaa608b81f2ee58ea3fe3","up":true},{"one":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","other":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","up":false},{"one":"96419a246f5ca44f2796d91ccc55b3ac477e1754e74d202cdd133e6b5b96051b","other":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","up":true},{"one":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","other":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","up":true},{"one":"1254b1e5c9e80b367520bfefc35cf32a60aff56c48315107c4b07aa1a7866af9","other":"1953df7e29813b00b0137118623b6ce34113d17d4f9aaa608b81f2ee58ea3fe3","up":true},{"one":"3c5ae3bb462b109222b4a1ac1cf9b04508fcd53a2d13796880965e99e0216ee8","other":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","up":true},{"one":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","other":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","up":true},{"one":"f2f5e82cf81f07f8cf82c7171c5ee5a42b7963619fafd4295137357cea1b7bb0","other":"1953df7e29813b00b0137118623b6ce34113d17d4f9aaa608b81f2ee58ea3fe3","up":true},{"one":"57657e7b7d2642c6e5daefd27000d0da41982491486fd1aee57d0f99f072a471","other":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","up":true},{"one":"3ab20836f88aab49491e82cfd1f1e8a59b5794cf91644b663d40cb22c5460be4","other":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","up":true},{"one":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","other":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","up":true},{"one":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","other":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","up":false},{"one":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","other":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","up":true},{"one":"41b2b713dba3a11683ad83d1cb4beb9ff4f5aaf49f2ad1ac2247c84a85d40a56","other":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","up":true},{"one":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","other":"1254b1e5c9e80b367520bfefc35cf32a60aff56c48315107c4b07aa1a7866af9","up":true},{"one":"c95a481b19583fc85e9b079fa0b5cf51eb46e71b9f57fc6df2e3b78389d8de2f","other":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","up":true},{"one":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","other":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","up":true},{"one":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","other":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","up":true},{"one":"715b38eca0c630aad73e5f7d65453c6666f459994e3cc4bba586af75d78e8fe4","other":"6701b38360d0a72ddc355c17385af34b7ed9eeec00bc3867e162b5571af69e3f","up":true},{"one":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","other":"0c728c174a25e11bc171d7d35eaaa3974859a4dbf7a64e1d9c9da6b3ad63b21a","up":true},{"one":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","other":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","up":true},{"one":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","other":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","up":true},{"one":"574101500f64e9a1bfbf31f4f379a60a0ea9bee449af1cd29962214dd960b3c6","other":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","up":true},{"one":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","other":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","up":true},{"one":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","other":"4b8acc021ddea1fff036b4b0e5939c38507ef792490701f7b341e4ff10a78567","up":true},{"one":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","other":"294066bf056b1530308d096871b08a046086abcbc7ed7a0a97d9fb46cccd6003","up":true},{"one":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","other":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","up":true},{"one":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","other":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","up":true},{"one":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","other":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","up":true},{"one":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","other":"3388123bad3bf1debf51c5921c09d06f7627d05f6115af39d92dbe9b2f2313dc","up":true},{"one":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","other":"1587ed904df26ed711e2235fea41e5812a7d33ce5dab2ebb4e88515b18f43d6e","up":true},{"one":"6f1c48453e1bfb9c474d7f888d8c437c75db569e2c900867c4eaf3e6504847aa","other":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","up":true},{"one":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","other":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","up":true},{"one":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","other":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","up":true},{"one":"03059077d3fadf62794756f6947fc32ed00d82162dbe3e6ebcd32952bd421321","other":"07a6873c7eadb651196a76ab30241cc1c984549c5086f1c3bb1be7bd493346be","up":true},{"one":"0020768d96fca0cf47a9632ca25ff794c996042f07a7242ec7c70fbc75e0e45e","other":"07a6873c7eadb651196a76ab30241cc1c984549c5086f1c3bb1be7bd493346be","up":true},{"one":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","other":"07a6873c7eadb651196a76ab30241cc1c984549c5086f1c3bb1be7bd493346be","up":true},{"one":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","other":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","up":true},{"one":"67ee67b8f4d3ee50d8cdd027ba96e4b2f8ef9017bac217bd5a2d9dca35325519","other":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","up":true},{"one":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","other":"03059077d3fadf62794756f6947fc32ed00d82162dbe3e6ebcd32952bd421321","up":true},{"one":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","other":"7ec4083eca4749caa706ac08afc0c91cdcec42f18e4d360f18695a79de362115","up":true},{"one":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","other":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","up":true},{"one":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","other":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","up":false},{"one":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","other":"67ee67b8f4d3ee50d8cdd027ba96e4b2f8ef9017bac217bd5a2d9dca35325519","up":false},{"one":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","other":"48df69314a57a6ab2fc31108104aae2242ad2a941d2f87119a151250344aee02","up":true},{"one":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","other":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","up":true},{"one":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","other":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","up":true},{"one":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","other":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","up":true},{"one":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","other":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","up":true},{"one":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","other":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","up":true},{"one":"b820b318a3a12a7a7f45d06fff7f5d7e3ca2ca8097c396c1d76c735e1c915cc0","other":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","up":true},{"one":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","other":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","up":true},{"one":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","other":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","up":true},{"one":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","other":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","up":true},{"one":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","other":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","up":true},{"one":"48df69314a57a6ab2fc31108104aae2242ad2a941d2f87119a151250344aee02","other":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","up":true},{"one":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","other":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","up":true},{"one":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","other":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","up":false},{"one":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","other":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","up":true},{"one":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","other":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","up":true},{"one":"f0b803dcfec89e1d68739ca05cf0c9c04e80a2b1cd47496a83168a71184fd4cf","other":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","up":true},{"one":"d0b76e826a6eaeabdc060a0588bd1becc1901b60a8eb4e8adbe61107883a2987","other":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","up":true},{"one":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","other":"413b15ae74e38b8610dd5edd52e05630f8acfa4139a1b361775afa8e748ebbfc","up":true},{"one":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","other":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","up":true},{"one":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","other":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","up":true},{"one":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","other":"f1ad1843560ead294e9d60c9bfc99d8a827f2cfe557e0f9fe7ebf82aec56926d","up":true},{"one":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","other":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","up":true},{"one":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","other":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","up":true},{"one":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","other":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","up":true},{"one":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","other":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","up":true},{"one":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","other":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","up":true},{"one":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","other":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","up":true},{"one":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","other":"5b308b25cfe30d0b68784dc73be464dfa85a8e49ddd6f6271ff0ee2b63798928","up":true},{"one":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","other":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","up":true},{"one":"a011c924104e072ec415cf72255167582e5d3f189f541066ba88b3ae0041b508","other":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","up":true},{"one":"802f9e88535e40c179cb5f7d2a16500696e2af2115d3f7ca0cac5c2f634109c2","other":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","up":true},{"one":"83d0ffd43e2469b9c3498862d0186e413cc4d205f7854bf9dd73c4d74379217c","other":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","up":true},{"one":"8ea97f6abd40d70717688c5dd38daf6f6b12005df21ae397dfb700fbfe3447ac","other":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","up":true},{"one":"0a3fe6516edff33cde6afc46c6174a8562f17454b6f3ad41be8c12ebf1b0a07d","other":"03059077d3fadf62794756f6947fc32ed00d82162dbe3e6ebcd32952bd421321","up":true},{"one":"f1ad1843560ead294e9d60c9bfc99d8a827f2cfe557e0f9fe7ebf82aec56926d","other":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","up":false},{"one":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","other":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","up":true},{"one":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","other":"57657e7b7d2642c6e5daefd27000d0da41982491486fd1aee57d0f99f072a471","up":true},{"one":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","other":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","up":true},{"one":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","other":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","up":false},{"one":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","other":"85642219f8bb8ce048cfce5aecddc8acd035afa035a0137c354f5b6fc3e3889d","up":true},{"one":"91088ea79cd8f27da8879760dd21c363aa9be1135437c2b87464426874b597e5","other":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","up":true},{"one":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","other":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","up":true},{"one":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","other":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","up":true},{"one":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","other":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","up":true},{"one":"bd39cdc673c4184193347f0efd74bb894f7f36a994c154ffe3dadcd456efe460","other":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","up":true},{"one":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","other":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","up":true},{"one":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","other":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","up":true},{"one":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","other":"715b38eca0c630aad73e5f7d65453c6666f459994e3cc4bba586af75d78e8fe4","up":true},{"one":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","other":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","up":true},{"one":"af8fb611627063b408d2b96c1ab68fafb223a86d947de3dac3a59649cd13e0cf","other":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","up":true},{"one":"9a09aa168f7f4ebd90652d6900ace1ba88b8785290b2687783d4a23e9bdb90ee","other":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","up":true},{"one":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","other":"e2554d1bf145ec0e7027ca8e8ba0c85fc697bc4b54e36a6cdc53f1efa37a6634","up":true},{"one":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","other":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","up":true},{"one":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","other":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","up":true},{"one":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","other":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","up":true},{"one":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","other":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","up":false},{"one":"e54d69f3bb061c62dede6e5a8704700bf9b8063edead7c755c9586f4d96754f1","other":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","up":true},{"one":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","other":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","up":true},{"one":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","other":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","up":true},{"one":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","other":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","up":true},{"one":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","other":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","up":true},{"one":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","other":"307c36a947f5c4c597075d8346a5db6133fbd2c14da099f85c1d2b7d6d8331f2","up":true},{"one":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","other":"57657e7b7d2642c6e5daefd27000d0da41982491486fd1aee57d0f99f072a471","up":true},{"one":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","other":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","up":true},{"one":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","other":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","up":true},{"one":"41b2b713dba3a11683ad83d1cb4beb9ff4f5aaf49f2ad1ac2247c84a85d40a56","other":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","up":true},{"one":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","other":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","up":true},{"one":"c95a481b19583fc85e9b079fa0b5cf51eb46e71b9f57fc6df2e3b78389d8de2f","other":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","up":true},{"one":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","other":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","up":true},{"one":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","other":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","up":true},{"one":"574101500f64e9a1bfbf31f4f379a60a0ea9bee449af1cd29962214dd960b3c6","other":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","up":true},{"one":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","other":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","up":true},{"one":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","other":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","up":true},{"one":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","other":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","up":true},{"one":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","other":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","up":true},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","up":true},{"one":"0020768d96fca0cf47a9632ca25ff794c996042f07a7242ec7c70fbc75e0e45e","other":"03059077d3fadf62794756f6947fc32ed00d82162dbe3e6ebcd32952bd421321","up":true},{"one":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","other":"03059077d3fadf62794756f6947fc32ed00d82162dbe3e6ebcd32952bd421321","up":true},{"one":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","other":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","up":true},{"one":"67ee67b8f4d3ee50d8cdd027ba96e4b2f8ef9017bac217bd5a2d9dca35325519","other":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","up":true},{"one":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","other":"03059077d3fadf62794756f6947fc32ed00d82162dbe3e6ebcd32952bd421321","up":true},{"one":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","other":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","up":true},{"one":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","other":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","up":false},{"one":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","other":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","up":false},{"one":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","other":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","up":true},{"one":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","other":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","up":true},{"one":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","other":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","up":false},{"one":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","other":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","up":false},{"one":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","other":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","up":true},{"one":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","other":"e4e95f8381568c1cba7e8f64dd710554b923241a1c988dddb42a2cd96434705f","up":true},{"one":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","other":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","up":true},{"one":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","other":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","up":true},{"one":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","other":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","up":true},{"one":"f0b803dcfec89e1d68739ca05cf0c9c04e80a2b1cd47496a83168a71184fd4cf","other":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","up":true},{"one":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","other":"208e648a32b59b77c2abbeac0402c8d009485a19dd4a04a216cd91d0c6713929","up":true},{"one":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","other":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","up":true},{"one":"6923d78400f277fe29c35ba38a416edd7c960cc75d94c35aa3446b4d5e2f6c3b","other":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","up":true},{"one":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","other":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","up":true},{"one":"48ec978577e2dbf0448ab119a3821be5d25dbf5a295fd4f695b9311429538a26","other":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","up":true},{"one":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","other":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","up":true},{"one":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","other":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","up":true},{"one":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","other":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","up":true},{"one":"802f9e88535e40c179cb5f7d2a16500696e2af2115d3f7ca0cac5c2f634109c2","other":"a011c924104e072ec415cf72255167582e5d3f189f541066ba88b3ae0041b508","up":true},{"one":"a011c924104e072ec415cf72255167582e5d3f189f541066ba88b3ae0041b508","other":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","up":true},{"one":"3388123bad3bf1debf51c5921c09d06f7627d05f6115af39d92dbe9b2f2313dc","other":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","up":true},{"one":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","other":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","up":true},{"one":"a011c924104e072ec415cf72255167582e5d3f189f541066ba88b3ae0041b508","other":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","up":true},{"one":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","other":"802f9e88535e40c179cb5f7d2a16500696e2af2115d3f7ca0cac5c2f634109c2","up":true},{"one":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","other":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","up":true},{"one":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","other":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","up":true},{"one":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","other":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","up":true},{"one":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","other":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","up":true},{"one":"8ea97f6abd40d70717688c5dd38daf6f6b12005df21ae397dfb700fbfe3447ac","other":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","up":true},{"one":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","other":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","up":true},{"one":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","other":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","up":true},{"one":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","other":"e1909ff70b17f666d74c73e8d9803df1e6e20733e4aeecdc78e444b6632d92c9","up":true},{"one":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","other":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","up":true},{"one":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","other":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","up":true},{"one":"3ef6bb1dcd37360fe4a463b70add6cd4ba112a293112911b2304ab05b4c1b543","other":"7aef1625409f479ee1abad565f8d7e73b17714e77f54d1ac1b2fe80382a058cc","up":true},{"one":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","other":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","up":true},{"one":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","other":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","up":false},{"one":"85642219f8bb8ce048cfce5aecddc8acd035afa035a0137c354f5b6fc3e3889d","other":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","up":true},{"one":"bb3198bb61f3dc96365a99b7141c0b85720614de4640afa76458d27484e1353a","other":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","up":true},{"one":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","other":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","up":true},{"one":"b40da884b5e0ab429b3608ce6030c0f7f542d008713ed1a444c70d9c00989ecf","other":"d56025bb110210216fdc8f86ad205607c6fb4744d3d444931aa17512a854356a","up":true},{"one":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","other":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","up":false},{"one":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","other":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","up":true},{"one":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","other":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","up":true},{"one":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","other":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","up":true},{"one":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","other":"802f9e88535e40c179cb5f7d2a16500696e2af2115d3f7ca0cac5c2f634109c2","up":true},{"one":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","other":"1953df7e29813b00b0137118623b6ce34113d17d4f9aaa608b81f2ee58ea3fe3","up":true},{"one":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","other":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","up":true},{"one":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"bd39cdc673c4184193347f0efd74bb894f7f36a994c154ffe3dadcd456efe460","other":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","up":true},{"one":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"d777e45233aa4d8b723f6a6aa9cbf5de4c0b19f6fd00af6d538b67ec01b74ab5","other":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","up":true},{"one":"05a4778e1be7da949c1a7bffa3408020b66b4ee3c98e1f62e35d212c77e83ada","other":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","up":true},{"one":"1a8ecbde88be22a9bbb5f3ef19be5377923d655fd9cd838f9d08a442614742c3","other":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","up":true},{"one":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","other":"df52d5de0f5e69e7e4c9e0ffcec1f9df86d38044bb57e5a0cb18bde354ecb66d","up":true},{"one":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","other":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","up":true},{"one":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","other":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","up":true},{"one":"5b308b25cfe30d0b68784dc73be464dfa85a8e49ddd6f6271ff0ee2b63798928","other":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","up":true},{"one":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","other":"1953df7e29813b00b0137118623b6ce34113d17d4f9aaa608b81f2ee58ea3fe3","up":true},{"one":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","other":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","up":true},{"one":"e2554d1bf145ec0e7027ca8e8ba0c85fc697bc4b54e36a6cdc53f1efa37a6634","other":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","up":true},{"one":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","other":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","up":true},{"one":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","other":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","up":true},{"one":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","other":"1953df7e29813b00b0137118623b6ce34113d17d4f9aaa608b81f2ee58ea3fe3","up":true},{"one":"f2f5e82cf81f07f8cf82c7171c5ee5a42b7963619fafd4295137357cea1b7bb0","other":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","up":true},{"one":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","other":"507060be030344850df6fc76719d503254b38d96d77eb25f49ca9d955944b10a","up":true},{"one":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","other":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","up":true},{"one":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","other":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","up":true},{"one":"0c728c174a25e11bc171d7d35eaaa3974859a4dbf7a64e1d9c9da6b3ad63b21a","other":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","up":true},{"one":"574101500f64e9a1bfbf31f4f379a60a0ea9bee449af1cd29962214dd960b3c6","other":"57657e7b7d2642c6e5daefd27000d0da41982491486fd1aee57d0f99f072a471","up":true},{"one":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","other":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","up":true},{"one":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","other":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","up":true},{"one":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","other":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","up":true},{"one":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","other":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","up":true},{"one":"1587ed904df26ed711e2235fea41e5812a7d33ce5dab2ebb4e88515b18f43d6e","other":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","up":true},{"one":"294066bf056b1530308d096871b08a046086abcbc7ed7a0a97d9fb46cccd6003","other":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","up":true},{"one":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","other":"8ea97f6abd40d70717688c5dd38daf6f6b12005df21ae397dfb700fbfe3447ac","up":true},{"one":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","other":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","up":true},{"one":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","other":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","up":true},{"one":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","other":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","up":true},{"one":"6f1c48453e1bfb9c474d7f888d8c437c75db569e2c900867c4eaf3e6504847aa","other":"3ab20836f88aab49491e82cfd1f1e8a59b5794cf91644b663d40cb22c5460be4","up":true},{"one":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","other":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","up":true},{"one":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","other":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","up":true},{"one":"020f57c369b6521552f7498e444aed21ac57832ed3ffaa7b45f607793ea1a86c","other":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","up":true},{"one":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","other":"03059077d3fadf62794756f6947fc32ed00d82162dbe3e6ebcd32952bd421321","up":true},{"one":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","other":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","up":true},{"one":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","other":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","up":true},{"one":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","other":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","up":true},{"one":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","other":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","up":true},{"one":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","other":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","up":true},{"one":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","other":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","up":true},{"one":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","other":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","up":true},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","up":true},{"one":"b820b318a3a12a7a7f45d06fff7f5d7e3ca2ca8097c396c1d76c735e1c915cc0","other":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","up":true},{"one":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","other":"d56025bb110210216fdc8f86ad205607c6fb4744d3d444931aa17512a854356a","up":true},{"one":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","other":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","up":true},{"one":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","other":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","up":true},{"one":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","other":"715b38eca0c630aad73e5f7d65453c6666f459994e3cc4bba586af75d78e8fe4","up":true},{"one":"b7aa334a49788734068ac73e4b11bc554776217500c24eb76a85fe02f55ef037","other":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","up":true},{"one":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","other":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","up":false},{"one":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","other":"85642219f8bb8ce048cfce5aecddc8acd035afa035a0137c354f5b6fc3e3889d","up":true},{"one":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","other":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","up":true},{"one":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","other":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","up":true},{"one":"34e48bd6562a308ccf6a4a0257f253e908e97d16defe2f1a31e046d5558a4769","other":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","up":true},{"one":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","other":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","up":true},{"one":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","other":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","up":true},{"one":"e4e95f8381568c1cba7e8f64dd710554b923241a1c988dddb42a2cd96434705f","other":"85642219f8bb8ce048cfce5aecddc8acd035afa035a0137c354f5b6fc3e3889d","up":true},{"one":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","other":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","up":true},{"one":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","other":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","up":false},{"one":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","other":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","up":true},{"one":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","other":"b7aa334a49788734068ac73e4b11bc554776217500c24eb76a85fe02f55ef037","up":true},{"one":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","other":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","up":true},{"one":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","other":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","up":true},{"one":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","other":"6923d78400f277fe29c35ba38a416edd7c960cc75d94c35aa3446b4d5e2f6c3b","up":true},{"one":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","other":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","up":true},{"one":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","other":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","up":true},{"one":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","other":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","up":true},{"one":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","other":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","up":true},{"one":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","other":"3388123bad3bf1debf51c5921c09d06f7627d05f6115af39d92dbe9b2f2313dc","up":true},{"one":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","other":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","up":true},{"one":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","other":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","up":true},{"one":"83d0ffd43e2469b9c3498862d0186e413cc4d205f7854bf9dd73c4d74379217c","other":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","up":true},{"one":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","other":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","up":true},{"one":"8ea97f6abd40d70717688c5dd38daf6f6b12005df21ae397dfb700fbfe3447ac","other":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","up":true},{"one":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","other":"e1909ff70b17f666d74c73e8d9803df1e6e20733e4aeecdc78e444b6632d92c9","up":true},{"one":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","other":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","up":true},{"one":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","other":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","up":true},{"one":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","other":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","up":true},{"one":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","other":"03059077d3fadf62794756f6947fc32ed00d82162dbe3e6ebcd32952bd421321","up":true},{"one":"c7fa3f5dd856ac0cd8fc3f2933180cfe1e605b31c630731f3e0a00d392f4097a","other":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","up":true},{"one":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","other":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","up":true},{"one":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","other":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","up":true},{"one":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","other":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","up":true},{"one":"d56025bb110210216fdc8f86ad205607c6fb4744d3d444931aa17512a854356a","other":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","up":true},{"one":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","other":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","up":true},{"one":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","other":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","up":true},{"one":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","other":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","up":true},{"one":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","other":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","up":false},{"one":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","other":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","up":true},{"one":"91088ea79cd8f27da8879760dd21c363aa9be1135437c2b87464426874b597e5","other":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","up":true},{"one":"b40da884b5e0ab429b3608ce6030c0f7f542d008713ed1a444c70d9c00989ecf","other":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","up":true},{"one":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","other":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","up":true},{"one":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","other":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","up":true},{"one":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","other":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","up":true},{"one":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","other":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","up":true},{"one":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","other":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","up":true},{"one":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","other":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","up":true},{"one":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","other":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","up":false},{"one":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","other":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","up":true},{"one":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","other":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","up":true},{"one":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","other":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","up":true},{"one":"05a4778e1be7da949c1a7bffa3408020b66b4ee3c98e1f62e35d212c77e83ada","other":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","up":true},{"one":"aa7876d43dd0ce40f421f68c0d13c8c718d7a7a5e7c567dd20d87d19934cdc05","other":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","up":false},{"one":"1a8ecbde88be22a9bbb5f3ef19be5377923d655fd9cd838f9d08a442614742c3","other":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","up":true},{"one":"af8fb611627063b408d2b96c1ab68fafb223a86d947de3dac3a59649cd13e0cf","other":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","up":true},{"one":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","other":"b40da884b5e0ab429b3608ce6030c0f7f542d008713ed1a444c70d9c00989ecf","up":true},{"one":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","other":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","up":true},{"one":"d60644fb69edf1d360a5b3d480030df97600a3d94a189da98b58c43758c98331","other":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","up":true},{"one":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","other":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","up":true},{"one":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","other":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","up":true},{"one":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","other":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","up":false},{"one":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","other":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","up":true},{"one":"69d8801f9f19fec51b636cc27fca2c93e67dac8f3650aea35259d01293d5c9e6","other":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","up":true},{"one":"5b308b25cfe30d0b68784dc73be464dfa85a8e49ddd6f6271ff0ee2b63798928","other":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","up":true},{"one":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","other":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","up":true},{"one":"e54d69f3bb061c62dede6e5a8704700bf9b8063edead7c755c9586f4d96754f1","other":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","up":true},{"one":"e2554d1bf145ec0e7027ca8e8ba0c85fc697bc4b54e36a6cdc53f1efa37a6634","other":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","up":true},{"one":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","other":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","up":true},{"one":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","other":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","up":true},{"one":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","other":"57657e7b7d2642c6e5daefd27000d0da41982491486fd1aee57d0f99f072a471","up":true},{"one":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","other":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","up":true},{"one":"1a85e13e2ad82c8ffa10ad52b382947e638036120188ae611fb1fb333cb5232b","other":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","up":true},{"one":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","other":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","up":true},{"one":"1254b1e5c9e80b367520bfefc35cf32a60aff56c48315107c4b07aa1a7866af9","other":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","up":true},{"one":"96419a246f5ca44f2796d91ccc55b3ac477e1754e74d202cdd133e6b5b96051b","other":"d60644fb69edf1d360a5b3d480030df97600a3d94a189da98b58c43758c98331","up":true},{"one":"57657e7b7d2642c6e5daefd27000d0da41982491486fd1aee57d0f99f072a471","other":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","up":true},{"one":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","other":"41b2b713dba3a11683ad83d1cb4beb9ff4f5aaf49f2ad1ac2247c84a85d40a56","up":true},{"one":"48ec978577e2dbf0448ab119a3821be5d25dbf5a295fd4f695b9311429538a26","other":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","up":true},{"one":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","other":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","up":true},{"one":"41b2b713dba3a11683ad83d1cb4beb9ff4f5aaf49f2ad1ac2247c84a85d40a56","other":"57657e7b7d2642c6e5daefd27000d0da41982491486fd1aee57d0f99f072a471","up":true},{"one":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","other":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","up":true},{"one":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"1953df7e29813b00b0137118623b6ce34113d17d4f9aaa608b81f2ee58ea3fe3","other":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","up":true},{"one":"f2f5e82cf81f07f8cf82c7171c5ee5a42b7963619fafd4295137357cea1b7bb0","other":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","up":true},{"one":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","other":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","up":true},{"one":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","other":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","up":true},{"one":"0c728c174a25e11bc171d7d35eaaa3974859a4dbf7a64e1d9c9da6b3ad63b21a","other":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","up":true},{"one":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","other":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","up":true},{"one":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","other":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","up":true},{"one":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","other":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","up":true},{"one":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","other":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","up":true},{"one":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","other":"3388123bad3bf1debf51c5921c09d06f7627d05f6115af39d92dbe9b2f2313dc","up":true},{"one":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","other":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","up":true},{"one":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","other":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","up":true},{"one":"020f57c369b6521552f7498e444aed21ac57832ed3ffaa7b45f607793ea1a86c","other":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","up":true},{"one":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","other":"020f57c369b6521552f7498e444aed21ac57832ed3ffaa7b45f607793ea1a86c","up":true},{"one":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","other":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","up":false},{"one":"413b15ae74e38b8610dd5edd52e05630f8acfa4139a1b361775afa8e748ebbfc","other":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","up":true},{"one":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","other":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","up":true},{"one":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","other":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","up":false},{"one":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","other":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","up":true},{"one":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","other":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","up":false},{"one":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","other":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","up":false},{"one":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","other":"b7aa334a49788734068ac73e4b11bc554776217500c24eb76a85fe02f55ef037","up":true},{"one":"48df69314a57a6ab2fc31108104aae2242ad2a941d2f87119a151250344aee02","other":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","up":true},{"one":"6701b38360d0a72ddc355c17385af34b7ed9eeec00bc3867e162b5571af69e3f","other":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","up":false},{"one":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","other":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","up":true},{"one":"208e648a32b59b77c2abbeac0402c8d009485a19dd4a04a216cd91d0c6713929","other":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","up":true},{"one":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","other":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","up":false},{"one":"b7aa334a49788734068ac73e4b11bc554776217500c24eb76a85fe02f55ef037","other":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","up":true},{"one":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","other":"c7fa3f5dd856ac0cd8fc3f2933180cfe1e605b31c630731f3e0a00d392f4097a","up":true},{"one":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","other":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","up":true},{"one":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","other":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","up":true},{"one":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","other":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","up":true},{"one":"d0b76e826a6eaeabdc060a0588bd1becc1901b60a8eb4e8adbe61107883a2987","other":"48df69314a57a6ab2fc31108104aae2242ad2a941d2f87119a151250344aee02","up":true},{"one":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","other":"510c70916948815cd32759c451515bb024e38e0db8bb128710396978dc705a67","up":true},{"one":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","other":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","up":true},{"one":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","other":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","up":true},{"one":"307c36a947f5c4c597075d8346a5db6133fbd2c14da099f85c1d2b7d6d8331f2","other":"5b308b25cfe30d0b68784dc73be464dfa85a8e49ddd6f6271ff0ee2b63798928","up":true},{"one":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","other":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","up":true},{"one":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","other":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","up":true},{"one":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","other":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","up":true},{"one":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","other":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","up":true},{"one":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","other":"6f1c48453e1bfb9c474d7f888d8c437c75db569e2c900867c4eaf3e6504847aa","up":true},{"one":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","other":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","up":true},{"one":"df52d5de0f5e69e7e4c9e0ffcec1f9df86d38044bb57e5a0cb18bde354ecb66d","other":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","up":true},{"one":"0a3fe6516edff33cde6afc46c6174a8562f17454b6f3ad41be8c12ebf1b0a07d","other":"3ef6bb1dcd37360fe4a463b70add6cd4ba112a293112911b2304ab05b4c1b543","up":true},{"one":"d96e430326c7fc91fcd19c4e85b472a41ba24317ff09e5bd8eb2bd7a306499be","other":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","up":true},{"one":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","other":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","up":true},{"one":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","other":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","up":true},{"one":"7ec4083eca4749caa706ac08afc0c91cdcec42f18e4d360f18695a79de362115","other":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","up":true},{"one":"85642219f8bb8ce048cfce5aecddc8acd035afa035a0137c354f5b6fc3e3889d","other":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","up":true},{"one":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","other":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","up":true},{"one":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","other":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","up":true},{"one":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","other":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","up":true},{"one":"d777e45233aa4d8b723f6a6aa9cbf5de4c0b19f6fd00af6d538b67ec01b74ab5","other":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","up":true},{"one":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","other":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","up":true},{"one":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","other":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","up":false},{"one":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","other":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","up":true},{"one":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","other":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","up":true},{"one":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","other":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","up":true},{"one":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","other":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","up":true},{"one":"d0443f24523af0eec6c20b21d84b2d54cb0ba8d4d1a205b41328bb22e8d3cd63","other":"91088ea79cd8f27da8879760dd21c363aa9be1135437c2b87464426874b597e5","up":true},{"one":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","other":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","up":true},{"one":"4b8acc021ddea1fff036b4b0e5939c38507ef792490701f7b341e4ff10a78567","other":"d96e430326c7fc91fcd19c4e85b472a41ba24317ff09e5bd8eb2bd7a306499be","up":true},{"one":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","other":"91088ea79cd8f27da8879760dd21c363aa9be1135437c2b87464426874b597e5","up":true},{"one":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","other":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","up":true},{"one":"1254b1e5c9e80b367520bfefc35cf32a60aff56c48315107c4b07aa1a7866af9","other":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","up":true},{"one":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","other":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","up":true},{"one":"3c5ae3bb462b109222b4a1ac1cf9b04508fcd53a2d13796880965e99e0216ee8","other":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","up":false},{"one":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","other":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","up":true},{"one":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","other":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","up":true},{"one":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","other":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","up":true},{"one":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","other":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","up":true},{"one":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","other":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","up":true},{"one":"c95a481b19583fc85e9b079fa0b5cf51eb46e71b9f57fc6df2e3b78389d8de2f","other":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","up":true},{"one":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","other":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","up":true},{"one":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","other":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","up":false},{"one":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","other":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","up":true},{"one":"020f57c369b6521552f7498e444aed21ac57832ed3ffaa7b45f607793ea1a86c","other":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","up":true},{"one":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","other":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","up":true},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","up":true},{"one":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","other":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","up":true},{"one":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","other":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","up":true},{"one":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","other":"b40da884b5e0ab429b3608ce6030c0f7f542d008713ed1a444c70d9c00989ecf","up":true},{"one":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","other":"715b38eca0c630aad73e5f7d65453c6666f459994e3cc4bba586af75d78e8fe4","up":true},{"one":"b820b318a3a12a7a7f45d06fff7f5d7e3ca2ca8097c396c1d76c735e1c915cc0","other":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","up":true},{"one":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","other":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","up":true},{"one":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","other":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","up":true},{"one":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","other":"9a09aa168f7f4ebd90652d6900ace1ba88b8785290b2687783d4a23e9bdb90ee","up":true},{"one":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","other":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","up":true},{"one":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","other":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","up":true},{"one":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","other":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","up":true},{"one":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","other":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","up":true},{"one":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","other":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","up":true},{"one":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","other":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","up":true},{"one":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","other":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","up":true},{"one":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","other":"a011c924104e072ec415cf72255167582e5d3f189f541066ba88b3ae0041b508","up":true},{"one":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","other":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","up":true},{"one":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","other":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","up":false},{"one":"f1ad1843560ead294e9d60c9bfc99d8a827f2cfe557e0f9fe7ebf82aec56926d","other":"f0b803dcfec89e1d68739ca05cf0c9c04e80a2b1cd47496a83168a71184fd4cf","up":true},{"one":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","other":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","up":true},{"one":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","other":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","up":true},{"one":"c7fa3f5dd856ac0cd8fc3f2933180cfe1e605b31c630731f3e0a00d392f4097a","other":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","up":true},{"one":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","other":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","up":true},{"one":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","other":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","up":true},{"one":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","other":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","up":true},{"one":"d96e430326c7fc91fcd19c4e85b472a41ba24317ff09e5bd8eb2bd7a306499be","other":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","up":true},{"one":"3ef6bb1dcd37360fe4a463b70add6cd4ba112a293112911b2304ab05b4c1b543","other":"67ee67b8f4d3ee50d8cdd027ba96e4b2f8ef9017bac217bd5a2d9dca35325519","up":true},{"one":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","other":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","up":true},{"one":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","other":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","up":true},{"one":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","other":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","up":false},{"one":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","other":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","up":false},{"one":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","other":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","up":false},{"one":"91088ea79cd8f27da8879760dd21c363aa9be1135437c2b87464426874b597e5","other":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","up":true},{"one":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","other":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","up":true},{"one":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","other":"91088ea79cd8f27da8879760dd21c363aa9be1135437c2b87464426874b597e5","up":true},{"one":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","other":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","up":true},{"one":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","other":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","up":false},{"one":"bd39cdc673c4184193347f0efd74bb894f7f36a994c154ffe3dadcd456efe460","other":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","up":true},{"one":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","other":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","up":true},{"one":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","other":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","up":true},{"one":"aa7876d43dd0ce40f421f68c0d13c8c718d7a7a5e7c567dd20d87d19934cdc05","other":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","up":true},{"one":"af8fb611627063b408d2b96c1ab68fafb223a86d947de3dac3a59649cd13e0cf","other":"e4e95f8381568c1cba7e8f64dd710554b923241a1c988dddb42a2cd96434705f","up":true},{"one":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","other":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","up":true},{"one":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","other":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","up":true},{"one":"d60644fb69edf1d360a5b3d480030df97600a3d94a189da98b58c43758c98331","other":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","up":true},{"one":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","other":"96419a246f5ca44f2796d91ccc55b3ac477e1754e74d202cdd133e6b5b96051b","up":true},{"one":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","other":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","up":true},{"one":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","other":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","up":true},{"one":"69d8801f9f19fec51b636cc27fca2c93e67dac8f3650aea35259d01293d5c9e6","other":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","up":true},{"one":"e54d69f3bb061c62dede6e5a8704700bf9b8063edead7c755c9586f4d96754f1","other":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","up":true},{"one":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","other":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","up":true},{"one":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","other":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","up":true},{"one":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","other":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","up":true},{"one":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","other":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","up":true},{"one":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","other":"85642219f8bb8ce048cfce5aecddc8acd035afa035a0137c354f5b6fc3e3889d","up":true},{"one":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","other":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","up":true},{"one":"1a85e13e2ad82c8ffa10ad52b382947e638036120188ae611fb1fb333cb5232b","other":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","up":true},{"one":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","other":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","up":true},{"one":"1254b1e5c9e80b367520bfefc35cf32a60aff56c48315107c4b07aa1a7866af9","other":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","up":true},{"one":"1a85e13e2ad82c8ffa10ad52b382947e638036120188ae611fb1fb333cb5232b","other":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","up":true},{"one":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","other":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","up":true},{"one":"96419a246f5ca44f2796d91ccc55b3ac477e1754e74d202cdd133e6b5b96051b","other":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","up":true},{"one":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","other":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","up":true},{"one":"96419a246f5ca44f2796d91ccc55b3ac477e1754e74d202cdd133e6b5b96051b","other":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","up":true},{"one":"8ea97f6abd40d70717688c5dd38daf6f6b12005df21ae397dfb700fbfe3447ac","other":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","up":true},{"one":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","other":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","up":true},{"one":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","other":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","up":true},{"one":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","other":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","up":true},{"one":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","other":"f2f5e82cf81f07f8cf82c7171c5ee5a42b7963619fafd4295137357cea1b7bb0","up":true},{"one":"57657e7b7d2642c6e5daefd27000d0da41982491486fd1aee57d0f99f072a471","other":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","up":true},{"one":"48ec978577e2dbf0448ab119a3821be5d25dbf5a295fd4f695b9311429538a26","other":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","up":true},{"one":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","other":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","up":true},{"one":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","other":"91088ea79cd8f27da8879760dd21c363aa9be1135437c2b87464426874b597e5","up":true},{"one":"48ec978577e2dbf0448ab119a3821be5d25dbf5a295fd4f695b9311429538a26","other":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","up":true},{"one":"4b8acc021ddea1fff036b4b0e5939c38507ef792490701f7b341e4ff10a78567","other":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","up":true},{"one":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","other":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","up":true},{"one":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","other":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","up":true},{"one":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","other":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","up":true},{"one":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","other":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","up":true},{"one":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","other":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","up":true},{"one":"1953df7e29813b00b0137118623b6ce34113d17d4f9aaa608b81f2ee58ea3fe3","other":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","up":true},{"one":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","other":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","up":true},{"one":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","other":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","up":true},{"one":"96419a246f5ca44f2796d91ccc55b3ac477e1754e74d202cdd133e6b5b96051b","other":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","up":true},{"one":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","other":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","up":true},{"one":"f2f5e82cf81f07f8cf82c7171c5ee5a42b7963619fafd4295137357cea1b7bb0","other":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","up":true},{"one":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","other":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","up":true},{"one":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","other":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","up":true},{"one":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","other":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","up":true},{"one":"f2f5e82cf81f07f8cf82c7171c5ee5a42b7963619fafd4295137357cea1b7bb0","other":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","up":true},{"one":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","other":"1a85e13e2ad82c8ffa10ad52b382947e638036120188ae611fb1fb333cb5232b","up":true},{"one":"0c728c174a25e11bc171d7d35eaaa3974859a4dbf7a64e1d9c9da6b3ad63b21a","other":"e2554d1bf145ec0e7027ca8e8ba0c85fc697bc4b54e36a6cdc53f1efa37a6634","up":true},{"one":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","other":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","up":true},{"one":"1953df7e29813b00b0137118623b6ce34113d17d4f9aaa608b81f2ee58ea3fe3","other":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","up":true},{"one":"1254b1e5c9e80b367520bfefc35cf32a60aff56c48315107c4b07aa1a7866af9","other":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","up":true},{"one":"1a85e13e2ad82c8ffa10ad52b382947e638036120188ae611fb1fb333cb5232b","other":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","up":true},{"one":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","other":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","up":true},{"one":"0c728c174a25e11bc171d7d35eaaa3974859a4dbf7a64e1d9c9da6b3ad63b21a","other":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","up":true},{"one":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","other":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","up":true},{"one":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","other":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","up":true},{"one":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","other":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","up":true},{"one":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","other":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","up":true},{"one":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","other":"574101500f64e9a1bfbf31f4f379a60a0ea9bee449af1cd29962214dd960b3c6","up":true},{"one":"f2f5e82cf81f07f8cf82c7171c5ee5a42b7963619fafd4295137357cea1b7bb0","other":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","up":true},{"one":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","other":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","up":true},{"one":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","other":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","up":true},{"one":"1587ed904df26ed711e2235fea41e5812a7d33ce5dab2ebb4e88515b18f43d6e","other":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","up":true},{"one":"57657e7b7d2642c6e5daefd27000d0da41982491486fd1aee57d0f99f072a471","other":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","up":true},{"one":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","other":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","up":true},{"one":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","other":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","up":true},{"one":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","other":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","up":true},{"one":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","other":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","up":true},{"one":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","other":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","up":true},{"one":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","other":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","up":true},{"one":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","other":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","up":true},{"one":"41b2b713dba3a11683ad83d1cb4beb9ff4f5aaf49f2ad1ac2247c84a85d40a56","other":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","up":true},{"one":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","other":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","up":true},{"one":"c95a481b19583fc85e9b079fa0b5cf51eb46e71b9f57fc6df2e3b78389d8de2f","other":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","up":true},{"one":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","other":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","up":true},{"one":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","other":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","up":true},{"one":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","other":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","up":true},{"one":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","other":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","up":true},{"one":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","other":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","up":false},{"one":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","other":"574101500f64e9a1bfbf31f4f379a60a0ea9bee449af1cd29962214dd960b3c6","up":true},{"one":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","other":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","up":true},{"one":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","other":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","up":true},{"one":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","other":"07a6873c7eadb651196a76ab30241cc1c984549c5086f1c3bb1be7bd493346be","up":true},{"one":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","other":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","up":true},{"one":"294066bf056b1530308d096871b08a046086abcbc7ed7a0a97d9fb46cccd6003","other":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","up":true},{"one":"07a6873c7eadb651196a76ab30241cc1c984549c5086f1c3bb1be7bd493346be","other":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","up":true},{"one":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","other":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","up":true},{"one":"03059077d3fadf62794756f6947fc32ed00d82162dbe3e6ebcd32952bd421321","other":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","up":true},{"one":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","other":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","up":true},{"one":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","other":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","up":true},{"one":"0c728c174a25e11bc171d7d35eaaa3974859a4dbf7a64e1d9c9da6b3ad63b21a","other":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","up":true},{"one":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","other":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","up":true},{"one":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","other":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","up":true},{"one":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","other":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","up":false},{"one":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","other":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","up":true},{"one":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","other":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","up":true},{"one":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","other":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","up":true},{"one":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","other":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","up":true},{"one":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","other":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","up":true},{"one":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","other":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","up":true},{"one":"48ec978577e2dbf0448ab119a3821be5d25dbf5a295fd4f695b9311429538a26","other":"413b15ae74e38b8610dd5edd52e05630f8acfa4139a1b361775afa8e748ebbfc","up":true},{"one":"f2f5e82cf81f07f8cf82c7171c5ee5a42b7963619fafd4295137357cea1b7bb0","other":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","up":true},{"one":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","other":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","up":true},{"one":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","other":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","up":true},{"one":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","other":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","up":true},{"one":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","other":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","up":true},{"one":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","other":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","up":true},{"one":"41b2b713dba3a11683ad83d1cb4beb9ff4f5aaf49f2ad1ac2247c84a85d40a56","other":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","up":true},{"one":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","other":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","up":true},{"one":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","other":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","up":true},{"one":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","other":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","up":true},{"one":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","other":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","up":true},{"one":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","other":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","up":true},{"one":"a011c924104e072ec415cf72255167582e5d3f189f541066ba88b3ae0041b508","other":"bb3198bb61f3dc96365a99b7141c0b85720614de4640afa76458d27484e1353a","up":true},{"one":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","other":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","up":true},{"one":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","other":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","up":true},{"one":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","other":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","up":true},{"one":"c95a481b19583fc85e9b079fa0b5cf51eb46e71b9f57fc6df2e3b78389d8de2f","other":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","up":true},{"one":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","other":"bd39cdc673c4184193347f0efd74bb894f7f36a994c154ffe3dadcd456efe460","up":true},{"one":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","other":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","up":true},{"one":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","other":"57657e7b7d2642c6e5daefd27000d0da41982491486fd1aee57d0f99f072a471","up":true},{"one":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","other":"bd39cdc673c4184193347f0efd74bb894f7f36a994c154ffe3dadcd456efe460","up":true},{"one":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","other":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","up":true},{"one":"294066bf056b1530308d096871b08a046086abcbc7ed7a0a97d9fb46cccd6003","other":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","up":true},{"one":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","other":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","up":true},{"one":"bb3198bb61f3dc96365a99b7141c0b85720614de4640afa76458d27484e1353a","other":"b820b318a3a12a7a7f45d06fff7f5d7e3ca2ca8097c396c1d76c735e1c915cc0","up":true},{"one":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","other":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","up":true},{"one":"413b15ae74e38b8610dd5edd52e05630f8acfa4139a1b361775afa8e748ebbfc","other":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","up":true},{"one":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","other":"b820b318a3a12a7a7f45d06fff7f5d7e3ca2ca8097c396c1d76c735e1c915cc0","up":true},{"one":"413b15ae74e38b8610dd5edd52e05630f8acfa4139a1b361775afa8e748ebbfc","other":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","up":true},{"one":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","other":"6f1c48453e1bfb9c474d7f888d8c437c75db569e2c900867c4eaf3e6504847aa","up":true},{"one":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","other":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","up":false},{"one":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","other":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","up":true},{"one":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","other":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","up":true},{"one":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","other":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","up":true},{"one":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","other":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","up":true},{"one":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","other":"d0443f24523af0eec6c20b21d84b2d54cb0ba8d4d1a205b41328bb22e8d3cd63","up":true},{"one":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","other":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","up":true},{"one":"f2f5e82cf81f07f8cf82c7171c5ee5a42b7963619fafd4295137357cea1b7bb0","other":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","up":true},{"one":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","other":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","up":true},{"one":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","other":"1254b1e5c9e80b367520bfefc35cf32a60aff56c48315107c4b07aa1a7866af9","up":true},{"one":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","other":"03059077d3fadf62794756f6947fc32ed00d82162dbe3e6ebcd32952bd421321","up":true},{"one":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","other":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","up":true},{"one":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","other":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","up":true},{"one":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","other":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","up":true},{"one":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","other":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","up":true},{"one":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","other":"96419a246f5ca44f2796d91ccc55b3ac477e1754e74d202cdd133e6b5b96051b","up":true},{"one":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","other":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","up":true},{"one":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","other":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","up":true},{"one":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","other":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","up":true},{"one":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","other":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","up":true},{"one":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","other":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","up":true},{"one":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","other":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","up":true},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"85642219f8bb8ce048cfce5aecddc8acd035afa035a0137c354f5b6fc3e3889d","up":true},{"one":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","other":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","up":true},{"one":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","other":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","up":true},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","up":true},{"one":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","other":"91088ea79cd8f27da8879760dd21c363aa9be1135437c2b87464426874b597e5","up":true},{"one":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","other":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","up":true},{"one":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","other":"c7fa3f5dd856ac0cd8fc3f2933180cfe1e605b31c630731f3e0a00d392f4097a","up":true},{"one":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","other":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","up":true},{"one":"a011c924104e072ec415cf72255167582e5d3f189f541066ba88b3ae0041b508","other":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","up":true},{"one":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","other":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","up":true},{"one":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","other":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","up":true},{"one":"208e648a32b59b77c2abbeac0402c8d009485a19dd4a04a216cd91d0c6713929","other":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","up":true},{"one":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","other":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","up":true},{"one":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","other":"294066bf056b1530308d096871b08a046086abcbc7ed7a0a97d9fb46cccd6003","up":true},{"one":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","other":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","up":true},{"one":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","other":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","up":false},{"one":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","other":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","up":true},{"one":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","other":"0020768d96fca0cf47a9632ca25ff794c996042f07a7242ec7c70fbc75e0e45e","up":true},{"one":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","other":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","up":true},{"one":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","other":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","up":true},{"one":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","other":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","up":true},{"one":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","other":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","up":true},{"one":"07a6873c7eadb651196a76ab30241cc1c984549c5086f1c3bb1be7bd493346be","other":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","up":true},{"one":"020f57c369b6521552f7498e444aed21ac57832ed3ffaa7b45f607793ea1a86c","other":"0020768d96fca0cf47a9632ca25ff794c996042f07a7242ec7c70fbc75e0e45e","up":true},{"one":"c95a481b19583fc85e9b079fa0b5cf51eb46e71b9f57fc6df2e3b78389d8de2f","other":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","up":true},{"one":"4b8acc021ddea1fff036b4b0e5939c38507ef792490701f7b341e4ff10a78567","other":"507060be030344850df6fc76719d503254b38d96d77eb25f49ca9d955944b10a","up":true},{"one":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","other":"7ec4083eca4749caa706ac08afc0c91cdcec42f18e4d360f18695a79de362115","up":true},{"one":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","other":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","up":true},{"one":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","other":"48df69314a57a6ab2fc31108104aae2242ad2a941d2f87119a151250344aee02","up":true},{"one":"bb3198bb61f3dc96365a99b7141c0b85720614de4640afa76458d27484e1353a","other":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","up":true},{"one":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","other":"b40da884b5e0ab429b3608ce6030c0f7f542d008713ed1a444c70d9c00989ecf","up":true},{"one":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","other":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","up":true},{"one":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","other":"7ec4083eca4749caa706ac08afc0c91cdcec42f18e4d360f18695a79de362115","up":true},{"one":"0020768d96fca0cf47a9632ca25ff794c996042f07a7242ec7c70fbc75e0e45e","other":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","up":true},{"one":"294066bf056b1530308d096871b08a046086abcbc7ed7a0a97d9fb46cccd6003","other":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","up":true},{"one":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","other":"d0443f24523af0eec6c20b21d84b2d54cb0ba8d4d1a205b41328bb22e8d3cd63","up":true},{"one":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","other":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","up":true},{"one":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","other":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","up":true},{"one":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","other":"b7aa334a49788734068ac73e4b11bc554776217500c24eb76a85fe02f55ef037","up":true},{"one":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","other":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","up":true},{"one":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","other":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","up":true},{"one":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","other":"03059077d3fadf62794756f6947fc32ed00d82162dbe3e6ebcd32952bd421321","up":true},{"one":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","other":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","up":true},{"one":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","other":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","up":true},{"one":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","other":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","up":true},{"one":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","other":"d0443f24523af0eec6c20b21d84b2d54cb0ba8d4d1a205b41328bb22e8d3cd63","up":true},{"one":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","other":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","up":true},{"one":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","other":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","up":true},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","up":true},{"one":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","other":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","up":true},{"one":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","other":"294066bf056b1530308d096871b08a046086abcbc7ed7a0a97d9fb46cccd6003","up":true},{"one":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","other":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","up":false},{"one":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","other":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","up":true},{"one":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","other":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","up":true},{"one":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","other":"3ab20836f88aab49491e82cfd1f1e8a59b5794cf91644b663d40cb22c5460be4","up":true},{"one":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","other":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","up":true},{"one":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","other":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","up":false},{"one":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","other":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","up":false},{"one":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","other":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","up":true},{"one":"7ec4083eca4749caa706ac08afc0c91cdcec42f18e4d360f18695a79de362115","other":"294066bf056b1530308d096871b08a046086abcbc7ed7a0a97d9fb46cccd6003","up":true},{"one":"34e48bd6562a308ccf6a4a0257f253e908e97d16defe2f1a31e046d5558a4769","other":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","up":true},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"af8fb611627063b408d2b96c1ab68fafb223a86d947de3dac3a59649cd13e0cf","up":true},{"one":"7ec4083eca4749caa706ac08afc0c91cdcec42f18e4d360f18695a79de362115","other":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","up":true},{"one":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","other":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","up":true},{"one":"d56025bb110210216fdc8f86ad205607c6fb4744d3d444931aa17512a854356a","other":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","up":true},{"one":"b820b318a3a12a7a7f45d06fff7f5d7e3ca2ca8097c396c1d76c735e1c915cc0","other":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","up":true},{"one":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","other":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","up":true},{"one":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","other":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","up":true},{"one":"d56025bb110210216fdc8f86ad205607c6fb4744d3d444931aa17512a854356a","other":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","up":true},{"one":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","other":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","up":true},{"one":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","other":"5b308b25cfe30d0b68784dc73be464dfa85a8e49ddd6f6271ff0ee2b63798928","up":true},{"one":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","other":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","up":true},{"one":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","other":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","up":true},{"one":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","other":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","up":true},{"one":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","other":"bb3198bb61f3dc96365a99b7141c0b85720614de4640afa76458d27484e1353a","up":true},{"one":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","other":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","up":true},{"one":"f0b803dcfec89e1d68739ca05cf0c9c04e80a2b1cd47496a83168a71184fd4cf","other":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","up":true},{"one":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","other":"0c728c174a25e11bc171d7d35eaaa3974859a4dbf7a64e1d9c9da6b3ad63b21a","up":true},{"one":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","other":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","up":true},{"one":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","other":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","up":true},{"one":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","other":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","up":true},{"one":"1587ed904df26ed711e2235fea41e5812a7d33ce5dab2ebb4e88515b18f43d6e","other":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","up":true},{"one":"85642219f8bb8ce048cfce5aecddc8acd035afa035a0137c354f5b6fc3e3889d","other":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","up":true},{"one":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","other":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","up":true},{"one":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","other":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","up":true},{"one":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","other":"507060be030344850df6fc76719d503254b38d96d77eb25f49ca9d955944b10a","up":true},{"one":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","other":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","up":true},{"one":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","other":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","up":true},{"one":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","other":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","up":true},{"one":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","other":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","up":true},{"one":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","other":"020f57c369b6521552f7498e444aed21ac57832ed3ffaa7b45f607793ea1a86c","up":true},{"one":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","other":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","up":true},{"one":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","other":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","up":true},{"one":"020f57c369b6521552f7498e444aed21ac57832ed3ffaa7b45f607793ea1a86c","other":"0a3fe6516edff33cde6afc46c6174a8562f17454b6f3ad41be8c12ebf1b0a07d","up":false},{"one":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","other":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","up":true},{"one":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","other":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","up":true},{"one":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","other":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","up":false},{"one":"07a6873c7eadb651196a76ab30241cc1c984549c5086f1c3bb1be7bd493346be","other":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","up":true},{"one":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","other":"0a3fe6516edff33cde6afc46c6174a8562f17454b6f3ad41be8c12ebf1b0a07d","up":true},{"one":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","other":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","up":true},{"one":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","other":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","up":true},{"one":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","other":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","up":true},{"one":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","other":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","up":true},{"one":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","other":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","up":true},{"one":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","other":"b7aa334a49788734068ac73e4b11bc554776217500c24eb76a85fe02f55ef037","up":true},{"one":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","other":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","up":true},{"one":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","other":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","up":true},{"one":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","other":"03059077d3fadf62794756f6947fc32ed00d82162dbe3e6ebcd32952bd421321","up":true},{"one":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","other":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","up":true},{"one":"307c36a947f5c4c597075d8346a5db6133fbd2c14da099f85c1d2b7d6d8331f2","other":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","up":true},{"one":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","other":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","up":true},{"one":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","other":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","up":true},{"one":"715b38eca0c630aad73e5f7d65453c6666f459994e3cc4bba586af75d78e8fe4","other":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","up":true},{"one":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","other":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","up":true},{"one":"df52d5de0f5e69e7e4c9e0ffcec1f9df86d38044bb57e5a0cb18bde354ecb66d","other":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","up":true},{"one":"b7aa334a49788734068ac73e4b11bc554776217500c24eb76a85fe02f55ef037","other":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","up":true},{"one":"83d0ffd43e2469b9c3498862d0186e413cc4d205f7854bf9dd73c4d74379217c","other":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","up":true},{"one":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","other":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","up":true},{"one":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","other":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","up":true},{"one":"e4e95f8381568c1cba7e8f64dd710554b923241a1c988dddb42a2cd96434705f","other":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","up":true},{"one":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","other":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","up":true},{"one":"507060be030344850df6fc76719d503254b38d96d77eb25f49ca9d955944b10a","other":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","up":true},{"one":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","other":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","up":true},{"one":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","other":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","up":true},{"one":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","other":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","up":true},{"one":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","other":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","up":true},{"one":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","other":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","up":true},{"one":"9a09aa168f7f4ebd90652d6900ace1ba88b8785290b2687783d4a23e9bdb90ee","other":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","up":true},{"one":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","other":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","up":true},{"one":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","other":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","up":true},{"one":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","other":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","up":true},{"one":"d777e45233aa4d8b723f6a6aa9cbf5de4c0b19f6fd00af6d538b67ec01b74ab5","other":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","up":true},{"one":"05a4778e1be7da949c1a7bffa3408020b66b4ee3c98e1f62e35d212c77e83ada","other":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","up":true},{"one":"1a8ecbde88be22a9bbb5f3ef19be5377923d655fd9cd838f9d08a442614742c3","other":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","up":true},{"one":"aa7876d43dd0ce40f421f68c0d13c8c718d7a7a5e7c567dd20d87d19934cdc05","other":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","up":true},{"one":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","other":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","up":true},{"one":"3388123bad3bf1debf51c5921c09d06f7627d05f6115af39d92dbe9b2f2313dc","other":"03059077d3fadf62794756f6947fc32ed00d82162dbe3e6ebcd32952bd421321","up":true},{"one":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","other":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","up":true},{"one":"e2554d1bf145ec0e7027ca8e8ba0c85fc697bc4b54e36a6cdc53f1efa37a6634","other":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","up":true},{"one":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","other":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","up":true},{"one":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","other":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","up":true},{"one":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","other":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","up":true},{"one":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","other":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","up":true},{"one":"48df69314a57a6ab2fc31108104aae2242ad2a941d2f87119a151250344aee02","other":"6923d78400f277fe29c35ba38a416edd7c960cc75d94c35aa3446b4d5e2f6c3b","up":true},{"one":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","other":"48ec978577e2dbf0448ab119a3821be5d25dbf5a295fd4f695b9311429538a26","up":true},{"one":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","other":"af8fb611627063b408d2b96c1ab68fafb223a86d947de3dac3a59649cd13e0cf","up":true},{"one":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","other":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","up":true},{"one":"3ab20836f88aab49491e82cfd1f1e8a59b5794cf91644b663d40cb22c5460be4","other":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","up":true},{"one":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","other":"57657e7b7d2642c6e5daefd27000d0da41982491486fd1aee57d0f99f072a471","up":true},{"one":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","other":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","up":true},{"one":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","other":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","up":true},{"one":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","other":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","up":true},{"one":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","other":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","up":true},{"one":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","other":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","up":true},{"one":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","other":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","up":true},{"one":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","other":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","up":true},{"one":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","other":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","up":true},{"one":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","other":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","up":true},{"one":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","other":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","up":true},{"one":"0a3fe6516edff33cde6afc46c6174a8562f17454b6f3ad41be8c12ebf1b0a07d","other":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","up":true},{"one":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","other":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","up":true},{"one":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","other":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","up":true},{"one":"d96e430326c7fc91fcd19c4e85b472a41ba24317ff09e5bd8eb2bd7a306499be","other":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","up":true},{"one":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","other":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","up":true},{"one":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","other":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","up":true},{"one":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","other":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","up":true},{"one":"4b8acc021ddea1fff036b4b0e5939c38507ef792490701f7b341e4ff10a78567","other":"48ec978577e2dbf0448ab119a3821be5d25dbf5a295fd4f695b9311429538a26","up":true},{"one":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","other":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","up":true},{"one":"bb3198bb61f3dc96365a99b7141c0b85720614de4640afa76458d27484e1353a","other":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","up":false},{"one":"d0b76e826a6eaeabdc060a0588bd1becc1901b60a8eb4e8adbe61107883a2987","other":"f1ad1843560ead294e9d60c9bfc99d8a827f2cfe557e0f9fe7ebf82aec56926d","up":true},{"one":"7ec4083eca4749caa706ac08afc0c91cdcec42f18e4d360f18695a79de362115","other":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","up":true},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","up":true},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","up":true},{"one":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","other":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","up":true},{"one":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","other":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","up":false},{"one":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","other":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","up":true},{"one":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","other":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","up":true},{"one":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","other":"3ab20836f88aab49491e82cfd1f1e8a59b5794cf91644b663d40cb22c5460be4","up":true},{"one":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","other":"3ab20836f88aab49491e82cfd1f1e8a59b5794cf91644b663d40cb22c5460be4","up":true},{"one":"3c5ae3bb462b109222b4a1ac1cf9b04508fcd53a2d13796880965e99e0216ee8","other":"3ab20836f88aab49491e82cfd1f1e8a59b5794cf91644b663d40cb22c5460be4","up":true},{"one":"b40da884b5e0ab429b3608ce6030c0f7f542d008713ed1a444c70d9c00989ecf","other":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","up":false},{"one":"1587ed904df26ed711e2235fea41e5812a7d33ce5dab2ebb4e88515b18f43d6e","other":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","up":true},{"one":"510c70916948815cd32759c451515bb024e38e0db8bb128710396978dc705a67","other":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","up":true},{"one":"413b15ae74e38b8610dd5edd52e05630f8acfa4139a1b361775afa8e748ebbfc","other":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","up":true},{"one":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","other":"0a3fe6516edff33cde6afc46c6174a8562f17454b6f3ad41be8c12ebf1b0a07d","up":false},{"one":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","other":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","up":true},{"one":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","other":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","up":true},{"one":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","other":"294066bf056b1530308d096871b08a046086abcbc7ed7a0a97d9fb46cccd6003","up":true},{"one":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","other":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","up":true},{"one":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","other":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","up":false},{"one":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","other":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","up":true},{"one":"208e648a32b59b77c2abbeac0402c8d009485a19dd4a04a216cd91d0c6713929","other":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","up":true},{"one":"07a6873c7eadb651196a76ab30241cc1c984549c5086f1c3bb1be7bd493346be","other":"0a3fe6516edff33cde6afc46c6174a8562f17454b6f3ad41be8c12ebf1b0a07d","up":false},{"one":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","other":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","up":true},{"one":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","other":"48ec978577e2dbf0448ab119a3821be5d25dbf5a295fd4f695b9311429538a26","up":true},{"one":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","other":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","up":true},{"one":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","other":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","up":true},{"one":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","other":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","up":false},{"one":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","other":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","up":true},{"one":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","other":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","up":true},{"one":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","other":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","up":true},{"one":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","other":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","up":true},{"one":"bd39cdc673c4184193347f0efd74bb894f7f36a994c154ffe3dadcd456efe460","other":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","up":true},{"one":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","other":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","up":true},{"one":"85642219f8bb8ce048cfce5aecddc8acd035afa035a0137c354f5b6fc3e3889d","other":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","up":true},{"one":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","other":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","up":true},{"one":"34e48bd6562a308ccf6a4a0257f253e908e97d16defe2f1a31e046d5558a4769","other":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","up":true},{"one":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","other":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","up":true},{"one":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","other":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","up":true},{"one":"507060be030344850df6fc76719d503254b38d96d77eb25f49ca9d955944b10a","other":"413b15ae74e38b8610dd5edd52e05630f8acfa4139a1b361775afa8e748ebbfc","up":true},{"one":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","other":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","up":true},{"one":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","other":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","up":true},{"one":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","other":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","up":false},{"one":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","other":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","up":true},{"one":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","other":"f1ad1843560ead294e9d60c9bfc99d8a827f2cfe557e0f9fe7ebf82aec56926d","up":true},{"one":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","other":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","up":true},{"one":"802f9e88535e40c179cb5f7d2a16500696e2af2115d3f7ca0cac5c2f634109c2","other":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","up":true},{"one":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","other":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","up":true},{"one":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","other":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","up":true},{"one":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","other":"b820b318a3a12a7a7f45d06fff7f5d7e3ca2ca8097c396c1d76c735e1c915cc0","up":true},{"one":"3388123bad3bf1debf51c5921c09d06f7627d05f6115af39d92dbe9b2f2313dc","other":"307c36a947f5c4c597075d8346a5db6133fbd2c14da099f85c1d2b7d6d8331f2","up":true},{"one":"c7fa3f5dd856ac0cd8fc3f2933180cfe1e605b31c630731f3e0a00d392f4097a","other":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","up":true},{"one":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","other":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","up":true},{"one":"8ea97f6abd40d70717688c5dd38daf6f6b12005df21ae397dfb700fbfe3447ac","other":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","up":true},{"one":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","other":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","up":true},{"one":"df52d5de0f5e69e7e4c9e0ffcec1f9df86d38044bb57e5a0cb18bde354ecb66d","other":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","up":true},{"one":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","other":"f1ad1843560ead294e9d60c9bfc99d8a827f2cfe557e0f9fe7ebf82aec56926d","up":true},{"one":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","other":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","up":true},{"one":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","other":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","up":true},{"one":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","other":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","up":false},{"one":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","other":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","up":true},{"one":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","other":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","up":true},{"one":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","other":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","up":true},{"one":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","other":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","up":true},{"one":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","other":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","up":false},{"one":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","other":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","up":true},{"one":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","other":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","up":true},{"one":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","other":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","up":true},{"one":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","other":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","up":true},{"one":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","other":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","up":true},{"one":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","other":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","up":true},{"one":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","other":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","up":true},{"one":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","other":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","up":true},{"one":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","other":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","up":true},{"one":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","other":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","up":false},{"one":"7aef1625409f479ee1abad565f8d7e73b17714e77f54d1ac1b2fe80382a058cc","other":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","up":true},{"one":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","other":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","up":true},{"one":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","other":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","up":true},{"one":"6701b38360d0a72ddc355c17385af34b7ed9eeec00bc3867e162b5571af69e3f","other":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","up":true},{"one":"6923d78400f277fe29c35ba38a416edd7c960cc75d94c35aa3446b4d5e2f6c3b","other":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","up":true},{"one":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","other":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","up":true},{"one":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","other":"0c728c174a25e11bc171d7d35eaaa3974859a4dbf7a64e1d9c9da6b3ad63b21a","up":true},{"one":"3ef6bb1dcd37360fe4a463b70add6cd4ba112a293112911b2304ab05b4c1b543","other":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","up":true},{"one":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","other":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","up":true},{"one":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","other":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","up":true},{"one":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","other":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","up":true},{"one":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","other":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","up":true},{"one":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","other":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","up":true},{"one":"1953df7e29813b00b0137118623b6ce34113d17d4f9aaa608b81f2ee58ea3fe3","other":"1a8ecbde88be22a9bbb5f3ef19be5377923d655fd9cd838f9d08a442614742c3","up":true},{"one":"1254b1e5c9e80b367520bfefc35cf32a60aff56c48315107c4b07aa1a7866af9","other":"1a8ecbde88be22a9bbb5f3ef19be5377923d655fd9cd838f9d08a442614742c3","up":true},{"one":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","other":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","up":true},{"one":"1a85e13e2ad82c8ffa10ad52b382947e638036120188ae611fb1fb333cb5232b","other":"1a8ecbde88be22a9bbb5f3ef19be5377923d655fd9cd838f9d08a442614742c3","up":true},{"one":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","other":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","up":true},{"one":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","other":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","up":true},{"one":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","other":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","up":true},{"one":"57657e7b7d2642c6e5daefd27000d0da41982491486fd1aee57d0f99f072a471","other":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","up":true},{"one":"574101500f64e9a1bfbf31f4f379a60a0ea9bee449af1cd29962214dd960b3c6","other":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","up":true},{"one":"41b2b713dba3a11683ad83d1cb4beb9ff4f5aaf49f2ad1ac2247c84a85d40a56","other":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","up":true},{"one":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","other":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","up":true},{"one":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","other":"7ec4083eca4749caa706ac08afc0c91cdcec42f18e4d360f18695a79de362115","up":true},{"one":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","other":"e2554d1bf145ec0e7027ca8e8ba0c85fc697bc4b54e36a6cdc53f1efa37a6634","up":true},{"one":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","other":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","up":false},{"one":"91088ea79cd8f27da8879760dd21c363aa9be1135437c2b87464426874b597e5","other":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","up":true},{"one":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","other":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","up":true},{"one":"b40da884b5e0ab429b3608ce6030c0f7f542d008713ed1a444c70d9c00989ecf","other":"aa7876d43dd0ce40f421f68c0d13c8c718d7a7a5e7c567dd20d87d19934cdc05","up":true},{"one":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","other":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","up":true},{"one":"1a8ecbde88be22a9bbb5f3ef19be5377923d655fd9cd838f9d08a442614742c3","other":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","up":true},{"one":"d60644fb69edf1d360a5b3d480030df97600a3d94a189da98b58c43758c98331","other":"e2554d1bf145ec0e7027ca8e8ba0c85fc697bc4b54e36a6cdc53f1efa37a6634","up":true},{"one":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","other":"5b308b25cfe30d0b68784dc73be464dfa85a8e49ddd6f6271ff0ee2b63798928","up":true},{"one":"6f1c48453e1bfb9c474d7f888d8c437c75db569e2c900867c4eaf3e6504847aa","other":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","up":true},{"one":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","other":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","up":true},{"one":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"69d8801f9f19fec51b636cc27fca2c93e67dac8f3650aea35259d01293d5c9e6","other":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","up":true},{"one":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","other":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","up":true},{"one":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","other":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","up":true},{"one":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","other":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","up":true},{"one":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","other":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","up":true},{"one":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","other":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","up":true},{"one":"d0443f24523af0eec6c20b21d84b2d54cb0ba8d4d1a205b41328bb22e8d3cd63","other":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","up":true},{"one":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","other":"48ec978577e2dbf0448ab119a3821be5d25dbf5a295fd4f695b9311429538a26","up":true},{"one":"c95a481b19583fc85e9b079fa0b5cf51eb46e71b9f57fc6df2e3b78389d8de2f","other":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","up":true},{"one":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","other":"91088ea79cd8f27da8879760dd21c363aa9be1135437c2b87464426874b597e5","up":true},{"one":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","other":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","up":true},{"one":"c95a481b19583fc85e9b079fa0b5cf51eb46e71b9f57fc6df2e3b78389d8de2f","other":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","up":true},{"one":"d0443f24523af0eec6c20b21d84b2d54cb0ba8d4d1a205b41328bb22e8d3cd63","other":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","up":true},{"one":"91088ea79cd8f27da8879760dd21c363aa9be1135437c2b87464426874b597e5","other":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","up":true},{"one":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","other":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","up":true},{"one":"f0b803dcfec89e1d68739ca05cf0c9c04e80a2b1cd47496a83168a71184fd4cf","other":"bb3198bb61f3dc96365a99b7141c0b85720614de4640afa76458d27484e1353a","up":true},{"one":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","other":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","up":true},{"one":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","other":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","up":true},{"one":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","other":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","up":false},{"one":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","other":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","up":true},{"one":"c95a481b19583fc85e9b079fa0b5cf51eb46e71b9f57fc6df2e3b78389d8de2f","other":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","up":true},{"one":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","other":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","up":true},{"one":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","other":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","up":true},{"one":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","other":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","up":true},{"one":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","other":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","up":true},{"one":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","other":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","up":true},{"one":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","other":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","up":true},{"one":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","other":"83d0ffd43e2469b9c3498862d0186e413cc4d205f7854bf9dd73c4d74379217c","up":true},{"one":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","other":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","up":true},{"one":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","other":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","up":false},{"one":"715b38eca0c630aad73e5f7d65453c6666f459994e3cc4bba586af75d78e8fe4","other":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","up":true},{"one":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","other":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","up":true},{"one":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","other":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","up":true},{"one":"83d0ffd43e2469b9c3498862d0186e413cc4d205f7854bf9dd73c4d74379217c","other":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","up":true},{"one":"f1ad1843560ead294e9d60c9bfc99d8a827f2cfe557e0f9fe7ebf82aec56926d","other":"d96e430326c7fc91fcd19c4e85b472a41ba24317ff09e5bd8eb2bd7a306499be","up":true},{"one":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","other":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","up":false},{"one":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","other":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","up":true},{"one":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","other":"d0443f24523af0eec6c20b21d84b2d54cb0ba8d4d1a205b41328bb22e8d3cd63","up":true},{"one":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","other":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","up":true},{"one":"b820b318a3a12a7a7f45d06fff7f5d7e3ca2ca8097c396c1d76c735e1c915cc0","other":"b7aa334a49788734068ac73e4b11bc554776217500c24eb76a85fe02f55ef037","up":true},{"one":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","other":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","up":true},{"one":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","other":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","up":true},{"one":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","other":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","up":false},{"one":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","other":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","up":true},{"one":"a011c924104e072ec415cf72255167582e5d3f189f541066ba88b3ae0041b508","other":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","up":true},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","up":true},{"one":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","other":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","up":true},{"one":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","other":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","up":true},{"one":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","other":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","up":true},{"one":"f2f5e82cf81f07f8cf82c7171c5ee5a42b7963619fafd4295137357cea1b7bb0","other":"e2554d1bf145ec0e7027ca8e8ba0c85fc697bc4b54e36a6cdc53f1efa37a6634","up":true},{"one":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","other":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","up":true},{"one":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","other":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","up":true},{"one":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","other":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","up":true},{"one":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","other":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","up":true},{"one":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","other":"85642219f8bb8ce048cfce5aecddc8acd035afa035a0137c354f5b6fc3e3889d","up":true},{"one":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","other":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","up":true},{"one":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","other":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","up":true},{"one":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","other":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","up":true},{"one":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","other":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","up":true},{"one":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","other":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","up":true},{"one":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","other":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","up":true},{"one":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","other":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","up":true},{"one":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","other":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","up":true},{"one":"3ef6bb1dcd37360fe4a463b70add6cd4ba112a293112911b2304ab05b4c1b543","other":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","up":true},{"one":"85642219f8bb8ce048cfce5aecddc8acd035afa035a0137c354f5b6fc3e3889d","other":"802f9e88535e40c179cb5f7d2a16500696e2af2115d3f7ca0cac5c2f634109c2","up":true},{"one":"d777e45233aa4d8b723f6a6aa9cbf5de4c0b19f6fd00af6d538b67ec01b74ab5","other":"bb3198bb61f3dc96365a99b7141c0b85720614de4640afa76458d27484e1353a","up":true},{"one":"34e48bd6562a308ccf6a4a0257f253e908e97d16defe2f1a31e046d5558a4769","other":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","up":true},{"one":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","other":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","up":true},{"one":"507060be030344850df6fc76719d503254b38d96d77eb25f49ca9d955944b10a","other":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","up":true},{"one":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","other":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","up":false},{"one":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","other":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","up":true},{"one":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","other":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","up":false},{"one":"b40da884b5e0ab429b3608ce6030c0f7f542d008713ed1a444c70d9c00989ecf","other":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","up":true},{"one":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","other":"b820b318a3a12a7a7f45d06fff7f5d7e3ca2ca8097c396c1d76c735e1c915cc0","up":true},{"one":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","other":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","up":true},{"one":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","other":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","up":true},{"one":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","other":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","up":true},{"one":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","other":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","up":true},{"one":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","other":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","up":true},{"one":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","other":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","up":true},{"one":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","other":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","up":true},{"one":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","other":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","up":true},{"one":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","other":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","up":true},{"one":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","other":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","up":true},{"one":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","other":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","up":true},{"one":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","other":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","up":true},{"one":"d777e45233aa4d8b723f6a6aa9cbf5de4c0b19f6fd00af6d538b67ec01b74ab5","other":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","up":true},{"one":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","other":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","up":true},{"one":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","other":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","up":true},{"one":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","other":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","up":true},{"one":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","other":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","up":true},{"one":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","other":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","up":true},{"one":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","other":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","up":true},{"one":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","other":"aa7876d43dd0ce40f421f68c0d13c8c718d7a7a5e7c567dd20d87d19934cdc05","up":true},{"one":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","other":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","up":true},{"one":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","other":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","up":true},{"one":"f0b803dcfec89e1d68739ca05cf0c9c04e80a2b1cd47496a83168a71184fd4cf","other":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","up":true},{"one":"48df69314a57a6ab2fc31108104aae2242ad2a941d2f87119a151250344aee02","other":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","up":true},{"one":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","other":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","up":true},{"one":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","other":"d0b76e826a6eaeabdc060a0588bd1becc1901b60a8eb4e8adbe61107883a2987","up":true},{"one":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","other":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","up":true},{"one":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","other":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","up":true},{"one":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","other":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","up":true},{"one":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","other":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","up":true},{"one":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","other":"83d0ffd43e2469b9c3498862d0186e413cc4d205f7854bf9dd73c4d74379217c","up":true},{"one":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","other":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","up":true},{"one":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","other":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","up":true},{"one":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","other":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","up":true},{"one":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","other":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","up":true},{"one":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","other":"b40da884b5e0ab429b3608ce6030c0f7f542d008713ed1a444c70d9c00989ecf","up":true},{"one":"91088ea79cd8f27da8879760dd21c363aa9be1135437c2b87464426874b597e5","other":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","up":true},{"one":"c95a481b19583fc85e9b079fa0b5cf51eb46e71b9f57fc6df2e3b78389d8de2f","other":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","up":false},{"one":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"b7aa334a49788734068ac73e4b11bc554776217500c24eb76a85fe02f55ef037","other":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","up":true},{"one":"83d0ffd43e2469b9c3498862d0186e413cc4d205f7854bf9dd73c4d74379217c","other":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","up":true},{"one":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","other":"41b2b713dba3a11683ad83d1cb4beb9ff4f5aaf49f2ad1ac2247c84a85d40a56","up":true},{"one":"e4e95f8381568c1cba7e8f64dd710554b923241a1c988dddb42a2cd96434705f","other":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","up":true},{"one":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","other":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","up":true},{"one":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","other":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","up":true},{"one":"4b8acc021ddea1fff036b4b0e5939c38507ef792490701f7b341e4ff10a78567","other":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","up":true},{"one":"d56025bb110210216fdc8f86ad205607c6fb4744d3d444931aa17512a854356a","other":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","up":false},{"one":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","other":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","up":true},{"one":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","other":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","up":false},{"one":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","other":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","up":true},{"one":"3c5ae3bb462b109222b4a1ac1cf9b04508fcd53a2d13796880965e99e0216ee8","other":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","up":true},{"one":"3ab20836f88aab49491e82cfd1f1e8a59b5794cf91644b663d40cb22c5460be4","other":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","up":true},{"one":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","other":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","up":true},{"one":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","other":"e2554d1bf145ec0e7027ca8e8ba0c85fc697bc4b54e36a6cdc53f1efa37a6634","up":true},{"one":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","other":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","up":true},{"one":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","other":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","up":true},{"one":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","other":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","up":true},{"one":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","other":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","up":true},{"one":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","other":"507060be030344850df6fc76719d503254b38d96d77eb25f49ca9d955944b10a","up":true},{"one":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","other":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","up":true},{"one":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","other":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","up":true},{"one":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","other":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","up":true},{"one":"e54d69f3bb061c62dede6e5a8704700bf9b8063edead7c755c9586f4d96754f1","other":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","up":true},{"one":"48df69314a57a6ab2fc31108104aae2242ad2a941d2f87119a151250344aee02","other":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","up":true},{"one":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","other":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","up":true},{"one":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","other":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","up":true},{"one":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","other":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","up":true},{"one":"0c728c174a25e11bc171d7d35eaaa3974859a4dbf7a64e1d9c9da6b3ad63b21a","other":"1254b1e5c9e80b367520bfefc35cf32a60aff56c48315107c4b07aa1a7866af9","up":true},{"one":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","other":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","up":true},{"one":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","other":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","up":true},{"one":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","other":"b820b318a3a12a7a7f45d06fff7f5d7e3ca2ca8097c396c1d76c735e1c915cc0","up":true},{"one":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","other":"3c5ae3bb462b109222b4a1ac1cf9b04508fcd53a2d13796880965e99e0216ee8","up":true},{"one":"307c36a947f5c4c597075d8346a5db6133fbd2c14da099f85c1d2b7d6d8331f2","other":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","up":true},{"one":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","other":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","up":true},{"one":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","other":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","up":true},{"one":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","other":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","up":true},{"one":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","other":"0c728c174a25e11bc171d7d35eaaa3974859a4dbf7a64e1d9c9da6b3ad63b21a","up":true},{"one":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","other":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","up":true},{"one":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","other":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","up":true},{"one":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","other":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","up":false},{"one":"d777e45233aa4d8b723f6a6aa9cbf5de4c0b19f6fd00af6d538b67ec01b74ab5","other":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","up":true},{"one":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","other":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","up":true},{"one":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","other":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","up":true},{"one":"d96e430326c7fc91fcd19c4e85b472a41ba24317ff09e5bd8eb2bd7a306499be","other":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","up":true},{"one":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","other":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","up":true},{"one":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","other":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","up":true},{"one":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","other":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","up":true},{"one":"6701b38360d0a72ddc355c17385af34b7ed9eeec00bc3867e162b5571af69e3f","other":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","up":true},{"one":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","other":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","up":true},{"one":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","other":"d777e45233aa4d8b723f6a6aa9cbf5de4c0b19f6fd00af6d538b67ec01b74ab5","up":true},{"one":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","other":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","up":true},{"one":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","other":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","up":true},{"one":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","other":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","up":true},{"one":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","other":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","up":true},{"one":"3c5ae3bb462b109222b4a1ac1cf9b04508fcd53a2d13796880965e99e0216ee8","other":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","up":true},{"one":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","other":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","up":true},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","up":true},{"one":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","other":"4b8acc021ddea1fff036b4b0e5939c38507ef792490701f7b341e4ff10a78567","up":true},{"one":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","other":"b40da884b5e0ab429b3608ce6030c0f7f542d008713ed1a444c70d9c00989ecf","up":true},{"one":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","other":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","up":true},{"one":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","other":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","up":true},{"one":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","other":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","up":true},{"one":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","other":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","up":true},{"one":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","other":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","up":true},{"one":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","other":"f1ad1843560ead294e9d60c9bfc99d8a827f2cfe557e0f9fe7ebf82aec56926d","up":true},{"one":"e4e95f8381568c1cba7e8f64dd710554b923241a1c988dddb42a2cd96434705f","other":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","up":true},{"one":"96419a246f5ca44f2796d91ccc55b3ac477e1754e74d202cdd133e6b5b96051b","other":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","up":true},{"one":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","other":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","up":true},{"one":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","other":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","up":true},{"one":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","other":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","up":true},{"one":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","other":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","up":true},{"one":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","other":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","up":true},{"one":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","other":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","up":true},{"one":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","other":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","up":true},{"one":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","other":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","up":true},{"one":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","other":"69d8801f9f19fec51b636cc27fca2c93e67dac8f3650aea35259d01293d5c9e6","up":true},{"one":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","other":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","up":true},{"one":"f0b803dcfec89e1d68739ca05cf0c9c04e80a2b1cd47496a83168a71184fd4cf","other":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","up":true},{"one":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","other":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","up":true},{"one":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","other":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","up":false},{"one":"d60644fb69edf1d360a5b3d480030df97600a3d94a189da98b58c43758c98331","other":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","up":true},{"one":"802f9e88535e40c179cb5f7d2a16500696e2af2115d3f7ca0cac5c2f634109c2","other":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","up":true},{"one":"3388123bad3bf1debf51c5921c09d06f7627d05f6115af39d92dbe9b2f2313dc","other":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","up":true},{"one":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","other":"69d8801f9f19fec51b636cc27fca2c93e67dac8f3650aea35259d01293d5c9e6","up":true},{"one":"69d8801f9f19fec51b636cc27fca2c93e67dac8f3650aea35259d01293d5c9e6","other":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","up":true},{"one":"df52d5de0f5e69e7e4c9e0ffcec1f9df86d38044bb57e5a0cb18bde354ecb66d","other":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","up":true},{"one":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","other":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","up":true},{"one":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","other":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","up":true},{"one":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","other":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","up":true},{"one":"d0443f24523af0eec6c20b21d84b2d54cb0ba8d4d1a205b41328bb22e8d3cd63","other":"d56025bb110210216fdc8f86ad205607c6fb4744d3d444931aa17512a854356a","up":true},{"one":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","other":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","up":true},{"one":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","other":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","up":true},{"one":"0a3fe6516edff33cde6afc46c6174a8562f17454b6f3ad41be8c12ebf1b0a07d","other":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","up":true},{"one":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","other":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","up":true},{"one":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","other":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","up":true},{"one":"d56025bb110210216fdc8f86ad205607c6fb4744d3d444931aa17512a854356a","other":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","up":true},{"one":"bb3198bb61f3dc96365a99b7141c0b85720614de4640afa76458d27484e1353a","other":"b7aa334a49788734068ac73e4b11bc554776217500c24eb76a85fe02f55ef037","up":true},{"one":"7ec4083eca4749caa706ac08afc0c91cdcec42f18e4d360f18695a79de362115","other":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","up":true},{"one":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","other":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","up":true},{"one":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","other":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","up":true},{"one":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","other":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","up":true},{"one":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","other":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","up":true},{"one":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","other":"3ab20836f88aab49491e82cfd1f1e8a59b5794cf91644b663d40cb22c5460be4","up":true},{"one":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","other":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","up":true},{"one":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","other":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","up":true},{"one":"f2f5e82cf81f07f8cf82c7171c5ee5a42b7963619fafd4295137357cea1b7bb0","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"41b2b713dba3a11683ad83d1cb4beb9ff4f5aaf49f2ad1ac2247c84a85d40a56","other":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","up":true},{"one":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","other":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","up":true},{"one":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","other":"307c36a947f5c4c597075d8346a5db6133fbd2c14da099f85c1d2b7d6d8331f2","up":true},{"one":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","other":"91088ea79cd8f27da8879760dd21c363aa9be1135437c2b87464426874b597e5","up":true},{"one":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","other":"91088ea79cd8f27da8879760dd21c363aa9be1135437c2b87464426874b597e5","up":true},{"one":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","other":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","up":true},{"one":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","other":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","up":true},{"one":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","other":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","up":true},{"one":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","other":"507060be030344850df6fc76719d503254b38d96d77eb25f49ca9d955944b10a","up":true},{"one":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","other":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","up":true},{"one":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","other":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","up":true},{"one":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","other":"6f1c48453e1bfb9c474d7f888d8c437c75db569e2c900867c4eaf3e6504847aa","up":true},{"one":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","other":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","up":true},{"one":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","other":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","up":true},{"one":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","other":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","up":true},{"one":"bd39cdc673c4184193347f0efd74bb894f7f36a994c154ffe3dadcd456efe460","other":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","up":true},{"one":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","other":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","up":false},{"one":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","other":"1953df7e29813b00b0137118623b6ce34113d17d4f9aaa608b81f2ee58ea3fe3","up":true},{"one":"34e48bd6562a308ccf6a4a0257f253e908e97d16defe2f1a31e046d5558a4769","other":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","up":true},{"one":"507060be030344850df6fc76719d503254b38d96d77eb25f49ca9d955944b10a","other":"48ec978577e2dbf0448ab119a3821be5d25dbf5a295fd4f695b9311429538a26","up":true},{"one":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","other":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","up":true},{"one":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","other":"1a85e13e2ad82c8ffa10ad52b382947e638036120188ae611fb1fb333cb5232b","up":true},{"one":"e2554d1bf145ec0e7027ca8e8ba0c85fc697bc4b54e36a6cdc53f1efa37a6634","other":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","up":true},{"one":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","other":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","up":true},{"one":"0c728c174a25e11bc171d7d35eaaa3974859a4dbf7a64e1d9c9da6b3ad63b21a","other":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","up":true},{"one":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","other":"a011c924104e072ec415cf72255167582e5d3f189f541066ba88b3ae0041b508","up":true},{"one":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","other":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","up":true},{"one":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","other":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","up":true},{"one":"307c36a947f5c4c597075d8346a5db6133fbd2c14da099f85c1d2b7d6d8331f2","other":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","up":true},{"one":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","other":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","up":true},{"one":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","other":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","up":true},{"one":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","other":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","up":true},{"one":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","other":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","up":true},{"one":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","other":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","up":true},{"one":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","other":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","up":false},{"one":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","other":"e1909ff70b17f666d74c73e8d9803df1e6e20733e4aeecdc78e444b6632d92c9","up":true},{"one":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","other":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","up":true},{"one":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","other":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","up":true},{"one":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","other":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","up":true},{"one":"d777e45233aa4d8b723f6a6aa9cbf5de4c0b19f6fd00af6d538b67ec01b74ab5","other":"d56025bb110210216fdc8f86ad205607c6fb4744d3d444931aa17512a854356a","up":true},{"one":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","other":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","up":true},{"one":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","other":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","up":true},{"one":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","other":"bd39cdc673c4184193347f0efd74bb894f7f36a994c154ffe3dadcd456efe460","up":true},{"one":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","other":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","up":true},{"one":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","other":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","up":true},{"one":"0020768d96fca0cf47a9632ca25ff794c996042f07a7242ec7c70fbc75e0e45e","other":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","up":true},{"one":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","other":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","up":true},{"one":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","other":"a011c924104e072ec415cf72255167582e5d3f189f541066ba88b3ae0041b508","up":true},{"one":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","other":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","up":true},{"one":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","other":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","up":true},{"one":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","other":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","up":true},{"one":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","other":"d96e430326c7fc91fcd19c4e85b472a41ba24317ff09e5bd8eb2bd7a306499be","up":true},{"one":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","other":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","up":false},{"one":"715b38eca0c630aad73e5f7d65453c6666f459994e3cc4bba586af75d78e8fe4","other":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","up":true},{"one":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","other":"b40da884b5e0ab429b3608ce6030c0f7f542d008713ed1a444c70d9c00989ecf","up":true},{"one":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","other":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","up":true},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","up":true},{"one":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","other":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","up":true},{"one":"69d8801f9f19fec51b636cc27fca2c93e67dac8f3650aea35259d01293d5c9e6","other":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","up":true},{"one":"9a09aa168f7f4ebd90652d6900ace1ba88b8785290b2687783d4a23e9bdb90ee","other":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","up":true},{"one":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","other":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","up":true},{"one":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","other":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","up":true},{"one":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","other":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","up":true},{"one":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","other":"5b308b25cfe30d0b68784dc73be464dfa85a8e49ddd6f6271ff0ee2b63798928","up":true},{"one":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","other":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","up":true},{"one":"510c70916948815cd32759c451515bb024e38e0db8bb128710396978dc705a67","other":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","up":false},{"one":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","other":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","up":true},{"one":"574101500f64e9a1bfbf31f4f379a60a0ea9bee449af1cd29962214dd960b3c6","other":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","up":true},{"one":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","other":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","up":true},{"one":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","other":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","up":true},{"one":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","other":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","up":true},{"one":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","other":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","up":true},{"one":"1a8ecbde88be22a9bbb5f3ef19be5377923d655fd9cd838f9d08a442614742c3","other":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","up":true},{"one":"d60644fb69edf1d360a5b3d480030df97600a3d94a189da98b58c43758c98331","other":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","up":true},{"one":"b40da884b5e0ab429b3608ce6030c0f7f542d008713ed1a444c70d9c00989ecf","other":"bd39cdc673c4184193347f0efd74bb894f7f36a994c154ffe3dadcd456efe460","up":true},{"one":"6f1c48453e1bfb9c474d7f888d8c437c75db569e2c900867c4eaf3e6504847aa","other":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","up":true},{"one":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","other":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","up":true},{"one":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","other":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","up":true},{"one":"0c728c174a25e11bc171d7d35eaaa3974859a4dbf7a64e1d9c9da6b3ad63b21a","other":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","up":true},{"one":"307c36a947f5c4c597075d8346a5db6133fbd2c14da099f85c1d2b7d6d8331f2","other":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","up":true},{"one":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","other":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","up":true},{"one":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","other":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","up":true},{"one":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","other":"bb3198bb61f3dc96365a99b7141c0b85720614de4640afa76458d27484e1353a","up":true},{"one":"83d0ffd43e2469b9c3498862d0186e413cc4d205f7854bf9dd73c4d74379217c","other":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","up":true},{"one":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","other":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","up":true},{"one":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","other":"a011c924104e072ec415cf72255167582e5d3f189f541066ba88b3ae0041b508","up":true},{"one":"6701b38360d0a72ddc355c17385af34b7ed9eeec00bc3867e162b5571af69e3f","other":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","up":true},{"one":"6923d78400f277fe29c35ba38a416edd7c960cc75d94c35aa3446b4d5e2f6c3b","other":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","up":true},{"one":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","other":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","up":true},{"one":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","other":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","up":true},{"one":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","other":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","up":true},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","up":true},{"one":"a011c924104e072ec415cf72255167582e5d3f189f541066ba88b3ae0041b508","other":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","up":true},{"one":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","other":"4b8acc021ddea1fff036b4b0e5939c38507ef792490701f7b341e4ff10a78567","up":true},{"one":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","other":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","up":false},{"one":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","other":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","up":true},{"one":"69d8801f9f19fec51b636cc27fca2c93e67dac8f3650aea35259d01293d5c9e6","other":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","up":true},{"one":"413b15ae74e38b8610dd5edd52e05630f8acfa4139a1b361775afa8e748ebbfc","other":"4b8acc021ddea1fff036b4b0e5939c38507ef792490701f7b341e4ff10a78567","up":true},{"one":"1254b1e5c9e80b367520bfefc35cf32a60aff56c48315107c4b07aa1a7866af9","other":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","up":true},{"one":"1a85e13e2ad82c8ffa10ad52b382947e638036120188ae611fb1fb333cb5232b","other":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","up":true},{"one":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","other":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","up":true},{"one":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","other":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","up":true},{"one":"e4e95f8381568c1cba7e8f64dd710554b923241a1c988dddb42a2cd96434705f","other":"e54d69f3bb061c62dede6e5a8704700bf9b8063edead7c755c9586f4d96754f1","up":true},{"one":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","other":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","up":true},{"one":"96419a246f5ca44f2796d91ccc55b3ac477e1754e74d202cdd133e6b5b96051b","other":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","up":true},{"one":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","other":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","up":true},{"one":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","other":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","up":true},{"one":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","other":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","up":true},{"one":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","other":"d0443f24523af0eec6c20b21d84b2d54cb0ba8d4d1a205b41328bb22e8d3cd63","up":true},{"one":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","other":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","up":true},{"one":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","other":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","up":true},{"one":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","other":"7ec4083eca4749caa706ac08afc0c91cdcec42f18e4d360f18695a79de362115","up":true},{"one":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","other":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","up":false},{"one":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","other":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","up":true},{"one":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","other":"3388123bad3bf1debf51c5921c09d06f7627d05f6115af39d92dbe9b2f2313dc","up":true},{"one":"208e648a32b59b77c2abbeac0402c8d009485a19dd4a04a216cd91d0c6713929","other":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","up":true},{"one":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","other":"1a8ecbde88be22a9bbb5f3ef19be5377923d655fd9cd838f9d08a442614742c3","up":true},{"one":"8ea97f6abd40d70717688c5dd38daf6f6b12005df21ae397dfb700fbfe3447ac","other":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","up":false},{"one":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","other":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","up":true},{"one":"e54d69f3bb061c62dede6e5a8704700bf9b8063edead7c755c9586f4d96754f1","other":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","up":true},{"one":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","other":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","up":true},{"one":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","other":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","up":true},{"one":"d96e430326c7fc91fcd19c4e85b472a41ba24317ff09e5bd8eb2bd7a306499be","other":"bb3198bb61f3dc96365a99b7141c0b85720614de4640afa76458d27484e1353a","up":true},{"one":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","other":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","up":true},{"one":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","other":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","up":true},{"one":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","other":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","up":true},{"one":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","other":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","up":true},{"one":"83d0ffd43e2469b9c3498862d0186e413cc4d205f7854bf9dd73c4d74379217c","other":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","up":true},{"one":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","other":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","up":true},{"one":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","other":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","up":true},{"one":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","other":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","up":true},{"one":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","other":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","up":true},{"one":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","other":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","up":true},{"one":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","other":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","up":true},{"one":"1953df7e29813b00b0137118623b6ce34113d17d4f9aaa608b81f2ee58ea3fe3","other":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","up":true},{"one":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","other":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","up":true},{"one":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","other":"f2f5e82cf81f07f8cf82c7171c5ee5a42b7963619fafd4295137357cea1b7bb0","up":true},{"one":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","other":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","up":true},{"one":"9a09aa168f7f4ebd90652d6900ace1ba88b8785290b2687783d4a23e9bdb90ee","other":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","up":true},{"one":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","other":"af8fb611627063b408d2b96c1ab68fafb223a86d947de3dac3a59649cd13e0cf","up":true},{"one":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","other":"c7fa3f5dd856ac0cd8fc3f2933180cfe1e605b31c630731f3e0a00d392f4097a","up":true},{"one":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","other":"8ea97f6abd40d70717688c5dd38daf6f6b12005df21ae397dfb700fbfe3447ac","up":true},{"one":"1587ed904df26ed711e2235fea41e5812a7d33ce5dab2ebb4e88515b18f43d6e","other":"208e648a32b59b77c2abbeac0402c8d009485a19dd4a04a216cd91d0c6713929","up":true},{"one":"f0b803dcfec89e1d68739ca05cf0c9c04e80a2b1cd47496a83168a71184fd4cf","other":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","up":true},{"one":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","other":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","up":false},{"one":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","other":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","up":true},{"one":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","other":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","up":true},{"one":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","other":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","up":true},{"one":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","other":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","up":true},{"one":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","other":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","up":true},{"one":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","other":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","up":true},{"one":"af8fb611627063b408d2b96c1ab68fafb223a86d947de3dac3a59649cd13e0cf","other":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","up":true},{"one":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","other":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","up":false},{"one":"802f9e88535e40c179cb5f7d2a16500696e2af2115d3f7ca0cac5c2f634109c2","other":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","up":true},{"one":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","other":"85642219f8bb8ce048cfce5aecddc8acd035afa035a0137c354f5b6fc3e3889d","up":true},{"one":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","other":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","up":true},{"one":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","other":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","up":true},{"one":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","other":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","up":true},{"one":"3388123bad3bf1debf51c5921c09d06f7627d05f6115af39d92dbe9b2f2313dc","other":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","up":true},{"one":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","other":"e54d69f3bb061c62dede6e5a8704700bf9b8063edead7c755c9586f4d96754f1","up":true},{"one":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","other":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","up":true},{"one":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","other":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","up":true},{"one":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","other":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","up":true},{"one":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","other":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","up":true},{"one":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","other":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","up":true},{"one":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","other":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","up":true},{"one":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","other":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","up":true},{"one":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","other":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","up":true},{"one":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","other":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","up":true},{"one":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","other":"bd39cdc673c4184193347f0efd74bb894f7f36a994c154ffe3dadcd456efe460","up":true},{"one":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","other":"e4e95f8381568c1cba7e8f64dd710554b923241a1c988dddb42a2cd96434705f","up":false},{"one":"d777e45233aa4d8b723f6a6aa9cbf5de4c0b19f6fd00af6d538b67ec01b74ab5","other":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","up":true},{"one":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","other":"510c70916948815cd32759c451515bb024e38e0db8bb128710396978dc705a67","up":true},{"one":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","other":"3ef6bb1dcd37360fe4a463b70add6cd4ba112a293112911b2304ab05b4c1b543","up":true},{"one":"83d0ffd43e2469b9c3498862d0186e413cc4d205f7854bf9dd73c4d74379217c","other":"85642219f8bb8ce048cfce5aecddc8acd035afa035a0137c354f5b6fc3e3889d","up":true},{"one":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","other":"e54d69f3bb061c62dede6e5a8704700bf9b8063edead7c755c9586f4d96754f1","up":true},{"one":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","other":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","up":true},{"one":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","other":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","up":true},{"one":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","other":"802f9e88535e40c179cb5f7d2a16500696e2af2115d3f7ca0cac5c2f634109c2","up":true},{"one":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","other":"bd39cdc673c4184193347f0efd74bb894f7f36a994c154ffe3dadcd456efe460","up":true},{"one":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","other":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","up":true},{"one":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","other":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","up":true},{"one":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","other":"f1ad1843560ead294e9d60c9bfc99d8a827f2cfe557e0f9fe7ebf82aec56926d","up":true},{"one":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","other":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","up":true},{"one":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","other":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","up":true},{"one":"1254b1e5c9e80b367520bfefc35cf32a60aff56c48315107c4b07aa1a7866af9","other":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","up":true},{"one":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","other":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","up":true},{"one":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","other":"03059077d3fadf62794756f6947fc32ed00d82162dbe3e6ebcd32952bd421321","up":true},{"one":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","other":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","up":true},{"one":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","other":"1a85e13e2ad82c8ffa10ad52b382947e638036120188ae611fb1fb333cb5232b","up":true},{"one":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","other":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","up":true},{"one":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","other":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","up":true},{"one":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","other":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","up":true},{"one":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","other":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","up":true},{"one":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","other":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","up":true},{"one":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","other":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","up":true},{"one":"d96e430326c7fc91fcd19c4e85b472a41ba24317ff09e5bd8eb2bd7a306499be","other":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","up":true},{"one":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","other":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","up":true},{"one":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","other":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","up":true},{"one":"f1ad1843560ead294e9d60c9bfc99d8a827f2cfe557e0f9fe7ebf82aec56926d","other":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","up":true},{"one":"3ef6bb1dcd37360fe4a463b70add6cd4ba112a293112911b2304ab05b4c1b543","other":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","up":true},{"one":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","other":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","up":true},{"one":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","other":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","up":false},{"one":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","other":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","up":true},{"one":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","other":"0a3fe6516edff33cde6afc46c6174a8562f17454b6f3ad41be8c12ebf1b0a07d","up":true},{"one":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","other":"e4e95f8381568c1cba7e8f64dd710554b923241a1c988dddb42a2cd96434705f","up":true},{"one":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","other":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","up":true},{"one":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","other":"d60644fb69edf1d360a5b3d480030df97600a3d94a189da98b58c43758c98331","up":true},{"one":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","other":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","up":true},{"one":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","other":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","up":true},{"one":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","other":"802f9e88535e40c179cb5f7d2a16500696e2af2115d3f7ca0cac5c2f634109c2","up":true},{"one":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","other":"e1909ff70b17f666d74c73e8d9803df1e6e20733e4aeecdc78e444b6632d92c9","up":true},{"one":"0c728c174a25e11bc171d7d35eaaa3974859a4dbf7a64e1d9c9da6b3ad63b21a","other":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","up":true},{"one":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","other":"96419a246f5ca44f2796d91ccc55b3ac477e1754e74d202cdd133e6b5b96051b","up":true},{"one":"e2554d1bf145ec0e7027ca8e8ba0c85fc697bc4b54e36a6cdc53f1efa37a6634","other":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","up":true},{"one":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","other":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","up":true},{"one":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","other":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","up":true},{"one":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","other":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","up":true},{"one":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","other":"574101500f64e9a1bfbf31f4f379a60a0ea9bee449af1cd29962214dd960b3c6","up":true},{"one":"d777e45233aa4d8b723f6a6aa9cbf5de4c0b19f6fd00af6d538b67ec01b74ab5","other":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","up":true},{"one":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","other":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","up":false},{"one":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","other":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","up":true},{"one":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","other":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","up":false},{"one":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","other":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","up":true},{"one":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","other":"3ab20836f88aab49491e82cfd1f1e8a59b5794cf91644b663d40cb22c5460be4","up":false},{"one":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","other":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","up":true},{"one":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","other":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","up":true},{"one":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","other":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","up":true},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","up":true},{"one":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","other":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","up":true},{"one":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","other":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","up":true},{"one":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","other":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","up":true},{"one":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","other":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","up":true},{"one":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","other":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","up":true},{"one":"96419a246f5ca44f2796d91ccc55b3ac477e1754e74d202cdd133e6b5b96051b","other":"91088ea79cd8f27da8879760dd21c363aa9be1135437c2b87464426874b597e5","up":true},{"one":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","other":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","up":true},{"one":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","other":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","up":true},{"one":"b40da884b5e0ab429b3608ce6030c0f7f542d008713ed1a444c70d9c00989ecf","other":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","up":true},{"one":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","other":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","up":true},{"one":"05a4778e1be7da949c1a7bffa3408020b66b4ee3c98e1f62e35d212c77e83ada","other":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","up":true},{"one":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","other":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","up":true},{"one":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","other":"020f57c369b6521552f7498e444aed21ac57832ed3ffaa7b45f607793ea1a86c","up":true},{"one":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","other":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","up":true},{"one":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","other":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","up":true},{"one":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","other":"83d0ffd43e2469b9c3498862d0186e413cc4d205f7854bf9dd73c4d74379217c","up":true},{"one":"df52d5de0f5e69e7e4c9e0ffcec1f9df86d38044bb57e5a0cb18bde354ecb66d","other":"d56025bb110210216fdc8f86ad205607c6fb4744d3d444931aa17512a854356a","up":true},{"one":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","other":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","up":true},{"one":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","other":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","up":true},{"one":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","other":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","up":true},{"one":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","other":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","up":true},{"one":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","other":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","up":true},{"one":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","other":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","up":true},{"one":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","other":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","up":true},{"one":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","other":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","up":false},{"one":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","other":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","up":true},{"one":"aa7876d43dd0ce40f421f68c0d13c8c718d7a7a5e7c567dd20d87d19934cdc05","other":"af8fb611627063b408d2b96c1ab68fafb223a86d947de3dac3a59649cd13e0cf","up":true},{"one":"1953df7e29813b00b0137118623b6ce34113d17d4f9aaa608b81f2ee58ea3fe3","other":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","up":true},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","up":false},{"one":"1a8ecbde88be22a9bbb5f3ef19be5377923d655fd9cd838f9d08a442614742c3","other":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","up":true},{"one":"9a09aa168f7f4ebd90652d6900ace1ba88b8785290b2687783d4a23e9bdb90ee","other":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","up":false},{"one":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","other":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","up":true},{"one":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","other":"91088ea79cd8f27da8879760dd21c363aa9be1135437c2b87464426874b597e5","up":true},{"one":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","other":"b820b318a3a12a7a7f45d06fff7f5d7e3ca2ca8097c396c1d76c735e1c915cc0","up":true},{"one":"510c70916948815cd32759c451515bb024e38e0db8bb128710396978dc705a67","other":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","up":true},{"one":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","other":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","up":true},{"one":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","other":"e54d69f3bb061c62dede6e5a8704700bf9b8063edead7c755c9586f4d96754f1","up":true},{"one":"208e648a32b59b77c2abbeac0402c8d009485a19dd4a04a216cd91d0c6713929","other":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","up":true},{"one":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","other":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","up":true},{"one":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","other":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","up":true},{"one":"b7aa334a49788734068ac73e4b11bc554776217500c24eb76a85fe02f55ef037","other":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","up":true},{"one":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","other":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","up":true},{"one":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","other":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","up":true},{"one":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","other":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","up":true},{"one":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","other":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","up":true},{"one":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","other":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","up":true},{"one":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","other":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","up":true},{"one":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","other":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","up":true},{"one":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","other":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","up":false},{"one":"df52d5de0f5e69e7e4c9e0ffcec1f9df86d38044bb57e5a0cb18bde354ecb66d","other":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","up":true},{"one":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","other":"e2554d1bf145ec0e7027ca8e8ba0c85fc697bc4b54e36a6cdc53f1efa37a6634","up":true},{"one":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","other":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","up":false},{"one":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","other":"8ea97f6abd40d70717688c5dd38daf6f6b12005df21ae397dfb700fbfe3447ac","up":true},{"one":"bb3198bb61f3dc96365a99b7141c0b85720614de4640afa76458d27484e1353a","other":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","up":true},{"one":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","other":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","up":false},{"one":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","other":"91088ea79cd8f27da8879760dd21c363aa9be1135437c2b87464426874b597e5","up":true},{"one":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","other":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","up":true},{"one":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","other":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","up":true},{"one":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","other":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","up":true},{"one":"67ee67b8f4d3ee50d8cdd027ba96e4b2f8ef9017bac217bd5a2d9dca35325519","other":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","up":true},{"one":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","other":"9a09aa168f7f4ebd90652d6900ace1ba88b8785290b2687783d4a23e9bdb90ee","up":true},{"one":"7aef1625409f479ee1abad565f8d7e73b17714e77f54d1ac1b2fe80382a058cc","other":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","up":true},{"one":"1254b1e5c9e80b367520bfefc35cf32a60aff56c48315107c4b07aa1a7866af9","other":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","up":true},{"one":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","other":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","up":true},{"one":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","other":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","up":true},{"one":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","other":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","up":true},{"one":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","other":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","up":true},{"one":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","other":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","up":true},{"one":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","other":"48ec978577e2dbf0448ab119a3821be5d25dbf5a295fd4f695b9311429538a26","up":true},{"one":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","other":"5b308b25cfe30d0b68784dc73be464dfa85a8e49ddd6f6271ff0ee2b63798928","up":true},{"one":"510c70916948815cd32759c451515bb024e38e0db8bb128710396978dc705a67","other":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","up":true},{"one":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","other":"307c36a947f5c4c597075d8346a5db6133fbd2c14da099f85c1d2b7d6d8331f2","up":true},{"one":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","other":"a011c924104e072ec415cf72255167582e5d3f189f541066ba88b3ae0041b508","up":true},{"one":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","other":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","up":true},{"one":"d96e430326c7fc91fcd19c4e85b472a41ba24317ff09e5bd8eb2bd7a306499be","other":"d56025bb110210216fdc8f86ad205607c6fb4744d3d444931aa17512a854356a","up":true},{"one":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","other":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","up":true},{"one":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","other":"e2554d1bf145ec0e7027ca8e8ba0c85fc697bc4b54e36a6cdc53f1efa37a6634","up":true},{"one":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","other":"03059077d3fadf62794756f6947fc32ed00d82162dbe3e6ebcd32952bd421321","up":true},{"one":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","other":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","up":true},{"one":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","other":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","up":true},{"one":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","other":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","up":true},{"one":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","other":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","up":true},{"one":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","other":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","up":true},{"one":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","other":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","up":true},{"one":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","other":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","up":true},{"one":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","other":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","up":true},{"one":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","other":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","up":true},{"one":"57657e7b7d2642c6e5daefd27000d0da41982491486fd1aee57d0f99f072a471","other":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","up":true},{"one":"574101500f64e9a1bfbf31f4f379a60a0ea9bee449af1cd29962214dd960b3c6","other":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","up":true},{"one":"c95a481b19583fc85e9b079fa0b5cf51eb46e71b9f57fc6df2e3b78389d8de2f","other":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","up":true},{"one":"e2554d1bf145ec0e7027ca8e8ba0c85fc697bc4b54e36a6cdc53f1efa37a6634","other":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","up":true},{"one":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","other":"715b38eca0c630aad73e5f7d65453c6666f459994e3cc4bba586af75d78e8fe4","up":true},{"one":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","other":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","up":true},{"one":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","other":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","up":true},{"one":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","other":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","up":true},{"one":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","other":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","up":true},{"one":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","other":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","up":true},{"one":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","other":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","up":true},{"one":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","other":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","up":true},{"one":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","other":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","up":true},{"one":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","other":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","up":true},{"one":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","other":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","up":true},{"one":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","other":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","up":true},{"one":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","other":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","up":false},{"one":"83d0ffd43e2469b9c3498862d0186e413cc4d205f7854bf9dd73c4d74379217c","other":"802f9e88535e40c179cb5f7d2a16500696e2af2115d3f7ca0cac5c2f634109c2","up":true},{"one":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","other":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","up":true},{"one":"e54d69f3bb061c62dede6e5a8704700bf9b8063edead7c755c9586f4d96754f1","other":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","up":false},{"one":"aa7876d43dd0ce40f421f68c0d13c8c718d7a7a5e7c567dd20d87d19934cdc05","other":"a011c924104e072ec415cf72255167582e5d3f189f541066ba88b3ae0041b508","up":true},{"one":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","other":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","up":true},{"one":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","other":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","up":true},{"one":"41b2b713dba3a11683ad83d1cb4beb9ff4f5aaf49f2ad1ac2247c84a85d40a56","other":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","up":true},{"one":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","other":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","up":true},{"one":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","other":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","up":true},{"one":"208e648a32b59b77c2abbeac0402c8d009485a19dd4a04a216cd91d0c6713929","other":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","up":true},{"one":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","other":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","up":true},{"one":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","other":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","up":true},{"one":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","other":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","up":true},{"one":"3ef6bb1dcd37360fe4a463b70add6cd4ba112a293112911b2304ab05b4c1b543","other":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","up":true},{"one":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","other":"69d8801f9f19fec51b636cc27fca2c93e67dac8f3650aea35259d01293d5c9e6","up":true},{"one":"05a4778e1be7da949c1a7bffa3408020b66b4ee3c98e1f62e35d212c77e83ada","other":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","up":true},{"one":"3388123bad3bf1debf51c5921c09d06f7627d05f6115af39d92dbe9b2f2313dc","other":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","up":true},{"one":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","other":"af8fb611627063b408d2b96c1ab68fafb223a86d947de3dac3a59649cd13e0cf","up":true},{"one":"b820b318a3a12a7a7f45d06fff7f5d7e3ca2ca8097c396c1d76c735e1c915cc0","other":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","up":true},{"one":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","other":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","up":true},{"one":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","other":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","up":true},{"one":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","other":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","up":true},{"one":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","other":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","up":true},{"one":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","other":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","up":true},{"one":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","other":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","up":false},{"one":"3ab20836f88aab49491e82cfd1f1e8a59b5794cf91644b663d40cb22c5460be4","other":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","up":true},{"one":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","other":"bb3198bb61f3dc96365a99b7141c0b85720614de4640afa76458d27484e1353a","up":true},{"one":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","other":"8ea97f6abd40d70717688c5dd38daf6f6b12005df21ae397dfb700fbfe3447ac","up":true},{"one":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","other":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","up":true},{"one":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","other":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","up":true},{"one":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","other":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","up":true},{"one":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","other":"f1ad1843560ead294e9d60c9bfc99d8a827f2cfe557e0f9fe7ebf82aec56926d","up":true},{"one":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","other":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","up":true},{"one":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","other":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","up":true},{"one":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","other":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","up":true},{"one":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","other":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","up":true},{"one":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","other":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","up":true},{"one":"9a09aa168f7f4ebd90652d6900ace1ba88b8785290b2687783d4a23e9bdb90ee","other":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","up":true},{"one":"294066bf056b1530308d096871b08a046086abcbc7ed7a0a97d9fb46cccd6003","other":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","up":true},{"one":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","other":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","up":true},{"one":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","other":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","up":true},{"one":"67ee67b8f4d3ee50d8cdd027ba96e4b2f8ef9017bac217bd5a2d9dca35325519","other":"69d8801f9f19fec51b636cc27fca2c93e67dac8f3650aea35259d01293d5c9e6","up":true},{"one":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","other":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","up":true},{"one":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","other":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","up":true},{"one":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","other":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","up":true},{"one":"7aef1625409f479ee1abad565f8d7e73b17714e77f54d1ac1b2fe80382a058cc","other":"69d8801f9f19fec51b636cc27fca2c93e67dac8f3650aea35259d01293d5c9e6","up":true},{"one":"1254b1e5c9e80b367520bfefc35cf32a60aff56c48315107c4b07aa1a7866af9","other":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","up":true},{"one":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","other":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","up":true},{"one":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","other":"307c36a947f5c4c597075d8346a5db6133fbd2c14da099f85c1d2b7d6d8331f2","up":true},{"one":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","other":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","up":true},{"one":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","other":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","up":false},{"one":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","other":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","up":true},{"one":"d0b76e826a6eaeabdc060a0588bd1becc1901b60a8eb4e8adbe61107883a2987","other":"d777e45233aa4d8b723f6a6aa9cbf5de4c0b19f6fd00af6d538b67ec01b74ab5","up":true},{"one":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","other":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","up":true},{"one":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","other":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","up":true},{"one":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","other":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","up":true},{"one":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","other":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","up":true},{"one":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","other":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","up":true},{"one":"96419a246f5ca44f2796d91ccc55b3ac477e1754e74d202cdd133e6b5b96051b","other":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","up":true},{"one":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","other":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","up":true},{"one":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","other":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","up":true},{"one":"85642219f8bb8ce048cfce5aecddc8acd035afa035a0137c354f5b6fc3e3889d","other":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","up":true},{"one":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","other":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","up":true},{"one":"715b38eca0c630aad73e5f7d65453c6666f459994e3cc4bba586af75d78e8fe4","other":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","up":true},{"one":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","other":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","up":true},{"one":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","other":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","up":true},{"one":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","other":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","up":true},{"one":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","other":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","up":true},{"one":"48ec978577e2dbf0448ab119a3821be5d25dbf5a295fd4f695b9311429538a26","other":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","up":true},{"one":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","other":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","up":true},{"one":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","other":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","up":true},{"one":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","other":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","up":true},{"one":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","other":"57657e7b7d2642c6e5daefd27000d0da41982491486fd1aee57d0f99f072a471","up":true},{"one":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","other":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","up":true},{"one":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","other":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","up":true},{"one":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","other":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","up":true},{"one":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","other":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","up":true},{"one":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","other":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","up":true},{"one":"0a3fe6516edff33cde6afc46c6174a8562f17454b6f3ad41be8c12ebf1b0a07d","other":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","up":true},{"one":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","other":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","up":true},{"one":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","other":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","up":true},{"one":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","other":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","up":true},{"one":"e54d69f3bb061c62dede6e5a8704700bf9b8063edead7c755c9586f4d96754f1","other":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","up":true},{"one":"6923d78400f277fe29c35ba38a416edd7c960cc75d94c35aa3446b4d5e2f6c3b","other":"510c70916948815cd32759c451515bb024e38e0db8bb128710396978dc705a67","up":true},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","up":true},{"one":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","other":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","up":true},{"one":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","other":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","up":true},{"one":"67ee67b8f4d3ee50d8cdd027ba96e4b2f8ef9017bac217bd5a2d9dca35325519","other":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","up":false},{"one":"7aef1625409f479ee1abad565f8d7e73b17714e77f54d1ac1b2fe80382a058cc","other":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","up":false},{"one":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","other":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","up":true},{"one":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","other":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","up":true},{"one":"510c70916948815cd32759c451515bb024e38e0db8bb128710396978dc705a67","other":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","up":true},{"one":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","other":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","up":true},{"one":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","other":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","up":true},{"one":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","other":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","up":true},{"one":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","other":"3ab20836f88aab49491e82cfd1f1e8a59b5794cf91644b663d40cb22c5460be4","up":false},{"one":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","other":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","up":true},{"one":"69d8801f9f19fec51b636cc27fca2c93e67dac8f3650aea35259d01293d5c9e6","other":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","up":true},{"one":"d96e430326c7fc91fcd19c4e85b472a41ba24317ff09e5bd8eb2bd7a306499be","other":"d0b76e826a6eaeabdc060a0588bd1becc1901b60a8eb4e8adbe61107883a2987","up":true},{"one":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","other":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","up":false},{"one":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","other":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","up":true},{"one":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","other":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","up":true},{"one":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","other":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","up":true},{"one":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","other":"3ef6bb1dcd37360fe4a463b70add6cd4ba112a293112911b2304ab05b4c1b543","up":true},{"one":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","other":"d56025bb110210216fdc8f86ad205607c6fb4744d3d444931aa17512a854356a","up":true},{"one":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","other":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","up":true},{"one":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","other":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","up":true},{"one":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","other":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","up":true},{"one":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","other":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","up":true},{"one":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","other":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","up":true},{"one":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","other":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","up":true},{"one":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","other":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","up":true},{"one":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","other":"b7aa334a49788734068ac73e4b11bc554776217500c24eb76a85fe02f55ef037","up":true},{"one":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","other":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","up":true},{"one":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","other":"b820b318a3a12a7a7f45d06fff7f5d7e3ca2ca8097c396c1d76c735e1c915cc0","up":true},{"one":"bb3198bb61f3dc96365a99b7141c0b85720614de4640afa76458d27484e1353a","other":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","up":true},{"one":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","other":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","up":true},{"one":"aa7876d43dd0ce40f421f68c0d13c8c718d7a7a5e7c567dd20d87d19934cdc05","other":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","up":true},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","up":true},{"one":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","other":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","up":true},{"one":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","other":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","up":true},{"one":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","other":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","up":true},{"one":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","other":"d0443f24523af0eec6c20b21d84b2d54cb0ba8d4d1a205b41328bb22e8d3cd63","up":true},{"one":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","other":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","up":true},{"one":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","other":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","up":true},{"one":"7aef1625409f479ee1abad565f8d7e73b17714e77f54d1ac1b2fe80382a058cc","other":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","up":true},{"one":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","other":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","up":true},{"one":"208e648a32b59b77c2abbeac0402c8d009485a19dd4a04a216cd91d0c6713929","other":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","up":true},{"one":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","other":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","up":true},{"one":"67ee67b8f4d3ee50d8cdd027ba96e4b2f8ef9017bac217bd5a2d9dca35325519","other":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","up":true},{"one":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","other":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","up":true},{"one":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","other":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","up":true},{"one":"05a4778e1be7da949c1a7bffa3408020b66b4ee3c98e1f62e35d212c77e83ada","other":"0c728c174a25e11bc171d7d35eaaa3974859a4dbf7a64e1d9c9da6b3ad63b21a","up":true},{"one":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","other":"aa7876d43dd0ce40f421f68c0d13c8c718d7a7a5e7c567dd20d87d19934cdc05","up":true},{"one":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","other":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","up":true},{"one":"d60644fb69edf1d360a5b3d480030df97600a3d94a189da98b58c43758c98331","other":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","up":true},{"one":"3c5ae3bb462b109222b4a1ac1cf9b04508fcd53a2d13796880965e99e0216ee8","other":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","up":true},{"one":"c95a481b19583fc85e9b079fa0b5cf51eb46e71b9f57fc6df2e3b78389d8de2f","other":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","up":true},{"one":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","other":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","up":true},{"one":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","other":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","up":true},{"one":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","other":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","up":true},{"one":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","other":"8ea97f6abd40d70717688c5dd38daf6f6b12005df21ae397dfb700fbfe3447ac","up":true},{"one":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","other":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","up":true},{"one":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","other":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","up":true},{"one":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","other":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","up":false},{"one":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","other":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","up":true},{"one":"e54d69f3bb061c62dede6e5a8704700bf9b8063edead7c755c9586f4d96754f1","other":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","up":true},{"one":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","other":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","up":true},{"one":"1587ed904df26ed711e2235fea41e5812a7d33ce5dab2ebb4e88515b18f43d6e","other":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","up":true},{"one":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","other":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","up":true},{"one":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","other":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","up":true},{"one":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","other":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","up":true},{"one":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","other":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","up":true},{"one":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","other":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","up":false},{"one":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","other":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","up":false},{"one":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","other":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","up":true},{"one":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","other":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","up":false},{"one":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","other":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","up":false},{"one":"5b308b25cfe30d0b68784dc73be464dfa85a8e49ddd6f6271ff0ee2b63798928","other":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","up":true},{"one":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","other":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","up":false},{"one":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","other":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","up":true},{"one":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","other":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","up":true},{"one":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","other":"0020768d96fca0cf47a9632ca25ff794c996042f07a7242ec7c70fbc75e0e45e","up":true},{"one":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","other":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","up":true},{"one":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","other":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","up":true},{"one":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","other":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","up":false},{"one":"0a3fe6516edff33cde6afc46c6174a8562f17454b6f3ad41be8c12ebf1b0a07d","other":"0020768d96fca0cf47a9632ca25ff794c996042f07a7242ec7c70fbc75e0e45e","up":true},{"one":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","other":"03059077d3fadf62794756f6947fc32ed00d82162dbe3e6ebcd32952bd421321","up":true},{"one":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","other":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","up":true},{"one":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","other":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","up":true},{"one":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","other":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","up":true},{"one":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","other":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","up":true},{"one":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","other":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","up":true},{"one":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","other":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","up":true},{"one":"294066bf056b1530308d096871b08a046086abcbc7ed7a0a97d9fb46cccd6003","other":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","up":false},{"one":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","other":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","up":true},{"one":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","other":"e4e95f8381568c1cba7e8f64dd710554b923241a1c988dddb42a2cd96434705f","up":true},{"one":"d0b76e826a6eaeabdc060a0588bd1becc1901b60a8eb4e8adbe61107883a2987","other":"df52d5de0f5e69e7e4c9e0ffcec1f9df86d38044bb57e5a0cb18bde354ecb66d","up":true},{"one":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","other":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","up":true},{"one":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","other":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","up":true},{"one":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","other":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","up":true},{"one":"3ef6bb1dcd37360fe4a463b70add6cd4ba112a293112911b2304ab05b4c1b543","other":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","up":true},{"one":"715b38eca0c630aad73e5f7d65453c6666f459994e3cc4bba586af75d78e8fe4","other":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","up":true},{"one":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","other":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","up":true},{"one":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","other":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","up":false},{"one":"e2554d1bf145ec0e7027ca8e8ba0c85fc697bc4b54e36a6cdc53f1efa37a6634","other":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","up":true},{"one":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","other":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","up":true},{"one":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","other":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","up":true},{"one":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","other":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","up":true},{"one":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","other":"c7fa3f5dd856ac0cd8fc3f2933180cfe1e605b31c630731f3e0a00d392f4097a","up":true},{"one":"6701b38360d0a72ddc355c17385af34b7ed9eeec00bc3867e162b5571af69e3f","other":"7ec4083eca4749caa706ac08afc0c91cdcec42f18e4d360f18695a79de362115","up":true},{"one":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","other":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","up":false},{"one":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","other":"57657e7b7d2642c6e5daefd27000d0da41982491486fd1aee57d0f99f072a471","up":true},{"one":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","other":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","up":true},{"one":"6923d78400f277fe29c35ba38a416edd7c960cc75d94c35aa3446b4d5e2f6c3b","other":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","up":true},{"one":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","other":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","up":true},{"one":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","other":"e4e95f8381568c1cba7e8f64dd710554b923241a1c988dddb42a2cd96434705f","up":true},{"one":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","other":"574101500f64e9a1bfbf31f4f379a60a0ea9bee449af1cd29962214dd960b3c6","up":true},{"one":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","other":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","up":true},{"one":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","other":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","up":true},{"one":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","other":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","up":true},{"one":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","other":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","up":true},{"one":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","other":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","up":true},{"one":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","other":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","up":true},{"one":"0020768d96fca0cf47a9632ca25ff794c996042f07a7242ec7c70fbc75e0e45e","other":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","up":true},{"one":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","other":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","up":true},{"one":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","other":"05a4778e1be7da949c1a7bffa3408020b66b4ee3c98e1f62e35d212c77e83ada","up":true},{"one":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","other":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","up":true},{"one":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","other":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","up":true},{"one":"e1909ff70b17f666d74c73e8d9803df1e6e20733e4aeecdc78e444b6632d92c9","other":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","up":true},{"one":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","other":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","up":true},{"one":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","other":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","up":true},{"one":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","other":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","up":true},{"one":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","other":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","up":true},{"one":"3ab20836f88aab49491e82cfd1f1e8a59b5794cf91644b663d40cb22c5460be4","other":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","up":true},{"one":"e54d69f3bb061c62dede6e5a8704700bf9b8063edead7c755c9586f4d96754f1","other":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","up":true},{"one":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","other":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","up":true},{"one":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","other":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","up":true},{"one":"510c70916948815cd32759c451515bb024e38e0db8bb128710396978dc705a67","other":"5b308b25cfe30d0b68784dc73be464dfa85a8e49ddd6f6271ff0ee2b63798928","up":true},{"one":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","other":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","up":true},{"one":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","other":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","up":false},{"one":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","other":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","up":true},{"one":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","other":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","up":true},{"one":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","other":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","up":true},{"one":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","other":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","up":true},{"one":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","other":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","up":false},{"one":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","other":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","up":true},{"one":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","other":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","up":false},{"one":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","other":"bb3198bb61f3dc96365a99b7141c0b85720614de4640afa76458d27484e1353a","up":true},{"one":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","other":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","up":true},{"one":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","other":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","up":true},{"one":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","other":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","up":true},{"one":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","other":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","up":true},{"one":"7ec4083eca4749caa706ac08afc0c91cdcec42f18e4d360f18695a79de362115","other":"7aef1625409f479ee1abad565f8d7e73b17714e77f54d1ac1b2fe80382a058cc","up":true},{"one":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","other":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","up":true},{"one":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","other":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","up":true},{"one":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","other":"3388123bad3bf1debf51c5921c09d06f7627d05f6115af39d92dbe9b2f2313dc","up":true},{"one":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","other":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","up":true},{"one":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","other":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","up":true},{"one":"3c5ae3bb462b109222b4a1ac1cf9b04508fcd53a2d13796880965e99e0216ee8","other":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","up":true},{"one":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","other":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","up":true},{"one":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","other":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","up":true},{"one":"6701b38360d0a72ddc355c17385af34b7ed9eeec00bc3867e162b5571af69e3f","other":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","up":true},{"one":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","other":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","up":false},{"one":"1254b1e5c9e80b367520bfefc35cf32a60aff56c48315107c4b07aa1a7866af9","other":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","up":true},{"one":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","other":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","up":true},{"one":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","other":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","up":true},{"one":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","other":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","up":true},{"one":"4b8acc021ddea1fff036b4b0e5939c38507ef792490701f7b341e4ff10a78567","other":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","up":true},{"one":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","other":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","up":true},{"one":"9a09aa168f7f4ebd90652d6900ace1ba88b8785290b2687783d4a23e9bdb90ee","other":"8ea97f6abd40d70717688c5dd38daf6f6b12005df21ae397dfb700fbfe3447ac","up":true},{"one":"48df69314a57a6ab2fc31108104aae2242ad2a941d2f87119a151250344aee02","other":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","up":true},{"one":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","other":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","up":true},{"one":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","other":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","up":true},{"one":"d56025bb110210216fdc8f86ad205607c6fb4744d3d444931aa17512a854356a","other":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","up":true},{"one":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","other":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","up":true},{"one":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","other":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","up":true},{"one":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","other":"d60644fb69edf1d360a5b3d480030df97600a3d94a189da98b58c43758c98331","up":true},{"one":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","other":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","up":true},{"one":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","other":"3ef6bb1dcd37360fe4a463b70add6cd4ba112a293112911b2304ab05b4c1b543","up":true},{"one":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","other":"020f57c369b6521552f7498e444aed21ac57832ed3ffaa7b45f607793ea1a86c","up":true},{"one":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","other":"c7fa3f5dd856ac0cd8fc3f2933180cfe1e605b31c630731f3e0a00d392f4097a","up":true},{"one":"d777e45233aa4d8b723f6a6aa9cbf5de4c0b19f6fd00af6d538b67ec01b74ab5","other":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","up":true},{"one":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","other":"d60644fb69edf1d360a5b3d480030df97600a3d94a189da98b58c43758c98331","up":true},{"one":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","other":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","up":true},{"one":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","other":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","up":false},{"one":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","other":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","up":true},{"one":"d96e430326c7fc91fcd19c4e85b472a41ba24317ff09e5bd8eb2bd7a306499be","other":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","up":true},{"one":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","other":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","up":true},{"one":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","other":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","up":true},{"one":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","other":"d0443f24523af0eec6c20b21d84b2d54cb0ba8d4d1a205b41328bb22e8d3cd63","up":true},{"one":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","other":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","up":true},{"one":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","other":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","up":true},{"one":"d60644fb69edf1d360a5b3d480030df97600a3d94a189da98b58c43758c98331","other":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","up":true},{"one":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","other":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","up":true},{"one":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","other":"b7aa334a49788734068ac73e4b11bc554776217500c24eb76a85fe02f55ef037","up":true},{"one":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","other":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","up":true},{"one":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","other":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","up":true},{"one":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","other":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","up":true},{"one":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","other":"0a3fe6516edff33cde6afc46c6174a8562f17454b6f3ad41be8c12ebf1b0a07d","up":false},{"one":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","other":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","up":true},{"one":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","other":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","up":true},{"one":"7aef1625409f479ee1abad565f8d7e73b17714e77f54d1ac1b2fe80382a058cc","other":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","up":true},{"one":"af8fb611627063b408d2b96c1ab68fafb223a86d947de3dac3a59649cd13e0cf","other":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","up":true},{"one":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","other":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","up":false},{"one":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","other":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","up":true},{"one":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","other":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","up":true},{"one":"1587ed904df26ed711e2235fea41e5812a7d33ce5dab2ebb4e88515b18f43d6e","other":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","up":false},{"one":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","other":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","up":true},{"one":"715b38eca0c630aad73e5f7d65453c6666f459994e3cc4bba586af75d78e8fe4","other":"7ec4083eca4749caa706ac08afc0c91cdcec42f18e4d360f18695a79de362115","up":true},{"one":"3388123bad3bf1debf51c5921c09d06f7627d05f6115af39d92dbe9b2f2313dc","other":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","up":true},{"one":"05a4778e1be7da949c1a7bffa3408020b66b4ee3c98e1f62e35d212c77e83ada","other":"1a85e13e2ad82c8ffa10ad52b382947e638036120188ae611fb1fb333cb5232b","up":true},{"one":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","other":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","up":true},{"one":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","other":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","up":true},{"one":"07a6873c7eadb651196a76ab30241cc1c984549c5086f1c3bb1be7bd493346be","other":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","up":true},{"one":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","other":"34e48bd6562a308ccf6a4a0257f253e908e97d16defe2f1a31e046d5558a4769","up":true},{"one":"03059077d3fadf62794756f6947fc32ed00d82162dbe3e6ebcd32952bd421321","other":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","up":true},{"one":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","other":"e2554d1bf145ec0e7027ca8e8ba0c85fc697bc4b54e36a6cdc53f1efa37a6634","up":true},{"one":"bd39cdc673c4184193347f0efd74bb894f7f36a994c154ffe3dadcd456efe460","other":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","up":true},{"one":"f1ad1843560ead294e9d60c9bfc99d8a827f2cfe557e0f9fe7ebf82aec56926d","other":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","up":true},{"one":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","other":"e1909ff70b17f666d74c73e8d9803df1e6e20733e4aeecdc78e444b6632d92c9","up":true},{"one":"c95a481b19583fc85e9b079fa0b5cf51eb46e71b9f57fc6df2e3b78389d8de2f","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"f0b803dcfec89e1d68739ca05cf0c9c04e80a2b1cd47496a83168a71184fd4cf","other":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","up":true},{"one":"b40da884b5e0ab429b3608ce6030c0f7f542d008713ed1a444c70d9c00989ecf","other":"b7aa334a49788734068ac73e4b11bc554776217500c24eb76a85fe02f55ef037","up":true},{"one":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","other":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","up":true},{"one":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","other":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","up":true},{"one":"6923d78400f277fe29c35ba38a416edd7c960cc75d94c35aa3446b4d5e2f6c3b","other":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","up":false},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","up":false},{"one":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","other":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","up":true},{"one":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","other":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","up":true},{"one":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","other":"e4e95f8381568c1cba7e8f64dd710554b923241a1c988dddb42a2cd96434705f","up":true},{"one":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","other":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","up":true},{"one":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","other":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","up":true},{"one":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","other":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","up":true},{"one":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","other":"f1ad1843560ead294e9d60c9bfc99d8a827f2cfe557e0f9fe7ebf82aec56926d","up":true},{"one":"d0b76e826a6eaeabdc060a0588bd1becc1901b60a8eb4e8adbe61107883a2987","other":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","up":true},{"one":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","other":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","up":true},{"one":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","other":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","up":true},{"one":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","other":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","up":true},{"one":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","other":"0020768d96fca0cf47a9632ca25ff794c996042f07a7242ec7c70fbc75e0e45e","up":true},{"one":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","other":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","up":true},{"one":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","other":"e4e95f8381568c1cba7e8f64dd710554b923241a1c988dddb42a2cd96434705f","up":true},{"one":"3ab20836f88aab49491e82cfd1f1e8a59b5794cf91644b663d40cb22c5460be4","other":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","up":true},{"one":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","other":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","up":true},{"one":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","other":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","up":false},{"one":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","other":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","up":true},{"one":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","other":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","up":true},{"one":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","other":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","up":false},{"one":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","other":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","up":true},{"one":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","other":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","up":true},{"one":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","other":"f0b803dcfec89e1d68739ca05cf0c9c04e80a2b1cd47496a83168a71184fd4cf","up":true},{"one":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","other":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","up":true},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","up":true},{"one":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","other":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","up":true},{"one":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","other":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","up":true},{"one":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","other":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","up":true},{"one":"c7fa3f5dd856ac0cd8fc3f2933180cfe1e605b31c630731f3e0a00d392f4097a","other":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","up":true},{"one":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","other":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","up":true},{"one":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","other":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","up":true},{"one":"48df69314a57a6ab2fc31108104aae2242ad2a941d2f87119a151250344aee02","other":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","up":true},{"one":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","other":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","up":true},{"one":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","other":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","up":true},{"one":"413b15ae74e38b8610dd5edd52e05630f8acfa4139a1b361775afa8e748ebbfc","other":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","up":true},{"one":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","other":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","up":true},{"one":"d777e45233aa4d8b723f6a6aa9cbf5de4c0b19f6fd00af6d538b67ec01b74ab5","other":"d60644fb69edf1d360a5b3d480030df97600a3d94a189da98b58c43758c98331","up":true},{"one":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","other":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","up":true},{"one":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","other":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","up":true},{"one":"510c70916948815cd32759c451515bb024e38e0db8bb128710396978dc705a67","other":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","up":true},{"one":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","other":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","up":true},{"one":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","other":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","up":true},{"one":"d60644fb69edf1d360a5b3d480030df97600a3d94a189da98b58c43758c98331","other":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","up":true},{"one":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"f0b803dcfec89e1d68739ca05cf0c9c04e80a2b1cd47496a83168a71184fd4cf","other":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","up":true},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","up":true},{"one":"69d8801f9f19fec51b636cc27fca2c93e67dac8f3650aea35259d01293d5c9e6","other":"6f1c48453e1bfb9c474d7f888d8c437c75db569e2c900867c4eaf3e6504847aa","up":true},{"one":"307c36a947f5c4c597075d8346a5db6133fbd2c14da099f85c1d2b7d6d8331f2","other":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","up":true},{"one":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","other":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","up":true},{"one":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","other":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","up":true},{"one":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","other":"b40da884b5e0ab429b3608ce6030c0f7f542d008713ed1a444c70d9c00989ecf","up":true},{"one":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","other":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","up":true},{"one":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","other":"85642219f8bb8ce048cfce5aecddc8acd035afa035a0137c354f5b6fc3e3889d","up":true},{"one":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","other":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","up":true},{"one":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","other":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","up":true},{"one":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","other":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","up":true},{"one":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","other":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","up":true},{"one":"208e648a32b59b77c2abbeac0402c8d009485a19dd4a04a216cd91d0c6713929","other":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","up":true},{"one":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","other":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","up":true},{"one":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","other":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","up":true},{"one":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","other":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","up":true},{"one":"07a6873c7eadb651196a76ab30241cc1c984549c5086f1c3bb1be7bd493346be","other":"00c83e87900972472e247a13acd65450a014a4edf0ab1548199699edeb50886c","up":true},{"one":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","other":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","up":true},{"one":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","other":"e4e95f8381568c1cba7e8f64dd710554b923241a1c988dddb42a2cd96434705f","up":true},{"one":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","other":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","up":true},{"one":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","other":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","up":true},{"one":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","other":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","up":true},{"one":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","other":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","up":true},{"one":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","other":"e2554d1bf145ec0e7027ca8e8ba0c85fc697bc4b54e36a6cdc53f1efa37a6634","up":true},{"one":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","other":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","up":true},{"one":"e54d69f3bb061c62dede6e5a8704700bf9b8063edead7c755c9586f4d96754f1","other":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","up":true},{"one":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","other":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","up":true},{"one":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","other":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","up":true},{"one":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","other":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","up":true},{"one":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","other":"4b8acc021ddea1fff036b4b0e5939c38507ef792490701f7b341e4ff10a78567","up":true},{"one":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","other":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","up":true},{"one":"d0443f24523af0eec6c20b21d84b2d54cb0ba8d4d1a205b41328bb22e8d3cd63","other":"d0b76e826a6eaeabdc060a0588bd1becc1901b60a8eb4e8adbe61107883a2987","up":true},{"one":"7aef1625409f479ee1abad565f8d7e73b17714e77f54d1ac1b2fe80382a058cc","other":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","up":true},{"one":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","other":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","up":true},{"one":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","other":"7aef1625409f479ee1abad565f8d7e73b17714e77f54d1ac1b2fe80382a058cc","up":true},{"one":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","other":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","up":true},{"one":"e1909ff70b17f666d74c73e8d9803df1e6e20733e4aeecdc78e444b6632d92c9","other":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","up":true},{"one":"34e48bd6562a308ccf6a4a0257f253e908e97d16defe2f1a31e046d5558a4769","other":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","up":true},{"one":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","other":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","up":true},{"one":"d96e430326c7fc91fcd19c4e85b472a41ba24317ff09e5bd8eb2bd7a306499be","other":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","up":true},{"one":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","other":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","up":true},{"one":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","other":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","up":true},{"one":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","other":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","up":true},{"one":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","other":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","up":true},{"one":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","other":"f0b803dcfec89e1d68739ca05cf0c9c04e80a2b1cd47496a83168a71184fd4cf","up":true},{"one":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","other":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","up":true},{"one":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","other":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","up":true},{"one":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","other":"b7aa334a49788734068ac73e4b11bc554776217500c24eb76a85fe02f55ef037","up":true},{"one":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","other":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","up":true},{"one":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","other":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","up":true},{"one":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","other":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","up":true},{"one":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","other":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","up":true},{"one":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","other":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","up":true},{"one":"5b308b25cfe30d0b68784dc73be464dfa85a8e49ddd6f6271ff0ee2b63798928","other":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","up":true},{"one":"307c36a947f5c4c597075d8346a5db6133fbd2c14da099f85c1d2b7d6d8331f2","other":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","up":true},{"one":"6f1c48453e1bfb9c474d7f888d8c437c75db569e2c900867c4eaf3e6504847aa","other":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","up":true},{"one":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","other":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","up":true},{"one":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","other":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","up":true},{"one":"6923d78400f277fe29c35ba38a416edd7c960cc75d94c35aa3446b4d5e2f6c3b","other":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","up":true},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","up":true},{"one":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","other":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","up":false},{"one":"c7fa3f5dd856ac0cd8fc3f2933180cfe1e605b31c630731f3e0a00d392f4097a","other":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","up":true},{"one":"9a09aa168f7f4ebd90652d6900ace1ba88b8785290b2687783d4a23e9bdb90ee","other":"83d0ffd43e2469b9c3498862d0186e413cc4d205f7854bf9dd73c4d74379217c","up":true},{"one":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","other":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","up":true},{"one":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","other":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","up":true},{"one":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","other":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","up":true},{"one":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","other":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","up":true},{"one":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","other":"48df69314a57a6ab2fc31108104aae2242ad2a941d2f87119a151250344aee02","up":true},{"one":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","other":"67ee67b8f4d3ee50d8cdd027ba96e4b2f8ef9017bac217bd5a2d9dca35325519","up":true},{"one":"05a4778e1be7da949c1a7bffa3408020b66b4ee3c98e1f62e35d212c77e83ada","other":"1953df7e29813b00b0137118623b6ce34113d17d4f9aaa608b81f2ee58ea3fe3","up":true},{"one":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","other":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","up":true},{"one":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","other":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","up":true},{"one":"b40da884b5e0ab429b3608ce6030c0f7f542d008713ed1a444c70d9c00989ecf","other":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","up":true},{"one":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","other":"48ec978577e2dbf0448ab119a3821be5d25dbf5a295fd4f695b9311429538a26","up":true},{"one":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","other":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","up":true},{"one":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","other":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","up":true},{"one":"1587ed904df26ed711e2235fea41e5812a7d33ce5dab2ebb4e88515b18f43d6e","other":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","up":true},{"one":"0c728c174a25e11bc171d7d35eaaa3974859a4dbf7a64e1d9c9da6b3ad63b21a","other":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","up":true},{"one":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","other":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","up":true},{"one":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","other":"48ec978577e2dbf0448ab119a3821be5d25dbf5a295fd4f695b9311429538a26","up":true},{"one":"d0b76e826a6eaeabdc060a0588bd1becc1901b60a8eb4e8adbe61107883a2987","other":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","up":true},{"one":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","other":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","up":true},{"one":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","other":"0c728c174a25e11bc171d7d35eaaa3974859a4dbf7a64e1d9c9da6b3ad63b21a","up":true},{"one":"d0443f24523af0eec6c20b21d84b2d54cb0ba8d4d1a205b41328bb22e8d3cd63","other":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","up":true},{"one":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","other":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","up":false},{"one":"208e648a32b59b77c2abbeac0402c8d009485a19dd4a04a216cd91d0c6713929","other":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","up":false},{"one":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","other":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","up":true},{"one":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","other":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","up":true},{"one":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","other":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","up":true},{"one":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","other":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","up":true},{"one":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","other":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","up":true},{"one":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","other":"3ab20836f88aab49491e82cfd1f1e8a59b5794cf91644b663d40cb22c5460be4","up":true},{"one":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","other":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","up":true},{"one":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","other":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","up":true},{"one":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","other":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","up":true},{"one":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","other":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","up":true},{"one":"e54d69f3bb061c62dede6e5a8704700bf9b8063edead7c755c9586f4d96754f1","other":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","up":true},{"one":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","other":"34e48bd6562a308ccf6a4a0257f253e908e97d16defe2f1a31e046d5558a4769","up":true},{"one":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","other":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","up":true},{"one":"57657e7b7d2642c6e5daefd27000d0da41982491486fd1aee57d0f99f072a471","other":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","up":true},{"one":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","other":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","up":true},{"one":"574101500f64e9a1bfbf31f4f379a60a0ea9bee449af1cd29962214dd960b3c6","other":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","up":true},{"one":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","other":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","up":true},{"one":"f1ad1843560ead294e9d60c9bfc99d8a827f2cfe557e0f9fe7ebf82aec56926d","other":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","up":true},{"one":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","other":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","up":true},{"one":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","other":"1953df7e29813b00b0137118623b6ce34113d17d4f9aaa608b81f2ee58ea3fe3","up":true},{"one":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","other":"d60644fb69edf1d360a5b3d480030df97600a3d94a189da98b58c43758c98331","up":true},{"one":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","other":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","up":true},{"one":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","other":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","up":true},{"one":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","other":"aa7876d43dd0ce40f421f68c0d13c8c718d7a7a5e7c567dd20d87d19934cdc05","up":true},{"one":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","other":"574101500f64e9a1bfbf31f4f379a60a0ea9bee449af1cd29962214dd960b3c6","up":true},{"one":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","other":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","up":true},{"one":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","other":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","up":true},{"one":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","other":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","up":true},{"one":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","other":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","up":true},{"one":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","other":"574101500f64e9a1bfbf31f4f379a60a0ea9bee449af1cd29962214dd960b3c6","up":true},{"one":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","other":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","up":true},{"one":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","other":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","up":true},{"one":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","other":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","up":true},{"one":"8ea97f6abd40d70717688c5dd38daf6f6b12005df21ae397dfb700fbfe3447ac","other":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","up":true},{"one":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","other":"c7fa3f5dd856ac0cd8fc3f2933180cfe1e605b31c630731f3e0a00d392f4097a","up":true},{"one":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","other":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","up":false},{"one":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","other":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","up":true},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"b40da884b5e0ab429b3608ce6030c0f7f542d008713ed1a444c70d9c00989ecf","up":true},{"one":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","other":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","up":true},{"one":"c7fa3f5dd856ac0cd8fc3f2933180cfe1e605b31c630731f3e0a00d392f4097a","other":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","up":true},{"one":"67ee67b8f4d3ee50d8cdd027ba96e4b2f8ef9017bac217bd5a2d9dca35325519","other":"6701b38360d0a72ddc355c17385af34b7ed9eeec00bc3867e162b5571af69e3f","up":true},{"one":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","other":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","up":true},{"one":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","other":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","up":true},{"one":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","other":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","up":true},{"one":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","other":"507060be030344850df6fc76719d503254b38d96d77eb25f49ca9d955944b10a","up":true},{"one":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","other":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","up":true},{"one":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","other":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","up":true},{"one":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","other":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","up":true},{"one":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","other":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","up":true},{"one":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","other":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","up":false},{"one":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","other":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","up":true},{"one":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","other":"413b15ae74e38b8610dd5edd52e05630f8acfa4139a1b361775afa8e748ebbfc","up":true},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","up":true},{"one":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","other":"f0b803dcfec89e1d68739ca05cf0c9c04e80a2b1cd47496a83168a71184fd4cf","up":true},{"one":"b40da884b5e0ab429b3608ce6030c0f7f542d008713ed1a444c70d9c00989ecf","other":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","up":true},{"one":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","other":"4b8acc021ddea1fff036b4b0e5939c38507ef792490701f7b341e4ff10a78567","up":true},{"one":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","other":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","up":true},{"one":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","other":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","up":true},{"one":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","other":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","up":false},{"one":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","other":"83d0ffd43e2469b9c3498862d0186e413cc4d205f7854bf9dd73c4d74379217c","up":true},{"one":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","other":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","up":true},{"one":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","other":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","up":true},{"one":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","other":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","up":true},{"one":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","other":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","up":false},{"one":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","other":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","up":true},{"one":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","other":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","up":true},{"one":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","other":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","up":true},{"one":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","other":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","up":true},{"one":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","other":"8ea97f6abd40d70717688c5dd38daf6f6b12005df21ae397dfb700fbfe3447ac","up":true},{"one":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","other":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","up":true},{"one":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","other":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","up":true},{"one":"510c70916948815cd32759c451515bb024e38e0db8bb128710396978dc705a67","other":"507060be030344850df6fc76719d503254b38d96d77eb25f49ca9d955944b10a","up":true},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"3ab20836f88aab49491e82cfd1f1e8a59b5794cf91644b663d40cb22c5460be4","up":true},{"one":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","other":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","up":true},{"one":"1587ed904df26ed711e2235fea41e5812a7d33ce5dab2ebb4e88515b18f43d6e","other":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","up":true},{"one":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","other":"7aef1625409f479ee1abad565f8d7e73b17714e77f54d1ac1b2fe80382a058cc","up":true},{"one":"3c5ae3bb462b109222b4a1ac1cf9b04508fcd53a2d13796880965e99e0216ee8","other":"3ef6bb1dcd37360fe4a463b70add6cd4ba112a293112911b2304ab05b4c1b543","up":true},{"one":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","other":"aa7876d43dd0ce40f421f68c0d13c8c718d7a7a5e7c567dd20d87d19934cdc05","up":true},{"one":"7aef1625409f479ee1abad565f8d7e73b17714e77f54d1ac1b2fe80382a058cc","other":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","up":true},{"one":"48ec978577e2dbf0448ab119a3821be5d25dbf5a295fd4f695b9311429538a26","other":"48df69314a57a6ab2fc31108104aae2242ad2a941d2f87119a151250344aee02","up":true},{"one":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","other":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","up":true},{"one":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","other":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","up":true},{"one":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","other":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","up":true},{"one":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","other":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","up":false},{"one":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","other":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","up":true},{"one":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","other":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","up":true},{"one":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","other":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","up":true},{"one":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","other":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","up":true},{"one":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","other":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","up":true},{"one":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","other":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","up":true},{"one":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","other":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","up":true},{"one":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","other":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","up":true},{"one":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","other":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","up":true},{"one":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","other":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","up":true},{"one":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","other":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","up":true},{"one":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","other":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","up":true},{"one":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","other":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","up":true},{"one":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","other":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","up":true},{"one":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","other":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","up":true},{"one":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","other":"6f1c48453e1bfb9c474d7f888d8c437c75db569e2c900867c4eaf3e6504847aa","up":true},{"one":"67ee67b8f4d3ee50d8cdd027ba96e4b2f8ef9017bac217bd5a2d9dca35325519","other":"6f1c48453e1bfb9c474d7f888d8c437c75db569e2c900867c4eaf3e6504847aa","up":true},{"one":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","other":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","up":true},{"one":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","other":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","up":true},{"one":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","other":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","up":true},{"one":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","other":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","up":true},{"one":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","other":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","up":true},{"one":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","other":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","up":true},{"one":"05a4778e1be7da949c1a7bffa3408020b66b4ee3c98e1f62e35d212c77e83ada","other":"1254b1e5c9e80b367520bfefc35cf32a60aff56c48315107c4b07aa1a7866af9","up":true},{"one":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","other":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","up":true},{"one":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","other":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","up":true},{"one":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","other":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","up":false},{"one":"208e648a32b59b77c2abbeac0402c8d009485a19dd4a04a216cd91d0c6713929","other":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","up":true},{"one":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","other":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","up":true},{"one":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","other":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","up":true},{"one":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","other":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","up":true},{"one":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","other":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","up":true},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","up":true},{"one":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","other":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","up":true},{"one":"d0b76e826a6eaeabdc060a0588bd1becc1901b60a8eb4e8adbe61107883a2987","other":"c7fa3f5dd856ac0cd8fc3f2933180cfe1e605b31c630731f3e0a00d392f4097a","up":true},{"one":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","other":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","up":true},{"one":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","other":"f1ad1843560ead294e9d60c9bfc99d8a827f2cfe557e0f9fe7ebf82aec56926d","up":true},{"one":"d56025bb110210216fdc8f86ad205607c6fb4744d3d444931aa17512a854356a","other":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","up":true},{"one":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","other":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","up":true},{"one":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","other":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","up":true},{"one":"aa7876d43dd0ce40f421f68c0d13c8c718d7a7a5e7c567dd20d87d19934cdc05","other":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","up":true},{"one":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","other":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","up":true},{"one":"510c70916948815cd32759c451515bb024e38e0db8bb128710396978dc705a67","other":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","up":true},{"one":"a011c924104e072ec415cf72255167582e5d3f189f541066ba88b3ae0041b508","other":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","up":true},{"one":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","other":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","up":true},{"one":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","other":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","up":true},{"one":"3c5ae3bb462b109222b4a1ac1cf9b04508fcd53a2d13796880965e99e0216ee8","other":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","up":true},{"one":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","other":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","up":true},{"one":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","other":"c95a481b19583fc85e9b079fa0b5cf51eb46e71b9f57fc6df2e3b78389d8de2f","up":true},{"one":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","other":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","up":true},{"one":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","other":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","up":true},{"one":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","other":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","up":false},{"one":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","other":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","up":true},{"one":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","other":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","up":true},{"one":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","other":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","up":true},{"one":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","other":"d2ac9af919a7bfea9d0acd11bc63874beb8c7213941128ba7e7741c7e79e871f","up":true},{"one":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","other":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","up":true},{"one":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","other":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","up":false},{"one":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","other":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","up":true},{"one":"6701b38360d0a72ddc355c17385af34b7ed9eeec00bc3867e162b5571af69e3f","other":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","up":true},{"one":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","other":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","up":true},{"one":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","other":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","up":true},{"one":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","other":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","up":false},{"one":"af8fb611627063b408d2b96c1ab68fafb223a86d947de3dac3a59649cd13e0cf","other":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","up":true},{"one":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","other":"a011c924104e072ec415cf72255167582e5d3f189f541066ba88b3ae0041b508","up":true},{"one":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","other":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","up":true},{"one":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","other":"208e648a32b59b77c2abbeac0402c8d009485a19dd4a04a216cd91d0c6713929","up":true},{"one":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","other":"294066bf056b1530308d096871b08a046086abcbc7ed7a0a97d9fb46cccd6003","up":true},{"one":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","other":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","up":true},{"one":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","other":"aa7876d43dd0ce40f421f68c0d13c8c718d7a7a5e7c567dd20d87d19934cdc05","up":true},{"one":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","other":"294066bf056b1530308d096871b08a046086abcbc7ed7a0a97d9fb46cccd6003","up":true},{"one":"67ee67b8f4d3ee50d8cdd027ba96e4b2f8ef9017bac217bd5a2d9dca35325519","other":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","up":false},{"one":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","other":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","up":true},{"one":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","other":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","up":true},{"one":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","other":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","up":true},{"one":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","other":"c95a481b19583fc85e9b079fa0b5cf51eb46e71b9f57fc6df2e3b78389d8de2f","up":true},{"one":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","other":"0a3fe6516edff33cde6afc46c6174a8562f17454b6f3ad41be8c12ebf1b0a07d","up":false},{"one":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","other":"6701b38360d0a72ddc355c17385af34b7ed9eeec00bc3867e162b5571af69e3f","up":true},{"one":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","other":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","up":true},{"one":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","other":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","up":true},{"one":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","other":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","up":true},{"one":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","other":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","up":true},{"one":"85642219f8bb8ce048cfce5aecddc8acd035afa035a0137c354f5b6fc3e3889d","other":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","up":true},{"one":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","other":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","up":true},{"one":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","other":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","up":false},{"one":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","other":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","up":true},{"one":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","other":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","up":true},{"one":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","other":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","up":true},{"one":"413b15ae74e38b8610dd5edd52e05630f8acfa4139a1b361775afa8e748ebbfc","other":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","up":true},{"one":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","other":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","up":true},{"one":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","other":"96419a246f5ca44f2796d91ccc55b3ac477e1754e74d202cdd133e6b5b96051b","up":true},{"one":"d56025bb110210216fdc8f86ad205607c6fb4744d3d444931aa17512a854356a","other":"d60644fb69edf1d360a5b3d480030df97600a3d94a189da98b58c43758c98331","up":true},{"one":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","other":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","up":true},{"one":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","other":"3ef6bb1dcd37360fe4a463b70add6cd4ba112a293112911b2304ab05b4c1b543","up":true},{"one":"5b308b25cfe30d0b68784dc73be464dfa85a8e49ddd6f6271ff0ee2b63798928","other":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","up":true},{"one":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","other":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","up":true},{"one":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","other":"294066bf056b1530308d096871b08a046086abcbc7ed7a0a97d9fb46cccd6003","up":true},{"one":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","other":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","up":true},{"one":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","other":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","up":true},{"one":"510c70916948815cd32759c451515bb024e38e0db8bb128710396978dc705a67","other":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","up":true},{"one":"48df69314a57a6ab2fc31108104aae2242ad2a941d2f87119a151250344aee02","other":"4b8acc021ddea1fff036b4b0e5939c38507ef792490701f7b341e4ff10a78567","up":true},{"one":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","other":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","up":true},{"one":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","other":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","up":true},{"one":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","other":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","up":true},{"one":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","other":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","up":true},{"one":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","other":"1953df7e29813b00b0137118623b6ce34113d17d4f9aaa608b81f2ee58ea3fe3","up":true},{"one":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","other":"57ee6d3d13e021ae5bb1a7e82c4cbdfeb02164d4305c0b4619a5ea3184d19354","up":true},{"one":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","other":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","up":true},{"one":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","other":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","up":true},{"one":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","other":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","up":true},{"one":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","other":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","up":true},{"one":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","other":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","up":true},{"one":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","other":"d777e45233aa4d8b723f6a6aa9cbf5de4c0b19f6fd00af6d538b67ec01b74ab5","up":true},{"one":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","other":"e4e95f8381568c1cba7e8f64dd710554b923241a1c988dddb42a2cd96434705f","up":true},{"one":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","other":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","up":true},{"one":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","other":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","up":true},{"one":"bd39cdc673c4184193347f0efd74bb894f7f36a994c154ffe3dadcd456efe460","other":"b820b318a3a12a7a7f45d06fff7f5d7e3ca2ca8097c396c1d76c735e1c915cc0","up":true},{"one":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","other":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","up":true},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"b7aa334a49788734068ac73e4b11bc554776217500c24eb76a85fe02f55ef037","up":true},{"one":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","other":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","up":true},{"one":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","other":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","up":true},{"one":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","other":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","up":true},{"one":"f1ad1843560ead294e9d60c9bfc99d8a827f2cfe557e0f9fe7ebf82aec56926d","other":"f2f5e82cf81f07f8cf82c7171c5ee5a42b7963619fafd4295137357cea1b7bb0","up":true},{"one":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","other":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","up":true},{"one":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","other":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","up":true},{"one":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","other":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","up":true},{"one":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","other":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","up":true},{"one":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","other":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","up":true},{"one":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","other":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","up":true},{"one":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","other":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","up":true},{"one":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","other":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","up":true},{"one":"df52d5de0f5e69e7e4c9e0ffcec1f9df86d38044bb57e5a0cb18bde354ecb66d","other":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","up":true},{"one":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","other":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","up":true},{"one":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","other":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","up":true},{"one":"17a00226ac9b2bb0f83e9add9c0ca1bcc830a30e36b1462007fbe261090f7a9a","other":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","up":true},{"one":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","other":"715b38eca0c630aad73e5f7d65453c6666f459994e3cc4bba586af75d78e8fe4","up":true},{"one":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","other":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","up":true},{"one":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","other":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","up":true},{"one":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","other":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","up":true},{"one":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","other":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","up":true},{"one":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","other":"69d8801f9f19fec51b636cc27fca2c93e67dac8f3650aea35259d01293d5c9e6","up":true},{"one":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","other":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","up":true},{"one":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","other":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","up":false},{"one":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","other":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","up":true},{"one":"507060be030344850df6fc76719d503254b38d96d77eb25f49ca9d955944b10a","other":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","up":true},{"one":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","other":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","up":true},{"one":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","other":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","up":true},{"one":"41b2b713dba3a11683ad83d1cb4beb9ff4f5aaf49f2ad1ac2247c84a85d40a56","other":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","up":true},{"one":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","other":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","up":true},{"one":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","other":"91088ea79cd8f27da8879760dd21c363aa9be1135437c2b87464426874b597e5","up":true},{"one":"c7fa3f5dd856ac0cd8fc3f2933180cfe1e605b31c630731f3e0a00d392f4097a","other":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","up":true},{"one":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","other":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","up":true},{"one":"34e48bd6562a308ccf6a4a0257f253e908e97d16defe2f1a31e046d5558a4769","other":"307c36a947f5c4c597075d8346a5db6133fbd2c14da099f85c1d2b7d6d8331f2","up":true},{"one":"96419a246f5ca44f2796d91ccc55b3ac477e1754e74d202cdd133e6b5b96051b","other":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","up":true},{"one":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","other":"d56025bb110210216fdc8f86ad205607c6fb4744d3d444931aa17512a854356a","up":true},{"one":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","other":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","up":true},{"one":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","other":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","up":true},{"one":"aa7876d43dd0ce40f421f68c0d13c8c718d7a7a5e7c567dd20d87d19934cdc05","other":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","up":true},{"one":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","other":"3c5ae3bb462b109222b4a1ac1cf9b04508fcd53a2d13796880965e99e0216ee8","up":true},{"one":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","other":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","up":true},{"one":"48df69314a57a6ab2fc31108104aae2242ad2a941d2f87119a151250344aee02","other":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","up":true},{"one":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","other":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","up":false},{"one":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","other":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","up":true},{"one":"d0b76e826a6eaeabdc060a0588bd1becc1901b60a8eb4e8adbe61107883a2987","other":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","up":true},{"one":"91088ea79cd8f27da8879760dd21c363aa9be1135437c2b87464426874b597e5","other":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","up":true},{"one":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","other":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","up":true},{"one":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","other":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","up":true},{"one":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","other":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","up":true},{"one":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","other":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","up":true},{"one":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","other":"f0b803dcfec89e1d68739ca05cf0c9c04e80a2b1cd47496a83168a71184fd4cf","up":true},{"one":"69d8801f9f19fec51b636cc27fca2c93e67dac8f3650aea35259d01293d5c9e6","other":"6923d78400f277fe29c35ba38a416edd7c960cc75d94c35aa3446b4d5e2f6c3b","up":true},{"one":"6f1c48453e1bfb9c474d7f888d8c437c75db569e2c900867c4eaf3e6504847aa","other":"6923d78400f277fe29c35ba38a416edd7c960cc75d94c35aa3446b4d5e2f6c3b","up":true},{"one":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","other":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","up":true},{"one":"bd39cdc673c4184193347f0efd74bb894f7f36a994c154ffe3dadcd456efe460","other":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","up":true},{"one":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","other":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","up":true},{"one":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","other":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","up":false},{"one":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","other":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","up":true},{"one":"510c70916948815cd32759c451515bb024e38e0db8bb128710396978dc705a67","other":"57657e7b7d2642c6e5daefd27000d0da41982491486fd1aee57d0f99f072a471","up":true},{"one":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","other":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","up":true},{"one":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","other":"1953df7e29813b00b0137118623b6ce34113d17d4f9aaa608b81f2ee58ea3fe3","up":true},{"one":"c7fa3f5dd856ac0cd8fc3f2933180cfe1e605b31c630731f3e0a00d392f4097a","other":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","up":true},{"one":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","other":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","up":true},{"one":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","other":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","up":true},{"one":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","other":"f0b803dcfec89e1d68739ca05cf0c9c04e80a2b1cd47496a83168a71184fd4cf","up":true},{"one":"34e48bd6562a308ccf6a4a0257f253e908e97d16defe2f1a31e046d5558a4769","other":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","up":true},{"one":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","other":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","up":true},{"one":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","other":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","up":true},{"one":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","other":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","up":true},{"one":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","other":"34e48bd6562a308ccf6a4a0257f253e908e97d16defe2f1a31e046d5558a4769","up":true},{"one":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","other":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","up":true},{"one":"0bc30a547e60dc358f1b431b8f44d46471c19de546723da15e5ec3d3afd2ddf8","other":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","up":true},{"one":"df52d5de0f5e69e7e4c9e0ffcec1f9df86d38044bb57e5a0cb18bde354ecb66d","other":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","up":true},{"one":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","other":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","up":true},{"one":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","other":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","up":true},{"one":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","other":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","up":true},{"one":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","other":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","up":true},{"one":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","other":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","up":true},{"one":"0a3fe6516edff33cde6afc46c6174a8562f17454b6f3ad41be8c12ebf1b0a07d","other":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","up":true},{"one":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","other":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","up":true},{"one":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","other":"9a09aa168f7f4ebd90652d6900ace1ba88b8785290b2687783d4a23e9bdb90ee","up":false},{"one":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","other":"9a09aa168f7f4ebd90652d6900ace1ba88b8785290b2687783d4a23e9bdb90ee","up":true},{"one":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","other":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","up":false},{"one":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","other":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","up":true},{"one":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","other":"d96e430326c7fc91fcd19c4e85b472a41ba24317ff09e5bd8eb2bd7a306499be","up":true},{"one":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","other":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","up":true},{"one":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","other":"307c36a947f5c4c597075d8346a5db6133fbd2c14da099f85c1d2b7d6d8331f2","up":true},{"one":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","other":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","up":true},{"one":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","other":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","up":true},{"one":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","other":"9a09aa168f7f4ebd90652d6900ace1ba88b8785290b2687783d4a23e9bdb90ee","up":true},{"one":"e1909ff70b17f666d74c73e8d9803df1e6e20733e4aeecdc78e444b6632d92c9","other":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","up":true},{"one":"05a4778e1be7da949c1a7bffa3408020b66b4ee3c98e1f62e35d212c77e83ada","other":"07a6873c7eadb651196a76ab30241cc1c984549c5086f1c3bb1be7bd493346be","up":true},{"one":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","other":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","up":true},{"one":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","other":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","up":true},{"one":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","other":"2a7d1316da51fc5e1378366a543a73fc40db1eb7355b3e199ee237cc1db81abf","up":true},{"one":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","other":"d0443f24523af0eec6c20b21d84b2d54cb0ba8d4d1a205b41328bb22e8d3cd63","up":true},{"one":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","other":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","up":true},{"one":"4b8acc021ddea1fff036b4b0e5939c38507ef792490701f7b341e4ff10a78567","other":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","up":true},{"one":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","other":"294066bf056b1530308d096871b08a046086abcbc7ed7a0a97d9fb46cccd6003","up":true},{"one":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","other":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","up":true},{"one":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","other":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","up":true},{"one":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","other":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","up":true},{"one":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","other":"1a85e13e2ad82c8ffa10ad52b382947e638036120188ae611fb1fb333cb5232b","up":true},{"one":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","other":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","up":true},{"one":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","other":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","up":true},{"one":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","other":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","up":true},{"one":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","other":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","up":true},{"one":"48ec978577e2dbf0448ab119a3821be5d25dbf5a295fd4f695b9311429538a26","other":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","up":true},{"one":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","other":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","up":false},{"one":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","other":"6923d78400f277fe29c35ba38a416edd7c960cc75d94c35aa3446b4d5e2f6c3b","up":true},{"one":"8ea97f6abd40d70717688c5dd38daf6f6b12005df21ae397dfb700fbfe3447ac","other":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","up":true},{"one":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","other":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","up":true},{"one":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","other":"1a8ecbde88be22a9bbb5f3ef19be5377923d655fd9cd838f9d08a442614742c3","up":true},{"one":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","other":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","up":true},{"one":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","other":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","up":true},{"one":"34e48bd6562a308ccf6a4a0257f253e908e97d16defe2f1a31e046d5558a4769","other":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","up":true},{"one":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","other":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","up":true},{"one":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","other":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","up":false},{"one":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","other":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","up":false},{"one":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","other":"f1b97eeabc889cdb4f67c699fdb30ad896abe08e603b154abdf5e122bb1e49ce","up":true},{"one":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","other":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","up":true},{"one":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","other":"bb3198bb61f3dc96365a99b7141c0b85720614de4640afa76458d27484e1353a","up":true},{"one":"0a3fe6516edff33cde6afc46c6174a8562f17454b6f3ad41be8c12ebf1b0a07d","other":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","up":true},{"one":"f2f5e82cf81f07f8cf82c7171c5ee5a42b7963619fafd4295137357cea1b7bb0","other":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","up":true},{"one":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","other":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","up":false},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","up":true},{"one":"d0443f24523af0eec6c20b21d84b2d54cb0ba8d4d1a205b41328bb22e8d3cd63","other":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","up":true},{"one":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","other":"83d0ffd43e2469b9c3498862d0186e413cc4d205f7854bf9dd73c4d74379217c","up":true},{"one":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","other":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","up":true},{"one":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","other":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","up":true},{"one":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","other":"208e648a32b59b77c2abbeac0402c8d009485a19dd4a04a216cd91d0c6713929","up":true},{"one":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","other":"307c36a947f5c4c597075d8346a5db6133fbd2c14da099f85c1d2b7d6d8331f2","up":true},{"one":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","other":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","up":true},{"one":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","other":"d96e430326c7fc91fcd19c4e85b472a41ba24317ff09e5bd8eb2bd7a306499be","up":true},{"one":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","other":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","up":true},{"one":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","other":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","up":true},{"one":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","other":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","up":true},{"one":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","other":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","up":true},{"one":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","other":"1254b1e5c9e80b367520bfefc35cf32a60aff56c48315107c4b07aa1a7866af9","up":true},{"one":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","other":"d0b76e826a6eaeabdc060a0588bd1becc1901b60a8eb4e8adbe61107883a2987","up":true},{"one":"2224bdce114e9e87621ab44d1a54b11e7714f30ba695c8353a9573adfcf822fa","other":"294066bf056b1530308d096871b08a046086abcbc7ed7a0a97d9fb46cccd6003","up":true},{"one":"510c70916948815cd32759c451515bb024e38e0db8bb128710396978dc705a67","other":"574101500f64e9a1bfbf31f4f379a60a0ea9bee449af1cd29962214dd960b3c6","up":true},{"one":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","other":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","up":true},{"one":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","other":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","up":true},{"one":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","other":"9a09aa168f7f4ebd90652d6900ace1ba88b8785290b2687783d4a23e9bdb90ee","up":true},{"one":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","other":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","up":true},{"one":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","other":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","up":true},{"one":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","other":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","up":true},{"one":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","other":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","up":true},{"one":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","other":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","up":true},{"one":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","other":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","up":true},{"one":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","other":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","up":false},{"one":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","other":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","up":true},{"one":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","other":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","up":true},{"one":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","other":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","up":true},{"one":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","other":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","up":true},{"one":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","other":"802f9e88535e40c179cb5f7d2a16500696e2af2115d3f7ca0cac5c2f634109c2","up":true},{"one":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","other":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","up":true},{"one":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","other":"802f9e88535e40c179cb5f7d2a16500696e2af2115d3f7ca0cac5c2f634109c2","up":true},{"one":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","other":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","up":true},{"one":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","other":"8a5439adf9987f8e0df052b5c32599dba0a25d3e45dce4cafdf2d2ddfdae2f42","up":true},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","up":true},{"one":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","other":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","up":true},{"one":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","other":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","up":true},{"one":"d0b76e826a6eaeabdc060a0588bd1becc1901b60a8eb4e8adbe61107883a2987","other":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","up":false},{"one":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","other":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","up":true},{"one":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","other":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","up":true},{"one":"d0b76e826a6eaeabdc060a0588bd1becc1901b60a8eb4e8adbe61107883a2987","other":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","up":true},{"one":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","other":"1a85e13e2ad82c8ffa10ad52b382947e638036120188ae611fb1fb333cb5232b","up":true},{"one":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","other":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","up":true},{"one":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","other":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","up":true},{"one":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","other":"1a8ecbde88be22a9bbb5f3ef19be5377923d655fd9cd838f9d08a442614742c3","up":true},{"one":"89efed66fb192f1530bad978261c660cf18473bc2ad4dcb6c3336afefe8aebf2","other":"8d3c0c717a2ead135c414b69841ade94e126eb0a89132a2c6f531728c62a9180","up":true},{"one":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","other":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","up":true},{"one":"48df69314a57a6ab2fc31108104aae2242ad2a941d2f87119a151250344aee02","other":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","up":true},{"one":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","other":"af8fb611627063b408d2b96c1ab68fafb223a86d947de3dac3a59649cd13e0cf","up":true},{"one":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","other":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","up":true},{"one":"34e48bd6562a308ccf6a4a0257f253e908e97d16defe2f1a31e046d5558a4769","other":"3c5ae3bb462b109222b4a1ac1cf9b04508fcd53a2d13796880965e99e0216ee8","up":true},{"one":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","other":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","up":true},{"one":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","other":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","up":true},{"one":"34e48bd6562a308ccf6a4a0257f253e908e97d16defe2f1a31e046d5558a4769","other":"3ef6bb1dcd37360fe4a463b70add6cd4ba112a293112911b2304ab05b4c1b543","up":false},{"one":"34e48bd6562a308ccf6a4a0257f253e908e97d16defe2f1a31e046d5558a4769","other":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","up":true},{"one":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","other":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","up":true},{"one":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","other":"f0b803dcfec89e1d68739ca05cf0c9c04e80a2b1cd47496a83168a71184fd4cf","up":true},{"one":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","other":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","up":true},{"one":"507060be030344850df6fc76719d503254b38d96d77eb25f49ca9d955944b10a","other":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","up":true},{"one":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","other":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","up":true},{"one":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","other":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","up":true},{"one":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","other":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","up":true},{"one":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","other":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","up":true},{"one":"a9dfced37b98d1dfabb96e5c68f0744c615385328aa469af16fbcf8f28d2062d","other":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","up":true},{"one":"41b2b713dba3a11683ad83d1cb4beb9ff4f5aaf49f2ad1ac2247c84a85d40a56","other":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","up":true},{"one":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","other":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","up":true},{"one":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","other":"05a4778e1be7da949c1a7bffa3408020b66b4ee3c98e1f62e35d212c77e83ada","up":true},{"one":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","other":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","up":true},{"one":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","other":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","up":true},{"one":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","other":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","up":true},{"one":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","other":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","up":true},{"one":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","other":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","up":false},{"one":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","other":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","up":true},{"one":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","other":"3054ce9b542ef2af36ab2b5d4590f2b9794107bbbe55cac109ecd5dfe56d8c32","up":false},{"one":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","other":"d60644fb69edf1d360a5b3d480030df97600a3d94a189da98b58c43758c98331","up":true},{"one":"715b38eca0c630aad73e5f7d65453c6666f459994e3cc4bba586af75d78e8fe4","other":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","up":false},{"one":"3c5ae3bb462b109222b4a1ac1cf9b04508fcd53a2d13796880965e99e0216ee8","other":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","up":true},{"one":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","other":"3388123bad3bf1debf51c5921c09d06f7627d05f6115af39d92dbe9b2f2313dc","up":false},{"one":"413b15ae74e38b8610dd5edd52e05630f8acfa4139a1b361775afa8e748ebbfc","other":"41b2b713dba3a11683ad83d1cb4beb9ff4f5aaf49f2ad1ac2247c84a85d40a56","up":true},{"one":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","other":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","up":true},{"one":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","other":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","up":true},{"one":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","other":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","up":true},{"one":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","other":"d0443f24523af0eec6c20b21d84b2d54cb0ba8d4d1a205b41328bb22e8d3cd63","up":true},{"one":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","other":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","up":true},{"one":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","other":"d3bd76f9b178b34e0a97bd0130c61a8dfa2097fcbc667f6e4f4eb77227425693","up":true},{"one":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","other":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","up":true},{"one":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","other":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","up":true},{"one":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","other":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","up":true},{"one":"fd44f2616755aad3efacba9ecf1d92d0ab986ba679007f735c8bcf3c6b94c150","other":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","up":false},{"one":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","other":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","up":true},{"one":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","other":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","up":true},{"one":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","other":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","up":true},{"one":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","other":"5b308b25cfe30d0b68784dc73be464dfa85a8e49ddd6f6271ff0ee2b63798928","up":true},{"one":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","other":"1953df7e29813b00b0137118623b6ce34113d17d4f9aaa608b81f2ee58ea3fe3","up":false},{"one":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","other":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","up":true},{"one":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","other":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","up":true},{"one":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","other":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","up":false},{"one":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","other":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","up":true},{"one":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","other":"9a09aa168f7f4ebd90652d6900ace1ba88b8785290b2687783d4a23e9bdb90ee","up":true},{"one":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","other":"d90b75bd11ea3243e097f2318bd95a35590c9f113324ffa1f94cd701a5d3eb32","up":true},{"one":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","other":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","up":true},{"one":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","other":"1a85e13e2ad82c8ffa10ad52b382947e638036120188ae611fb1fb333cb5232b","up":true},{"one":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","other":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","up":true},{"one":"e1909ff70b17f666d74c73e8d9803df1e6e20733e4aeecdc78e444b6632d92c9","other":"e2554d1bf145ec0e7027ca8e8ba0c85fc697bc4b54e36a6cdc53f1efa37a6634","up":true},{"one":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","other":"e3d8cbd8840509034f081859422bc599750fa4f040d2f90302b9513b433847f8","up":true},{"one":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","other":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","up":true},{"one":"0a3fe6516edff33cde6afc46c6174a8562f17454b6f3ad41be8c12ebf1b0a07d","other":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","up":true},{"one":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","other":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","up":true},{"one":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","other":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","up":true},{"one":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","other":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","up":false},{"one":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","other":"1a8ecbde88be22a9bbb5f3ef19be5377923d655fd9cd838f9d08a442614742c3","up":true},{"one":"507060be030344850df6fc76719d503254b38d96d77eb25f49ca9d955944b10a","other":"57657e7b7d2642c6e5daefd27000d0da41982491486fd1aee57d0f99f072a471","up":true},{"one":"41b2b713dba3a11683ad83d1cb4beb9ff4f5aaf49f2ad1ac2247c84a85d40a56","other":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","up":true},{"one":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","other":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","up":true},{"one":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","other":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","up":true},{"one":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","other":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","up":true},{"one":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","other":"7aef1625409f479ee1abad565f8d7e73b17714e77f54d1ac1b2fe80382a058cc","up":true},{"one":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","other":"3388123bad3bf1debf51c5921c09d06f7627d05f6115af39d92dbe9b2f2313dc","up":true},{"one":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","other":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","up":true},{"one":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","other":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","up":true},{"one":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","other":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","up":true},{"one":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","other":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","up":true},{"one":"6701b38360d0a72ddc355c17385af34b7ed9eeec00bc3867e162b5571af69e3f","other":"6f1c48453e1bfb9c474d7f888d8c437c75db569e2c900867c4eaf3e6504847aa","up":true},{"one":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","other":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","up":false},{"one":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","other":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","up":true},{"one":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","other":"f1ad1843560ead294e9d60c9bfc99d8a827f2cfe557e0f9fe7ebf82aec56926d","up":true},{"one":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","other":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","up":true},{"one":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","other":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","up":true},{"one":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","other":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","up":true},{"one":"3ef6bb1dcd37360fe4a463b70add6cd4ba112a293112911b2304ab05b4c1b543","other":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","up":true},{"one":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","other":"d0b76e826a6eaeabdc060a0588bd1becc1901b60a8eb4e8adbe61107883a2987","up":true},{"one":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","other":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","up":true},{"one":"96419a246f5ca44f2796d91ccc55b3ac477e1754e74d202cdd133e6b5b96051b","other":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","up":true},{"one":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","other":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","up":true},{"one":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","other":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","up":true},{"one":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","other":"d96e430326c7fc91fcd19c4e85b472a41ba24317ff09e5bd8eb2bd7a306499be","up":true},{"one":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","other":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","up":false},{"one":"507060be030344850df6fc76719d503254b38d96d77eb25f49ca9d955944b10a","other":"56bbd975ff675f73b566b447e894d311b8631438163b625436685b019a15213b","up":false},{"one":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","other":"3ff80089a2ef377b1844881300b3e85041b16f0e7508b01e59a0e76b63c8244e","up":true},{"one":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","other":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","up":true},{"one":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","other":"baafd0d5d03e5072341e9e3e69b8c48041cd6a9ca3da56a1a4934084db6e22bd","up":true},{"one":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","other":"f2f5e82cf81f07f8cf82c7171c5ee5a42b7963619fafd4295137357cea1b7bb0","up":true},{"one":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","other":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","up":true},{"one":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","other":"07a6873c7eadb651196a76ab30241cc1c984549c5086f1c3bb1be7bd493346be","up":true},{"one":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","other":"4b8acc021ddea1fff036b4b0e5939c38507ef792490701f7b341e4ff10a78567","up":true},{"one":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","other":"0020768d96fca0cf47a9632ca25ff794c996042f07a7242ec7c70fbc75e0e45e","up":true},{"one":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":false},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","up":false},{"one":"715b38eca0c630aad73e5f7d65453c6666f459994e3cc4bba586af75d78e8fe4","other":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","up":true},{"one":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","other":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","up":true},{"one":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","other":"f054c2c66725837bdf1d64602f9d1627d83a3c970d9bcabda7f4d390cd40a438","up":true},{"one":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","other":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","up":true},{"one":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","other":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","up":false},{"one":"0ad654954ad9b21c75831389c89574a7871a6ba8e1d54c92cf26c64c33148b42","other":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","up":true},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","up":true},{"one":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","other":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","up":true},{"one":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","other":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","up":true},{"one":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","other":"14e40f9abe8b2c845f2fc62a8f4347ff3630918cd62a33af7788e4bf599cfb96","up":true},{"one":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","other":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","up":false},{"one":"43fbbefa7ed5fb3b496c126bf54a77b7cc65b40c67facbf9605fafbe4e0397ad","other":"413b15ae74e38b8610dd5edd52e05630f8acfa4139a1b361775afa8e748ebbfc","up":false},{"one":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","other":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","up":true},{"one":"85642219f8bb8ce048cfce5aecddc8acd035afa035a0137c354f5b6fc3e3889d","other":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","up":true},{"one":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","other":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","up":true},{"one":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","other":"fd3fcb327e1988c4786dad45a6e325b2fa9ef07ee62721aa5cdafd5529bd125d","up":true},{"one":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","other":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","up":true},{"one":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","other":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","up":true},{"one":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","other":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","up":true},{"one":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","other":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","up":false},{"one":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","other":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","up":true},{"one":"d56025bb110210216fdc8f86ad205607c6fb4744d3d444931aa17512a854356a","other":"c4090ec37cddfca2dfae52d85993a635cf47547c917e5c69190e24a48db7cafd","up":false},{"one":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","other":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","up":true},{"one":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","other":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","up":true},{"one":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","other":"d60644fb69edf1d360a5b3d480030df97600a3d94a189da98b58c43758c98331","up":true},{"one":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","other":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","up":true},{"one":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","other":"8ee7f2d5c359bd5c37ec03d4e02ef8084768e12d094a7cd27cb5def6738b61c5","up":true},{"one":"e1909ff70b17f666d74c73e8d9803df1e6e20733e4aeecdc78e444b6632d92c9","other":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","up":false},{"one":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","other":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","up":true},{"one":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","other":"a105c9ec1789d99c59f23257f63e048c0c923e3748d35e6108c8c0a8d10e6ec1","up":false},{"one":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","other":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","up":true},{"one":"5b308b25cfe30d0b68784dc73be464dfa85a8e49ddd6f6271ff0ee2b63798928","other":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","up":true},{"one":"523768400c5056078f368a95723448fae6747917fb3a9c642930ebfd979d9e05","other":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","up":false},{"one":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","other":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","up":false},{"one":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","other":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","up":false},{"one":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","other":"cba44046d693629a872a22812ebcc9919ddd8148ec7085b23bbb828f258b48f2","up":true},{"one":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","other":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","up":true},{"one":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","other":"7e4585b52254f6081e19191e737fd1898528a0af77e3d752a081d29b92ac13a5","up":false},{"one":"6701b38360d0a72ddc355c17385af34b7ed9eeec00bc3867e162b5571af69e3f","other":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","up":true},{"one":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","other":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","up":false},{"one":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","other":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","up":true},{"one":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","other":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","up":false},{"one":"cf771ee06f5ab9cfb00084a5ee8929d15e434f2ffbc4ba4289cb2471104146fb","other":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","up":false},{"one":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","other":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","up":true},{"one":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","other":"4b70964ce2916bb056cec6da31283f4eb3cdf774b86f3ba36b13f56fec18c6db","up":false},{"one":"6923d78400f277fe29c35ba38a416edd7c960cc75d94c35aa3446b4d5e2f6c3b","other":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","up":true},{"one":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","other":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","up":true},{"one":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","other":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","up":true},{"one":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","other":"bb3198bb61f3dc96365a99b7141c0b85720614de4640afa76458d27484e1353a","up":false},{"one":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","other":"0020768d96fca0cf47a9632ca25ff794c996042f07a7242ec7c70fbc75e0e45e","up":true},{"one":"413b15ae74e38b8610dd5edd52e05630f8acfa4139a1b361775afa8e748ebbfc","other":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","up":false},{"one":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","other":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","up":false},{"one":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","other":"f293658b2837cd41cfb4f9eaa18d0025fc5877bcec701a97df914882f4bfe43e","up":false},{"one":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","other":"853bc86aad69d6b5ae27f27b7979f504a3d1033000a4fa164f563357414f2fcd","up":false},{"one":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","other":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","up":true},{"one":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","other":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","up":false},{"one":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","other":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","up":true},{"one":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","other":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","up":false},{"one":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","other":"d66f440d86e43ad7e153ed9f101bd1632e64ddd2e75791fcd3fe819bd190e665","up":false},{"one":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","other":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","up":true},{"one":"09d15d7dac32cbd6f9b5b292e6bcb5082d95d24edbb8ddef871890409aab60e7","other":"0d97827f6ea850c310c312598ebe1a92ceae08d1e055ca170f1e305f836ba398","up":true},{"one":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","other":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","up":true},{"one":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","other":"3ab20836f88aab49491e82cfd1f1e8a59b5794cf91644b663d40cb22c5460be4","up":false},{"one":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","other":"f97e87f80e6db30609a0811bef5cc7a5884326abed5cc970f3ce580476d0935c","up":false},{"one":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","other":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","up":false},{"one":"cbd3dc416cd646687d2cc0373bdd79f44d77f5ee775e298a083d8c62be248f3c","other":"ca8c1a36e8c6998028b7cf45086f6478a622f00878bcd2643bcb021efc8cf50b","up":false},{"one":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","other":"6923d78400f277fe29c35ba38a416edd7c960cc75d94c35aa3446b4d5e2f6c3b","up":true},{"one":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","other":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","up":false},{"one":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","other":"1a8ecbde88be22a9bbb5f3ef19be5377923d655fd9cd838f9d08a442614742c3","up":false},{"one":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","other":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","up":false},{"one":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","other":"6701b38360d0a72ddc355c17385af34b7ed9eeec00bc3867e162b5571af69e3f","up":false},{"one":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","other":"1a85e13e2ad82c8ffa10ad52b382947e638036120188ae611fb1fb333cb5232b","up":true},{"one":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","other":"c95a481b19583fc85e9b079fa0b5cf51eb46e71b9f57fc6df2e3b78389d8de2f","up":false},{"one":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","other":"bbcbaf233465acdcce94920a2a643c20772074af094a33ffb96cc915e30ffd8a","up":false},{"one":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","other":"03059077d3fadf62794756f6947fc32ed00d82162dbe3e6ebcd32952bd421321","up":false},{"one":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","other":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","up":false},{"one":"5b308b25cfe30d0b68784dc73be464dfa85a8e49ddd6f6271ff0ee2b63798928","other":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","up":true},{"one":"507060be030344850df6fc76719d503254b38d96d77eb25f49ca9d955944b10a","other":"574101500f64e9a1bfbf31f4f379a60a0ea9bee449af1cd29962214dd960b3c6","up":false},{"one":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","other":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","up":false},{"one":"6701b38360d0a72ddc355c17385af34b7ed9eeec00bc3867e162b5571af69e3f","other":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","up":false},{"one":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","other":"d7c1b747ad01a39ca048916de03c0288d80aede0c968c392a9656f616903ffd3","up":true},{"one":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","other":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","up":false},{"one":"398bb8a4dfcbf50a93de93688331fed42454d4941b367c569e9bc568d18fbc00","other":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","up":false},{"one":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","other":"6caa4a84138eeb4cbe5573edf4c2381edf01bd386c57f527dde377983301a563","up":true},{"one":"bf6b5c5f8d8b6beab2d82c1ba9616309fa7a3052050e31e5f46ecac4636fd10e","other":"beb777c02dda420daeecd9fafbaadcd2592ed51e9a8a5c5dd7abe43743a88001","up":false},{"one":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","other":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","up":false},{"one":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","other":"5b308b25cfe30d0b68784dc73be464dfa85a8e49ddd6f6271ff0ee2b63798928","up":false},{"one":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","other":"d717377aed09d9b0d4f072abd13f49d3e4f64fd8ec40aa45a47d10fdc716e225","up":false},{"one":"85642219f8bb8ce048cfce5aecddc8acd035afa035a0137c354f5b6fc3e3889d","other":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","up":true},{"one":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","other":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","up":true},{"one":"98aa8f9e3f434905222090453f2b0a97b7d60a252e0ecd519451645e0e685319","other":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","up":true},{"one":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","other":"df52d5de0f5e69e7e4c9e0ffcec1f9df86d38044bb57e5a0cb18bde354ecb66d","up":false},{"one":"e1909ff70b17f666d74c73e8d9803df1e6e20733e4aeecdc78e444b6632d92c9","other":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","up":true},{"one":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","other":"1af6b4d9b354555f37c5be62feb691dfa76a531404ed4de5ad2b905c84fc37fa","up":false},{"one":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","other":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","up":false},{"one":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","other":"34e48bd6562a308ccf6a4a0257f253e908e97d16defe2f1a31e046d5558a4769","up":false},{"one":"0fa02bedceaa28618f48f319d38b657a356099ac98734f402c4c846a4c35b678","other":"0c728c174a25e11bc171d7d35eaaa3974859a4dbf7a64e1d9c9da6b3ad63b21a","up":false},{"one":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","other":"1587ed904df26ed711e2235fea41e5812a7d33ce5dab2ebb4e88515b18f43d6e","up":false},{"one":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","other":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","up":false},{"one":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","other":"7ec4083eca4749caa706ac08afc0c91cdcec42f18e4d360f18695a79de362115","up":false},{"one":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","other":"e1909ff70b17f666d74c73e8d9803df1e6e20733e4aeecdc78e444b6632d92c9","up":false},{"one":"949386e823234d1afdf2a2134630a8028868bdcdf26443da4db22ece78871899","other":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","up":false},{"one":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","other":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","up":false},{"one":"41b2b713dba3a11683ad83d1cb4beb9ff4f5aaf49f2ad1ac2247c84a85d40a56","other":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","up":false},{"one":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","other":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","up":false},{"one":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","other":"4bf8d8c5550940df9712d758be4711014366af3ea6b5988c5c8610f331f37042","up":false},{"one":"67ee67b8f4d3ee50d8cdd027ba96e4b2f8ef9017bac217bd5a2d9dca35325519","other":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","up":false},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"307c36a947f5c4c597075d8346a5db6133fbd2c14da099f85c1d2b7d6d8331f2","up":false},{"one":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","other":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","up":false},{"one":"9a09aa168f7f4ebd90652d6900ace1ba88b8785290b2687783d4a23e9bdb90ee","other":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","up":false},{"one":"0a3fe6516edff33cde6afc46c6174a8562f17454b6f3ad41be8c12ebf1b0a07d","other":"0c1956cad397c9cb93524cb23fbfab99df2a79780ae596b93c77fd9db06f7f7a","up":true},{"one":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","other":"b820b318a3a12a7a7f45d06fff7f5d7e3ca2ca8097c396c1d76c735e1c915cc0","up":true},{"one":"3388123bad3bf1debf51c5921c09d06f7627d05f6115af39d92dbe9b2f2313dc","other":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","up":false},{"one":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","other":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","up":false},{"one":"507060be030344850df6fc76719d503254b38d96d77eb25f49ca9d955944b10a","other":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","up":false},{"one":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","other":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","up":true},{"one":"d0b76e826a6eaeabdc060a0588bd1becc1901b60a8eb4e8adbe61107883a2987","other":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","up":false},{"one":"63de4cd08e00a3dbc071adafe1d1ca255cc6d672e3c5aa6596ab60ed5cc82c5e","other":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","up":true},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"308528d445007e189c31e695f2bc74af3debab473f57bffd866c601155ffc3d3","up":false},{"one":"8ae89e105f03ff1bacbf53e47ac7bd9af66e0fbc0decb1c11bc60778cf97476a","other":"ffb73c715498775efa3d82de417a4469c66a155ca1149fec7a1ca0a227eab4f3","up":false},{"one":"715b38eca0c630aad73e5f7d65453c6666f459994e3cc4bba586af75d78e8fe4","other":"1fabca181afff59bda173b8de38b04169f9b299880bf72674fdc8abd1f546e9c","up":false},{"one":"0f65210e727ede01a854c3c8d256adced79866c368d0d51fb3ade9c390b02318","other":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","up":false},{"one":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","other":"37347d159418873210a3dcfa60c0f6ca50f9007ce6f0126eeea6918bda08b2de","up":false},{"one":"0ca9d19acb9c459358eb69ddaad98dca0b4c13b14cf1610716ed4f84dd972563","other":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","up":false},{"one":"3ef6bb1dcd37360fe4a463b70add6cd4ba112a293112911b2304ab05b4c1b543","other":"1254b1e5c9e80b367520bfefc35cf32a60aff56c48315107c4b07aa1a7866af9","up":false},{"one":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","other":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","up":false},{"one":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","other":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","up":false},{"one":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","other":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","up":false},{"one":"5caea6ca046c5e2d3c9e2389600c36d3feba80a455944842c2eb55c5b144318a","other":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","up":false},{"one":"f2f5e82cf81f07f8cf82c7171c5ee5a42b7963619fafd4295137357cea1b7bb0","other":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","up":false},{"one":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","other":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","up":false},{"one":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","other":"2cab6becfcdafacbc9e5387c79bc70dcb9c16e1789b404edf57e05354f05679d","up":false},{"one":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","other":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","up":true},{"one":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","other":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","up":false},{"one":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","other":"b45fd6b4ce2541816834066dcc1cee4bfb343ec965db53eda986e6b037513460","up":true},{"one":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","other":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","up":false},{"one":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","other":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","up":false},{"one":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","other":"413b15ae74e38b8610dd5edd52e05630f8acfa4139a1b361775afa8e748ebbfc","up":false},{"one":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","other":"581ab9df0d109d94291331b68cbbf8bc700d969b2f9e43d0717f88f5409fb815","up":false},{"one":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","other":"de1d3e466a171aef4bd47b6546d25c12a1f3120f0a5f68d377dc01362b408a2e","up":false},{"one":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","other":"c914c96fb6c0ecd51e043f0395c7c63595ccd990c94d9ee241b14da352aa8773","up":false},{"one":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","other":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","up":false},{"one":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","other":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","up":true},{"one":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","other":"7aef1625409f479ee1abad565f8d7e73b17714e77f54d1ac1b2fe80382a058cc","up":true},{"one":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","other":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","up":false},{"one":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","other":"1254b1e5c9e80b367520bfefc35cf32a60aff56c48315107c4b07aa1a7866af9","up":false},{"one":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","other":"1953df7e29813b00b0137118623b6ce34113d17d4f9aaa608b81f2ee58ea3fe3","up":false},{"one":"d1bc26382a2c66d93f96f82baa6324a62a1a5574b7a7078b579c452429e61cae","other":"d777e45233aa4d8b723f6a6aa9cbf5de4c0b19f6fd00af6d538b67ec01b74ab5","up":false},{"one":"d07c2c3d684684a3178bac8f4578d1447117f604bc475efe108dce5621643846","other":"d35c12f352b5e8603b6e354434de8310e35f9901af563a3ec775bfa00b806f23","up":false},{"one":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","other":"7ec4083eca4749caa706ac08afc0c91cdcec42f18e4d360f18695a79de362115","up":true},{"one":"5c9be78d69b28013fe73a16d07240b540f3399e041c92fc263a94632b1ed255d","other":"57657e7b7d2642c6e5daefd27000d0da41982491486fd1aee57d0f99f072a471","up":false},{"one":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","other":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","up":false},{"one":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","other":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","up":true},{"one":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","other":"3799c5f3ebaad70130fcf00bb8131c78951a8f4bbd67ad2bdadfff4a8f667143","up":false},{"one":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","other":"9a09aa168f7f4ebd90652d6900ace1ba88b8785290b2687783d4a23e9bdb90ee","up":false},{"one":"f2f5e82cf81f07f8cf82c7171c5ee5a42b7963619fafd4295137357cea1b7bb0","other":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","up":false},{"one":"5062d74876fdf425a8e576ed5e56d9719a2e506f45219fc10c48ed44d2f0360b","other":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","up":false},{"one":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","other":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","up":true},{"one":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","other":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","up":false},{"one":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","other":"dad35b937a6501fcd7483467d81296a6c8541def88d0ce45a702a1d1725bf567","up":false},{"one":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","other":"3ef6bb1dcd37360fe4a463b70add6cd4ba112a293112911b2304ab05b4c1b543","up":false},{"one":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","other":"1a8ecbde88be22a9bbb5f3ef19be5377923d655fd9cd838f9d08a442614742c3","up":false},{"one":"a1f1f5ce0313b3f63ec10fd5c19aa91fc56309ff599e08e19ed15e43c115950e","other":"6f1c48453e1bfb9c474d7f888d8c437c75db569e2c900867c4eaf3e6504847aa","up":false},{"one":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","other":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","up":false},{"one":"715b38eca0c630aad73e5f7d65453c6666f459994e3cc4bba586af75d78e8fe4","other":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","up":false},{"one":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","other":"36589cecb182e45d3e9f35e2405ed9a8bccc452028b903d30728ce099323fd5b","up":false},{"one":"3ef6bb1dcd37360fe4a463b70add6cd4ba112a293112911b2304ab05b4c1b543","other":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","up":false},{"one":"e56cd20d7e387065be2d52a5c3ea1682765c9378521709887c6e29cbf24b43e0","other":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","up":false},{"one":"507060be030344850df6fc76719d503254b38d96d77eb25f49ca9d955944b10a","other":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","up":false},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","up":false},{"one":"c9114dc588a9d7ba73e41a943d08c528f3a4e811dc8b216cd4ab58171d0f642c","other":"af8fb611627063b408d2b96c1ab68fafb223a86d947de3dac3a59649cd13e0cf","up":false},{"one":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","other":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","up":true},{"one":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","other":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","up":true},{"one":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","other":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","up":false},{"one":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","other":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","up":true},{"one":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","other":"e5d23024ad9e5dc8915f8d31428e9b3d62f1c4843e276451f17ebb766d06b456","up":false},{"one":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","other":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","up":false},{"one":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","other":"6701b38360d0a72ddc355c17385af34b7ed9eeec00bc3867e162b5571af69e3f","up":false},{"one":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","other":"2cb18a6dd5643091b3b41538bb32dc414785fcf180a5c51f10c8bbcdbc6a7847","up":false},{"one":"9a09aa168f7f4ebd90652d6900ace1ba88b8785290b2687783d4a23e9bdb90ee","other":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","up":false},{"one":"020f57c369b6521552f7498e444aed21ac57832ed3ffaa7b45f607793ea1a86c","other":"0daf815b74975070a8ef053b82b2f4fa7919d4888d472f7c57d7b60c7b56e056","up":false},{"one":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","other":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","up":false},{"one":"910da662d9444f160ff5d934ef667f1f3aaba1fc3275e6ba152f9aa7e85bb24d","other":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","up":false},{"one":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","other":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","up":false},{"one":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","other":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","up":false},{"one":"99eb314acf9de2b6f7085da247a552125e61c97c6b54388a21c0474d717e3590","other":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","up":false},{"one":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","other":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","up":true},{"one":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","other":"e54d69f3bb061c62dede6e5a8704700bf9b8063edead7c755c9586f4d96754f1","up":false},{"one":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","other":"24f87fd89a8aabaee05033a0a3eefc01ccbb5bbc6a53d615ef34f646a46b0156","up":true},{"one":"eee15b5c5ffcbd0aa56877a6a45aaa323367614180d3e5cf7b4aeefa789d8317","other":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","up":false},{"one":"5a67bff220db75aa2266742614cf15025b9c06aa733c805d0372f10882c292a1","other":"539980a9bfc5ce74bc35d81cc264f4c0d529a75df0c870b9e0b9ce45650a9f29","up":false},{"one":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","other":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","up":true},{"one":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","other":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","up":false},{"one":"0a3fe6516edff33cde6afc46c6174a8562f17454b6f3ad41be8c12ebf1b0a07d","other":"b04931ea8428258af41286377042ca4ef7b2f507ef3ba14512b36a043a184253","up":true},{"one":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","other":"efbb3dd3b0fb3daf4a8a7091d400d834fd9532ce0c3dbebb60d391559d64acb1","up":true},{"one":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","other":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","up":true},{"one":"6f1c48453e1bfb9c474d7f888d8c437c75db569e2c900867c4eaf3e6504847aa","other":"7ec4083eca4749caa706ac08afc0c91cdcec42f18e4d360f18695a79de362115","up":false},{"one":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","other":"ec785af2fb66c0af4c7d6ac750e337a5a8a795a22022cafa8a43df7fba11d9e1","up":false},{"one":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","other":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","up":true},{"one":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","other":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","up":false},{"one":"4dc43c820a6c34797152a558339bf468aa48c64e1e54c6158d5ea333a8f3dce8","other":"48df69314a57a6ab2fc31108104aae2242ad2a941d2f87119a151250344aee02","up":false},{"one":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","other":"992959c1dabb655a57cbadfeb18436f21226a56968ff0c9926a69b86692cf289","up":false},{"one":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","other":"42d6afeae77ae9e99c6bb727126dde5e1338b5421cafe2787251bd19d2b54d3a","up":false},{"one":"e7243f48530c3f040827c02fdc67642c7a0569138337a498517d4fb4d38f10af","other":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","up":false},{"one":"47a8e9918ad3b957a888540a497fe46dc4b64a1d1f1f8e1bacefdf6813d91d70","other":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","up":false},{"one":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","other":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","up":false},{"one":"5e3efcd12d75bb47f842261d24f509018dfa5687e3195e1d1bb2bc602cc9c291","other":"5b308b25cfe30d0b68784dc73be464dfa85a8e49ddd6f6271ff0ee2b63798928","up":false},{"one":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","other":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","up":false},{"one":"ce7128ea0b68755aea6e2dd5d5da1100a1e29ebc2d34ce02c0d025a4a700ea15","other":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","up":false},{"one":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","other":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","up":false},{"one":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","other":"c301dfd7e7510fa72b94531ead6c51cceeea230a004a91f9fb472fb19b39bc06","up":false},{"one":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","other":"ebc9b288de5534f762a4ab0e6522955a8f83ad15b662426e45956a820401d0a0","up":true},{"one":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","other":"1a85e13e2ad82c8ffa10ad52b382947e638036120188ae611fb1fb333cb5232b","up":false},{"one":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","other":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","up":true},{"one":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","other":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","up":true},{"one":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","other":"e4e95f8381568c1cba7e8f64dd710554b923241a1c988dddb42a2cd96434705f","up":false},{"one":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","other":"36070b5229906c010f3d756c6b9501f80757fdba261e38ea2a294b5047f67739","up":true},{"one":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","other":"1dfcaab676a9d9a83c7f076449aa0d8446770ec0960655e1202fd7ee426383eb","up":false},{"one":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","other":"0020768d96fca0cf47a9632ca25ff794c996042f07a7242ec7c70fbc75e0e45e","up":false},{"one":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","other":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","up":true},{"one":"44b7013bcd5e0bd36ed526602614f13a91c9fcfa8613312ae22d9c87377a5949","other":"413b15ae74e38b8610dd5edd52e05630f8acfa4139a1b361775afa8e748ebbfc","up":false},{"one":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","other":"2737567dbc8dbfa2a23fbd4264d1bf7cd126b54f1bc3aefdf4170bb80bc4c5a7","up":false},{"one":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","other":"efdd79da474a0784c7421ed246f6f8db721bd3551ad6e3793070c432181679f3","up":false},{"one":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","other":"86113f8a7d4304ffe158ccc0e94af9194cddbb27604ccc08dd2adbea9878be2f","up":false},{"one":"96419a246f5ca44f2796d91ccc55b3ac477e1754e74d202cdd133e6b5b96051b","other":"9f4c2e7b1c80175a9d7d85c983d9ee406e5d7ba644b0f8e85196c8bc7cbcbf71","up":false},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"b30eed51a958a822528b126471cf7088424a7913a5e89d6aaf989c968a0c4be9","up":true},{"one":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","other":"6701b38360d0a72ddc355c17385af34b7ed9eeec00bc3867e162b5571af69e3f","up":false},{"one":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","other":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","up":false},{"one":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","other":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","up":true},{"one":"1ea1653a07ce2e07a40018f3a5e8dce0258177e95696a78c57ea3eecc26e6949","other":"020f57c369b6521552f7498e444aed21ac57832ed3ffaa7b45f607793ea1a86c","up":false},{"one":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","other":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","up":false},{"one":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","other":"83d0ffd43e2469b9c3498862d0186e413cc4d205f7854bf9dd73c4d74379217c","up":false},{"one":"e4d6b7889d454cd01ee933f9ae28221702495d79acbd10458082ce90c09fb691","other":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","up":false},{"one":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","other":"c60e0a8c32d6842453792124d495af30721e79f29947938135f35b341c433ef4","up":false},{"one":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","other":"d455034d04322ce766b48f825dcac69ea9b0f749bd5cb8ab2c4e3a18b1c70ef8","up":true},{"one":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","other":"c3d8b4ee532f606e460666e45569c7f54baf4acc69e1ed027af4d7f11e194dae","up":false},{"one":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","other":"a0c98f72caba5cb7a4009937ff949b745b2330ed8b1a71ce70f4e59d50459449","up":false},{"one":"b3910e693ef69c37cdfc9f831f04fa8549797edd16ac316a95fe5f9fde3ab3a2","other":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","up":true},{"one":"7299df5ce4d90e2e7bfae929fc3fca7175b8899681e626041d8e5721b2ce8e9f","other":"07a6873c7eadb651196a76ab30241cc1c984549c5086f1c3bb1be7bd493346be","up":false},{"one":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","other":"efc128ce65a388db303d97f1f1844f2050f8d0a95820e8859865490ee273b0ce","up":false},{"one":"1daa2b253dc95a37bc9d238e1ac71be213d6f725b3bbb84c5c93f512e2ee501a","other":"ecff394703253e8778a1501c97b3a6a170e3d9d2b3fd8aa83443dec0edf19896","up":false},{"one":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","other":"4bbc289546fd7ade8b214daa4e8a23139a85d7e46d70a557025339f6fbbee87e","up":false},{"one":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","other":"71bfc67cadc7f06e638f2c87b0e25a4cec35b6655adb27f72b37774125bd38d3","up":false},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","up":true},{"one":"9b87fdd4d96b82263003c90346fc097008ff681bd246227a35eff3b431d6342e","other":"9918da1c179414f75c8d27103c3297a3bf5ccc62ae0bb13859611046536a5959","up":false},{"one":"8434ffb624f336f300f8c4f3315edcaeb99b7f542c8b3c2c972aa6bc210d1302","other":"802e9fa524f1a6a27fdcebf7f25ab28a5c043439b7844b1fc34da8242b6017a0","up":false},{"one":"98bc7e8a4941b065b98f6611935fab096dd4e0bdfd38a9f2ca4b63f2851e2704","other":"940415ad0c47373416bdaea50ed36062a47feb2e09537401f86893f337824ad8","up":false},{"one":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","other":"ee3f23e7e62f9af2906703eb9a1dd51a8c2ab3a9ca79c460e08085e565806eb3","up":false},{"one":"8d853eca48be257b7444327d61cf588218da71c688be9847ee9987d54eaed1ab","other":"86d32fe2816f5523698b81c638eaeca28efb3bc4775cdfb4679e0f99bec8a703","up":false},{"one":"e9fa1a8529dddce276f66c78223ca06ceb02fac95ea6e18b5118a0417b370a09","other":"ee527dffd4bae1880e3c5ddcba2e2fb1706c4d40804e102c9f3d514137ab073e","up":false},{"one":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","other":"a3f2a0d3dff1914624ae113dd0dfef72175f03ded5cba89ebf75e350c4651525","up":false},{"one":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","other":"384305aff56982a885d643f2b7289c5f35658ad5c36a76c1d97030af60ed975f","up":true},{"one":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","other":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","up":false},{"one":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","other":"f2b24713b8f8bcab21081bd17d11d859dd5ffb21c9baddd971fdf2d4b51c1ac6","up":false},{"one":"fc881a3a0da605c7781ba6b122f0507f303fa998185a351976aeae89f4ef8b8b","other":"f6a64941b0561dbbfb387a5d448e40eec0493f1c0c1fae4dcd314a1a51f6b02d","up":false},{"one":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","other":"d777e45233aa4d8b723f6a6aa9cbf5de4c0b19f6fd00af6d538b67ec01b74ab5","up":true},{"one":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","other":"3baa42389305677f589107a8ef58e129ab29d0e16820df4ce2195113eeb0f295","up":true},{"one":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","other":"15ecfd0ece9d43df1cb05bf2cf319c9d954557f3c269f7b0cd7f3edb6c89e581","up":false},{"one":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","other":"60bc3e01e8aace7338d7a326ea7945b5b9d829ae83c5c70dc69fe919b9a31481","up":false},{"one":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","other":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","up":false},{"one":"ebe8037a1344f9b599cca1aca404861c4194529ab7cb23b3f7a081f615427575","other":"a1b6a1a989455f9f1955276a0b3b5b4c19726acf8fa3f7218fab420036fa988f","up":false},{"one":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","other":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","up":false},{"one":"a478f5caa8ae441d8e933f636ff6c1166753c0b443742e20434619044a85081d","other":"e850535d4bce69008b1ac52bfc95a9ed7cde04fbe849a5ade3dbb9fa13a67c9a","up":true},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"a3174cb06f0ed08c640fc23d2840e002f537a07a93ad464beacd92c140a78a9e","up":true},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","up":true},{"one":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","other":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","up":true},{"one":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","other":"0536088e14b81899b914cfcebd2a3c3ddbbbef905ffb69375613d4853667be18","up":false},{"one":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","other":"e31eb44ccf3beb2579ad10ba30c0adb12deeccc54f9c477d30fe6fd8308c1010","up":true},{"one":"9bc3cc7ff18f1eccd36948e6831ce14d0223a27b233d5b839b8662e16f9fb435","other":"175873005cd8b0db7da431cc6cc077615a5c79a88c4698a1ae61f3bea212996f","up":false},{"one":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","other":"4c97be06dae9d63322656fea7bf0de266346b61674f95d1eaff0f14618623b5b","up":false},{"one":"44c8c26204fe5a0a48c62466be8079a60b445b4381422242584d8e1918cde748","other":"464b80305ce1b2094c1294c41aa842aeee31680ac90230dfcf26a3606a07f9a6","up":false},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"294066bf056b1530308d096871b08a046086abcbc7ed7a0a97d9fb46cccd6003","up":false},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"b820b318a3a12a7a7f45d06fff7f5d7e3ca2ca8097c396c1d76c735e1c915cc0","up":true},{"one":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","other":"11a0676648caea71425ae79a55acd71a0f2fdea70bb0dafbb53ac1b8e1249af0","up":false},{"one":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","other":"10b16d53d16acc9fcc9d2321bec7cf6ab7600bf11da955dd4f628d0bb92d99ce","up":false},{"one":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","other":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","up":false},{"one":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","other":"1d5b85468d868cec2f1be5f1c7dc89b33f61d27ab04e750a556b1e3aaf3f7c15","up":false},{"one":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","other":"0020768d96fca0cf47a9632ca25ff794c996042f07a7242ec7c70fbc75e0e45e","up":false},{"one":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","other":"020f57c369b6521552f7498e444aed21ac57832ed3ffaa7b45f607793ea1a86c","up":false},{"one":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","other":"e4697202e563f447c9c93f498365f4dc95d14633ed40387b934c485dc3aac575","up":false},{"one":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","other":"3a182b774ac0fa9d6ea04720cae972bae0cbb9d9673891abab8ae17e463e582f","up":true},{"one":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","other":"1254b1e5c9e80b367520bfefc35cf32a60aff56c48315107c4b07aa1a7866af9","up":false},{"one":"a75dd3af0823e79d336f78762b8d12e55429736f363e540c2c99a8b7bbc535d6","other":"e40694edf0f70c242ad2058bd94fdac0676c19ef93043829e03f7c8a7a23f179","up":false},{"one":"caa321a50e80a5ab3d73f717008f4601f57a21efa873e4f1cc4d6f356fbafdae","other":"3227103897adafbe4fe3aad206bab8253bb802bd980a0c6188500be89e656edd","up":true},{"one":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","other":"e6b7994917b34a1a760a48cca74f592aef94f8afd91686b5db8d0389ded09281","up":true},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","up":false},{"one":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","other":"78109d560f529ec24cc69b69eef6055548225c2965e8c23cf8c0650d2d3dfd2e","up":false},{"one":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","other":"7aef1625409f479ee1abad565f8d7e73b17714e77f54d1ac1b2fe80382a058cc","up":false},{"one":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","other":"7aef1625409f479ee1abad565f8d7e73b17714e77f54d1ac1b2fe80382a058cc","up":true},{"one":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","other":"77bc5628edeafaced10f1f3727a4f579bc69e457720127686d2754bf9803a446","up":true},{"one":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","other":"65a670260af4f38603757fba904cde5dc8f1d3240ad5ec6bca3153a4bebc147a","up":true},{"one":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","other":"2c4eb13e3d02c91e043130f04f9e248c3d1eb9ac7578b3825fabcb30884fd3b0","up":false},{"one":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","other":"2e3b4fb6298c357d2a4cfcdb2fcc0be2cbae0a11219e4cac2d0c01de3925dd52","up":false},{"one":"3d17891f506b4c968758f1d58eb35ca883fdd61ae9ad34ec2c076b506c124229","other":"293f6e407f98ce24090e5ed53fe606bbb272b61a3eec3859203ea54d16991e88","up":false},{"one":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","other":"2f1f2c4c25b97fcd8fbe79506e748b7c0df5b3bc15b884701df127e49548844e","up":false},{"one":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","other":"ac62fbb8e18ecc4bb4e6f5d4a999bc86815803106d451200457b29730619353a","up":false},{"one":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","other":"a9365c4cb8daf8bb018a6ec9bb03e9c75eb1955b26d8847b868db8e302a7aff5","up":false},{"one":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","other":"2c16611229217e96e69d3d4a06f7d44d7cc26e511a816db0362eb52bca1ce3e0","up":false},{"one":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","other":"76a5d17fde4e1c671e0338548501da39332c8ef59329d999b0fb6ff704060591","up":false},{"one":"38d707dcfe1a9d09c03b606dd4db89e6ced004c12f61d8b573b5c9f742f512bb","other":"7239f6fcd537b5d59b85071c962d4958806800de127b172c167c2b814cec4d84","up":false},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"a0e4e6ba6dda0b76cd43f93e7c7b3923e5223d3cb144715f2f38045e5923f99b","up":false},{"one":"1dfde87c7820c28cd673e6da6fa8c9b73542de6b5acfd99dd1316a22af5eb163","other":"167bc81f7d26f99ceeeda814de5bbfa75f8f2a61575570596bc5a3c618f5d230","up":false},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"a011c924104e072ec415cf72255167582e5d3f189f541066ba88b3ae0041b508","up":false},{"one":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","other":"92d565b13068e8b9af102af40e1112a00b91e398ef0c42a292e88d406c55b4f2","up":false},{"one":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","other":"93f88beedce7f9581ecc05615ba15fb313006a32a159adf169f2e02ecc697598","up":false},{"one":"22206b3ddff20a245a900b7bfbf2f400709c2670f0c49713be25eb57136e9f1b","other":"2f230166de0362acabe4258a19fe19d493912e0e0613674a6cc963cf48b376f1","up":false},{"one":"a6bdbd75f03fa311700a17ff4054ebf2855f05ab3f2f4af82cad573d91417a83","other":"a077a6f8832a5d7d2146d9b08de0c9f4ae3515a3f87f2751592e1158adb04155","up":false},{"one":"985f1b6d642a9fb71302fe325b3f39aaaf3c1c7ecd3fedb308c3753f7dd4e235","other":"97474907a03344503279f1d86695233962b6d36e597b6e41b11c941c2ca2dc46","up":false}]} \ No newline at end of file diff --git a/swarm/network/stream/testing/snapshot_32.json b/swarm/network/stream/testing/snapshot_32.json deleted file mode 100644 index 55bd2f22d6e8..000000000000 --- a/swarm/network/stream/testing/snapshot_32.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes":[{"node":{"config":{"id":"7b889ec7b0674ca8f6508a35d920149f1826d410b796e52bfa72306b1fafbae4","private_key":"b571efd8e722db149f98412648275074a828e5137b46722413039489eb617b36","name":"node_d2a392e9e7383b895587a7d6eea4a157de20caa9e2c62b4da5a8ff3186d129fee1330ddf3d180b1f59e1aa83686cd163222e38c84a84a0f60139968d6a56cbca","services":["streamer"],"enable_msg_events":true,"port":61468},"up":true}},{"node":{"config":{"id":"914839ec95b100922429a3616522daffc51c1a4c39b297d1d8cec591567b4ccc","private_key":"fbd2e7bb8b511ed0878fc14a5923105866ab2c3bdf5ee2c52c1a2bc7c00a185e","name":"node_6c0b3b7c1c08bb558d92f0172fb8bfabe52a4bf8348b2f82e50c6d492a595dd0677b4559d7d2cd5a6e51ecdaf9a0f898f154730b87a8d9b265c6a54f9f6802a7","services":["streamer"],"enable_msg_events":true,"port":61469},"up":true}},{"node":{"config":{"id":"484875287058e78ed92396e00f34e75f907a1f42d5597b9e8c30afcbe50c1810","private_key":"60a362da222a74d3d81ed679549417d55a92c79b8e0aab1bf05eb2d01f82c0b2","name":"node_dc63b83b488b7b505d5b2a60680b7dc87b1c5b61509e8030ebc67512ba72c0f616c1594946878dfb29e79f351d7134306778a74d5f5b7d5e08dbdb6dd6ade359","services":["streamer"],"enable_msg_events":true,"port":61470},"up":true}},{"node":{"config":{"id":"a82935d4ce212895fad97829cc8e04bd1ae1c53323af4e6d8e2c673b7dfaabb9","private_key":"00625fb0f90ed61656987faecdd0690f0a97c88e33f6e339fa3a8f4bfc1e640f","name":"node_eaa3ad48682b08d9c91907a79e82938c1b30108745b276788c830dfcb0c0e752134369d751230e95fb7ab9777c38dcd105ad123ec5cc86ef6dbdd11fe31aea93","services":["streamer"],"enable_msg_events":true,"port":61471},"up":true}},{"node":{"config":{"id":"d5355c5eced7ca0b210934d2e4f11ae09bec1c62246548ea092dc29e9987171f","private_key":"fbb38d0ed3ef4726052fdec97ddd7eb3a3feb3d2fd5ea6cffe8ff97b621958ce","name":"node_331084cce2884ad912d5a20467c68fc261a02d6df0b60ff5b1427edc0885eab66a4f0f7e7fc9c676e994842f8126a3b34dbde91243f26bed6a0ba14babed56cc","services":["streamer"],"enable_msg_events":true,"port":61472},"up":true}},{"node":{"config":{"id":"e7ac9c3a8ca80703b3499a376d57e2a9523b3bd3c5cf35b7a450f0af44680686","private_key":"b5c12698375aa5215df48003ed13c94e00e6bee1a0b05277237d40f0fcfc625e","name":"node_e00f9a5611008d54bbbfe6a1cf402e011c92f540b47a82b7795508d1b857eee4a522e26c8351ddf6e303d353ee35afecbf9f54d7aae36e6736174473ac4caf73","services":["streamer"],"enable_msg_events":true,"port":61473},"up":true}},{"node":{"config":{"id":"231cf4ad6466edcff849ba5f44069f51267dfda535d31a4d7241f5d83c1a521a","private_key":"90e044c682c33ed90f5f5cac9b8181ee978d2cc9d3d3f545140858d324f1d7e1","name":"node_e4b7b3bbeda388842ecb3688c041fe58bbd7e4566818a58b679e96215553dbf8569032a3ae941b3f8bb3e75886852d245bda5abf0c3cf70f532c38235e1a2175","services":["streamer"],"enable_msg_events":true,"port":61474},"up":true}},{"node":{"config":{"id":"a1b5ab3013c0031a169e72847a4509e83b994086fdd82b07fb6ca0d7cfe9d537","private_key":"2371e6ff2ecc4144ee61efa958488eed6c1f739e81f27bf775db1e9b66e89054","name":"node_0679dbbb51fa0283bcba387daa2180955d402a0ed42155095fcaffb96dbfd6b4b1bb0ed2ccf11cd990dfed4a3b320ee3f64286116a7b617a35989a448c367119","services":["streamer"],"enable_msg_events":true,"port":61475},"up":true}},{"node":{"config":{"id":"d19a0c646122e8d0b8148da1f64b3cb781fe974687a071e5a8522e9a22bc6646","private_key":"87a3816c95159c72a7ad1b466ae9a095f12c7db210fcddd8dbaaa697220f88bb","name":"node_a197cd9e7e2d19287745f486582b7242c991f36183e8b2bf4983d7a9b9026e9884e3295183f376cd796b814fdf352b1c8c5c609a0e5f5d5155ab925d4b6fdeb9","services":["streamer"],"enable_msg_events":true,"port":61476},"up":true}},{"node":{"config":{"id":"930de56c1ea44a1b39f2f99f6780ce284042d59238adb1047167c6329be4a291","private_key":"6729988ebc04b9398883c068277630ff0de16f16e3f67114380acfdb0d1b530b","name":"node_eb11498b8cebc767d535481938c664359c6c84674da4d8fac729df64e288248bb020477ad4422cf1b7cea844db5ee12bb5cea82ac6e89a338d8ca1073e061f4c","services":["streamer"],"enable_msg_events":true,"port":61477},"up":true}},{"node":{"config":{"id":"d514861e573828617604db5b88f407b152409308657454f99fc2820b44a45088","private_key":"208068d7a920e42db04340bf473f822d4d663f5139d664c4375294dd61b1dff3","name":"node_d3b5df108f35a82e569016a06fefadb52797dafb505de1981f6678e2156574893a169b0adcc595377267dec0b7d620b5b50c7e7696c39eecee2c3e5f7bf71d18","services":["streamer"],"enable_msg_events":true,"port":61478},"up":true}},{"node":{"config":{"id":"d668b8cc215cdbc92be719a33623494090ae0fb1a80f53bde0315b1a8fb3d139","private_key":"b3a7fa29c4cbf15e8fbbf289ab9876b438bc80092284efedde8e8d41ac76ad78","name":"node_1be1903b1104bfae07438fb888993cb3e812fc38c8f26af2e68bff9f1758e9a69b29be3636ee24060c09ce900bad2004681e1083792b2a6e532bbfc07a3a0459","services":["streamer"],"enable_msg_events":true,"port":61479},"up":true}},{"node":{"config":{"id":"391e49ed641480c2c40febdb23263264c83b2b6cdf3400f6526f7939c0245c17","private_key":"91b7188a3178bda6cd2991a615801d744f4963a3de963adb8afb3f12d02cbc0d","name":"node_26a6b8d30e0a8954bd9a0424b817ff79fec2e0521c1313c8262be9a3b9a0d1ee7f109be78d65ca9eb811a1634a813f4ba609887fd3c0085628b2a8ebd3426031","services":["streamer"],"enable_msg_events":true,"port":61480},"up":true}},{"node":{"config":{"id":"b412abec3dd5145d9236498056db35e50e0e90697016c757724c15a2e9b187af","private_key":"235b967cd72cdb90b423f2b5604e9157f660b253cd2df62369f204f45c3c4d7e","name":"node_255c353a7585ab1bab41bcca409383dcdff5837bdcd7cebece1b6896fc06d0f65346ba618dc057c77734439612cb8cc3fd8aa068eb7569f0ad9e9e172daea678","services":["streamer"],"enable_msg_events":true,"port":61481},"up":true}},{"node":{"config":{"id":"59a3ef9ba5d0a0137eab9442657cbaaae5bfcd6e77f9678b1b2616d44e442caf","private_key":"f97bf73ba53833e4f4c44023e87cef8ef7aeae380553e4a5a4f3215748646a27","name":"node_c88ceea7f4d5148de8e2d71ffefc5c336cc62d322eb4371e457cbb97573b6323d200ff7678f94ef481c7b9daca3a9a7bab1e658d51fa279d433e19305df57814","services":["streamer"],"enable_msg_events":true,"port":61482},"up":true}},{"node":{"config":{"id":"35b6e31caedb996b4a87a6b12b9a29eaacb4aba4c334543ba84b26d6bcbd65f0","private_key":"c4c2ee3b5792dc5d86b51c4e13c813df452094ff2f6296403d801adb1f75a500","name":"node_4299205173b81c2822767a53cb4728fd5c0e1602d5976ee5db63b8118e489e4645ccdad399d9367e9b87b0a1f39e7988db86af687fe8cfff35d618b19d64b1a9","services":["streamer"],"enable_msg_events":true,"port":61483},"up":true}},{"node":{"config":{"id":"8e5f934e6d0f0bf34425c3db2db405d238f6512cf132926ebd0b596fc721fbe4","private_key":"b39dd34865d2f02c1d4c5ef2ca635ef5be43c4b2318b133d281b02dacd8f5420","name":"node_5ed2923d11037da9799d933ea62cd23ce1aacec66a83a6d3a7e420ea0c66ec00af37977a78374df12dc0facb426d0c39cf306bea15abaf40f9072eacc4cf40fe","services":["streamer"],"enable_msg_events":true,"port":61484},"up":true}},{"node":{"config":{"id":"b17b568780b3a029d4292a742890d41be6a71031b15e6276792a77a9746f0699","private_key":"a93bc07ea3d259a03c75a6777e9401f062a8064f6837fda8a1abd518774a75ac","name":"node_66ce6e2c75f429c4bc7de6f1c785258686cfd66867336a1030830bbcc78bf642f4d1920ab5fc68953b70b7aca02ec518515d48d20397ed207ccbeb88c36e7057","services":["streamer"],"enable_msg_events":true,"port":61485},"up":true}},{"node":{"config":{"id":"8d0984a32d4b044065588b4bbe48531a30881bb869399c81b90b349e57f07ffc","private_key":"787adc2aab8500ce6ca1768040b4c81511e7982f525c0280830f8c32486a6fa0","name":"node_cf7d1a8ec1c3e68e862ef18947098ccbf6238fa7f85ee0018b7ac63b5387d6a9dbf194e3aad7edd1fda63ea63f2f5b8692ff885084adfd1f7b6613593960afc4","services":["streamer"],"enable_msg_events":true,"port":61486},"up":true}},{"node":{"config":{"id":"9756cdaac61a9ec4d346e352862ddbdb627f56b5bab132a31348359f62652824","private_key":"6a830e44eac33e88e29c9119fef789abd89503a44ae8b8dd64c0a26046df4bf7","name":"node_a54b9c0cbc07e65b216f78bc0f56cd7003896641f1aa361fdc11e0bc208ee6bc876a455e89dee8abf67c259c7b6a82e7ac0d6d06cc304ee05fbc26783c09863a","services":["streamer"],"enable_msg_events":true,"port":61487},"up":true}},{"node":{"config":{"id":"de95f5a58a8880b9e98f71c687afa472882244c012439719b6b066930355c993","private_key":"71341041894a268f86cf62e8e80605911c19f38266bc49358c54f9955b4bd984","name":"node_82bd5207dec292f911ff7dbcd242bf8962ab256057b1173dba2d71cd8cc77d41f38bf8d9a4eed83edeeb0e6f5bfabe8c7cae9490cc17dca1f454b9ad1b76224b","services":["streamer"],"enable_msg_events":true,"port":61488},"up":true}},{"node":{"config":{"id":"24a154986fbc308c5f2e3fba99f3263743b669ad88ffdd2252cce5fe0c02c279","private_key":"1a7bf17688ee55a634ddbf081213180f5a6e171a4b04679f50a949e88287591f","name":"node_61cbdddf6163152f5f8bbe3c710eeb9afb22dbec78d8f7eb3fd45ba44493b9387765527f059a10e694842786959c501303b1e62d3454c84870c14e0f36a58b0a","services":["streamer"],"enable_msg_events":true,"port":61489},"up":true}},{"node":{"config":{"id":"bfb02423df06888f87e8fbde52c2373020896f2a3dea69ecd2efa89c1aa298ac","private_key":"87d1292398bace7bdd961dc2e1f028d405eeafebf90ef8d8f4b411fdfd74d767","name":"node_74d5d4ec7fdc749d9148b1a338b643e0a06abe03720b1d20b505da1ff1585b09d2cf7053293544fae1409cf26a27144870125787a473a73a1c4ca98509dc67ca","services":["streamer"],"enable_msg_events":true,"port":61490},"up":true}},{"node":{"config":{"id":"7514001adcdeb5c9a0e88fb54082e96a5c162430dfc56b6da858607880366ff5","private_key":"1d65bea3808741aeabc5293fcb863c594c7ec07c955b3f23e28d2ef4b0b6e578","name":"node_12934e06d8821c5e149d944ee7a831428dc6c9eee8705f3b21046fdadc2864a9ffe2bdad60a4ba5a34d7a79c22631d52b62d511e81971b6b98896acae9427e94","services":["streamer"],"enable_msg_events":true,"port":61491},"up":true}},{"node":{"config":{"id":"c71983aed3779037757e433a4f916be40f5199e89861a3998ccdd7e5e76f69b4","private_key":"f067299a421cd124f8652427793dc41c8a5e6bf1dc2c4de59ef396ca08e80256","name":"node_e0e0e90fa669023a1a9c564727cc657ce564c23e05b1c2b61f6bfbd5eb03feb75d34b029886e3426e1bbe4a49398794dfefa0557b7db57c15700c69c0e038029","services":["streamer"],"enable_msg_events":true,"port":61492},"up":true}},{"node":{"config":{"id":"a4e6f50a17347a8475d6b704484b06869d9625993761acbde6a0ae5ac0b40762","private_key":"5f76a5630d4f1979d6d03dfb0c8836de7852e4ad14536f4b999e565b599027b8","name":"node_1149e4bcd71c75b14ba7492587fa77e4a4de14e5c580a165ad8835258d1afcb15e65d02257ad8884b0e47be061d3129d8e3af90cbe4a1a96f828a1f84e0d72c6","services":["streamer"],"enable_msg_events":true,"port":61493},"up":true}},{"node":{"config":{"id":"5ec7e6ffb44f67eccc35adbf0923755cd5b6a8abcdd371db9b1be555780ca5a1","private_key":"0e4febbd0bb937f5992d0e640d5b599a0dfc3b40740d7a0a5fd16423dfcc87fb","name":"node_695e5706cece9d923c19a147283b772dd1ffa79e8d08506d2ed897d89545ffbdac1a130bd7ceeb82a2a57f2b4a613aa6e7264911b909fb9bff27cd435b802687","services":["streamer"],"enable_msg_events":true,"port":61494},"up":true}},{"node":{"config":{"id":"8f7dc84801ba8da80761fe1e0d2fe72a48e45fa753cc8bbe930bf9277a219432","private_key":"54d714717b902eb733a693d473a4b4b243318a9fd68d1b8abe98eabcbaa8c015","name":"node_e28c769630f19e79966bc354bb38313522778e4f06cd0872db8bd3afd7651882eba7b0403d82701cd063cbaee4ba0ba4a97c0c488fb44e8c8b72368d6881fdcf","services":["streamer"],"enable_msg_events":true,"port":61495},"up":true}},{"node":{"config":{"id":"d81271c644a75f0bf9d785319aded8b7a0e9323f85e81e94ef951e8c66251e75","private_key":"43e07e3593646f9ec6c3b630daa34ba80dc37bc0f5c57e3f3097f3503de413dc","name":"node_2b1d1ec3ee35fa63571f46a2781d5fb14379319da2d67e482c24b27bacc2b792cd61a2c1138b8ba27673b1c08588064fdf9c73c80f0278e4f0ba133d78127337","services":["streamer"],"enable_msg_events":true,"port":61496},"up":true}},{"node":{"config":{"id":"deeef9d38d9afb206ee7e6447e80befaf387c2af476a7e0c60c55c73dd36450d","private_key":"ef8ebfc1df0b19885db4b08df697c11a86c2442bce48d5f6f807b7e8dec70e04","name":"node_b5e3b3cc7ac15224ff10cf8974ceaf7ad2ebb18ab9505bbb342c42e6ca65fe3541c14e1ee10ac3a0e7a0d87c29e25114e0c0d957d5dbbadf6136805be3046b4c","services":["streamer"],"enable_msg_events":true,"port":61497},"up":true}},{"node":{"config":{"id":"a0ca706ab2ba950ff9bb92123c66c95132a63b9c93975b9b6e0d485937dd8f65","private_key":"1cb586254c20e9a6f4cc428f878dcfbcc8257f00c21efa88519de7a425cb5e4f","name":"node_fb6e36b9de5eef05780efcf16113fc9098a699ac525bd970dc5b6a92f596da6ffca044e434b97cd712a4035c38dd3ab08488042519f6269bc5057853b643bbcc","services":["streamer"],"enable_msg_events":true,"port":61498},"up":true}},{"node":{"config":{"id":"dab5098cf929579a87e26d30f9a8b001ad3158afe0f6231c50e58f33c6d62207","private_key":"2621e61b3b896a37c2b4ee05a264bb685f4a70c3caf8a162eee28dd6e64fa2fd","name":"node_aca2af376ef1c983514f75cd717c0852e9d4321e705517196fab4fafe109c1f3581866e1865d41977f8c23a31ec7fa218c11db0c87eb4bb96390025a96386f9a","services":["streamer"],"enable_msg_events":true,"port":61499},"up":true}}],"conns":[{"one":"dab5098cf929579a87e26d30f9a8b001ad3158afe0f6231c50e58f33c6d62207","other":"7b889ec7b0674ca8f6508a35d920149f1826d410b796e52bfa72306b1fafbae4","up":true},{"one":"7b889ec7b0674ca8f6508a35d920149f1826d410b796e52bfa72306b1fafbae4","other":"914839ec95b100922429a3616522daffc51c1a4c39b297d1d8cec591567b4ccc","up":true},{"one":"914839ec95b100922429a3616522daffc51c1a4c39b297d1d8cec591567b4ccc","other":"484875287058e78ed92396e00f34e75f907a1f42d5597b9e8c30afcbe50c1810","up":true},{"one":"484875287058e78ed92396e00f34e75f907a1f42d5597b9e8c30afcbe50c1810","other":"a82935d4ce212895fad97829cc8e04bd1ae1c53323af4e6d8e2c673b7dfaabb9","up":true},{"one":"a82935d4ce212895fad97829cc8e04bd1ae1c53323af4e6d8e2c673b7dfaabb9","other":"d5355c5eced7ca0b210934d2e4f11ae09bec1c62246548ea092dc29e9987171f","up":true},{"one":"d5355c5eced7ca0b210934d2e4f11ae09bec1c62246548ea092dc29e9987171f","other":"e7ac9c3a8ca80703b3499a376d57e2a9523b3bd3c5cf35b7a450f0af44680686","up":true},{"one":"e7ac9c3a8ca80703b3499a376d57e2a9523b3bd3c5cf35b7a450f0af44680686","other":"231cf4ad6466edcff849ba5f44069f51267dfda535d31a4d7241f5d83c1a521a","up":true},{"one":"231cf4ad6466edcff849ba5f44069f51267dfda535d31a4d7241f5d83c1a521a","other":"a1b5ab3013c0031a169e72847a4509e83b994086fdd82b07fb6ca0d7cfe9d537","up":true},{"one":"a0ca706ab2ba950ff9bb92123c66c95132a63b9c93975b9b6e0d485937dd8f65","other":"dab5098cf929579a87e26d30f9a8b001ad3158afe0f6231c50e58f33c6d62207","up":true},{"one":"deeef9d38d9afb206ee7e6447e80befaf387c2af476a7e0c60c55c73dd36450d","other":"a0ca706ab2ba950ff9bb92123c66c95132a63b9c93975b9b6e0d485937dd8f65","up":true},{"one":"a1b5ab3013c0031a169e72847a4509e83b994086fdd82b07fb6ca0d7cfe9d537","other":"d19a0c646122e8d0b8148da1f64b3cb781fe974687a071e5a8522e9a22bc6646","up":true},{"one":"d19a0c646122e8d0b8148da1f64b3cb781fe974687a071e5a8522e9a22bc6646","other":"930de56c1ea44a1b39f2f99f6780ce284042d59238adb1047167c6329be4a291","up":true},{"one":"930de56c1ea44a1b39f2f99f6780ce284042d59238adb1047167c6329be4a291","other":"d514861e573828617604db5b88f407b152409308657454f99fc2820b44a45088","up":true},{"one":"d514861e573828617604db5b88f407b152409308657454f99fc2820b44a45088","other":"d668b8cc215cdbc92be719a33623494090ae0fb1a80f53bde0315b1a8fb3d139","up":true},{"one":"d668b8cc215cdbc92be719a33623494090ae0fb1a80f53bde0315b1a8fb3d139","other":"391e49ed641480c2c40febdb23263264c83b2b6cdf3400f6526f7939c0245c17","up":true},{"one":"391e49ed641480c2c40febdb23263264c83b2b6cdf3400f6526f7939c0245c17","other":"b412abec3dd5145d9236498056db35e50e0e90697016c757724c15a2e9b187af","up":true},{"one":"b412abec3dd5145d9236498056db35e50e0e90697016c757724c15a2e9b187af","other":"59a3ef9ba5d0a0137eab9442657cbaaae5bfcd6e77f9678b1b2616d44e442caf","up":true},{"one":"59a3ef9ba5d0a0137eab9442657cbaaae5bfcd6e77f9678b1b2616d44e442caf","other":"35b6e31caedb996b4a87a6b12b9a29eaacb4aba4c334543ba84b26d6bcbd65f0","up":true},{"one":"35b6e31caedb996b4a87a6b12b9a29eaacb4aba4c334543ba84b26d6bcbd65f0","other":"8e5f934e6d0f0bf34425c3db2db405d238f6512cf132926ebd0b596fc721fbe4","up":true},{"one":"8e5f934e6d0f0bf34425c3db2db405d238f6512cf132926ebd0b596fc721fbe4","other":"b17b568780b3a029d4292a742890d41be6a71031b15e6276792a77a9746f0699","up":true},{"one":"b17b568780b3a029d4292a742890d41be6a71031b15e6276792a77a9746f0699","other":"8d0984a32d4b044065588b4bbe48531a30881bb869399c81b90b349e57f07ffc","up":true},{"one":"8d0984a32d4b044065588b4bbe48531a30881bb869399c81b90b349e57f07ffc","other":"9756cdaac61a9ec4d346e352862ddbdb627f56b5bab132a31348359f62652824","up":true},{"one":"9756cdaac61a9ec4d346e352862ddbdb627f56b5bab132a31348359f62652824","other":"de95f5a58a8880b9e98f71c687afa472882244c012439719b6b066930355c993","up":true},{"one":"de95f5a58a8880b9e98f71c687afa472882244c012439719b6b066930355c993","other":"24a154986fbc308c5f2e3fba99f3263743b669ad88ffdd2252cce5fe0c02c279","up":true},{"one":"24a154986fbc308c5f2e3fba99f3263743b669ad88ffdd2252cce5fe0c02c279","other":"bfb02423df06888f87e8fbde52c2373020896f2a3dea69ecd2efa89c1aa298ac","up":true},{"one":"bfb02423df06888f87e8fbde52c2373020896f2a3dea69ecd2efa89c1aa298ac","other":"7514001adcdeb5c9a0e88fb54082e96a5c162430dfc56b6da858607880366ff5","up":true},{"one":"7514001adcdeb5c9a0e88fb54082e96a5c162430dfc56b6da858607880366ff5","other":"c71983aed3779037757e433a4f916be40f5199e89861a3998ccdd7e5e76f69b4","up":true},{"one":"c71983aed3779037757e433a4f916be40f5199e89861a3998ccdd7e5e76f69b4","other":"a4e6f50a17347a8475d6b704484b06869d9625993761acbde6a0ae5ac0b40762","up":true},{"one":"a4e6f50a17347a8475d6b704484b06869d9625993761acbde6a0ae5ac0b40762","other":"5ec7e6ffb44f67eccc35adbf0923755cd5b6a8abcdd371db9b1be555780ca5a1","up":true},{"one":"5ec7e6ffb44f67eccc35adbf0923755cd5b6a8abcdd371db9b1be555780ca5a1","other":"8f7dc84801ba8da80761fe1e0d2fe72a48e45fa753cc8bbe930bf9277a219432","up":true},{"one":"8f7dc84801ba8da80761fe1e0d2fe72a48e45fa753cc8bbe930bf9277a219432","other":"d81271c644a75f0bf9d785319aded8b7a0e9323f85e81e94ef951e8c66251e75","up":true},{"one":"d81271c644a75f0bf9d785319aded8b7a0e9323f85e81e94ef951e8c66251e75","other":"deeef9d38d9afb206ee7e6447e80befaf387c2af476a7e0c60c55c73dd36450d","up":true},{"one":"7b889ec7b0674ca8f6508a35d920149f1826d410b796e52bfa72306b1fafbae4","other":"484875287058e78ed92396e00f34e75f907a1f42d5597b9e8c30afcbe50c1810","up":true},{"one":"a82935d4ce212895fad97829cc8e04bd1ae1c53323af4e6d8e2c673b7dfaabb9","other":"a0ca706ab2ba950ff9bb92123c66c95132a63b9c93975b9b6e0d485937dd8f65","up":true},{"one":"d5355c5eced7ca0b210934d2e4f11ae09bec1c62246548ea092dc29e9987171f","other":"d514861e573828617604db5b88f407b152409308657454f99fc2820b44a45088","up":true},{"one":"391e49ed641480c2c40febdb23263264c83b2b6cdf3400f6526f7939c0245c17","other":"35b6e31caedb996b4a87a6b12b9a29eaacb4aba4c334543ba84b26d6bcbd65f0","up":true},{"one":"b412abec3dd5145d9236498056db35e50e0e90697016c757724c15a2e9b187af","other":"b17b568780b3a029d4292a742890d41be6a71031b15e6276792a77a9746f0699","up":true},{"one":"9756cdaac61a9ec4d346e352862ddbdb627f56b5bab132a31348359f62652824","other":"8f7dc84801ba8da80761fe1e0d2fe72a48e45fa753cc8bbe930bf9277a219432","up":true},{"one":"c71983aed3779037757e433a4f916be40f5199e89861a3998ccdd7e5e76f69b4","other":"d81271c644a75f0bf9d785319aded8b7a0e9323f85e81e94ef951e8c66251e75","up":true},{"one":"8e5f934e6d0f0bf34425c3db2db405d238f6512cf132926ebd0b596fc721fbe4","other":"8f7dc84801ba8da80761fe1e0d2fe72a48e45fa753cc8bbe930bf9277a219432","up":true},{"one":"deeef9d38d9afb206ee7e6447e80befaf387c2af476a7e0c60c55c73dd36450d","other":"de95f5a58a8880b9e98f71c687afa472882244c012439719b6b066930355c993","up":true},{"one":"8d0984a32d4b044065588b4bbe48531a30881bb869399c81b90b349e57f07ffc","other":"8e5f934e6d0f0bf34425c3db2db405d238f6512cf132926ebd0b596fc721fbe4","up":true},{"one":"7514001adcdeb5c9a0e88fb54082e96a5c162430dfc56b6da858607880366ff5","other":"5ec7e6ffb44f67eccc35adbf0923755cd5b6a8abcdd371db9b1be555780ca5a1","up":true},{"one":"35b6e31caedb996b4a87a6b12b9a29eaacb4aba4c334543ba84b26d6bcbd65f0","other":"231cf4ad6466edcff849ba5f44069f51267dfda535d31a4d7241f5d83c1a521a","up":true},{"one":"930de56c1ea44a1b39f2f99f6780ce284042d59238adb1047167c6329be4a291","other":"9756cdaac61a9ec4d346e352862ddbdb627f56b5bab132a31348359f62652824","up":true},{"one":"e7ac9c3a8ca80703b3499a376d57e2a9523b3bd3c5cf35b7a450f0af44680686","other":"d514861e573828617604db5b88f407b152409308657454f99fc2820b44a45088","up":true},{"one":"5ec7e6ffb44f67eccc35adbf0923755cd5b6a8abcdd371db9b1be555780ca5a1","other":"59a3ef9ba5d0a0137eab9442657cbaaae5bfcd6e77f9678b1b2616d44e442caf","up":true},{"one":"7b889ec7b0674ca8f6508a35d920149f1826d410b796e52bfa72306b1fafbae4","other":"7514001adcdeb5c9a0e88fb54082e96a5c162430dfc56b6da858607880366ff5","up":true},{"one":"a82935d4ce212895fad97829cc8e04bd1ae1c53323af4e6d8e2c673b7dfaabb9","other":"a1b5ab3013c0031a169e72847a4509e83b994086fdd82b07fb6ca0d7cfe9d537","up":true},{"one":"e7ac9c3a8ca80703b3499a376d57e2a9523b3bd3c5cf35b7a450f0af44680686","other":"d668b8cc215cdbc92be719a33623494090ae0fb1a80f53bde0315b1a8fb3d139","up":true},{"one":"d5355c5eced7ca0b210934d2e4f11ae09bec1c62246548ea092dc29e9987171f","other":"d668b8cc215cdbc92be719a33623494090ae0fb1a80f53bde0315b1a8fb3d139","up":true},{"one":"930de56c1ea44a1b39f2f99f6780ce284042d59238adb1047167c6329be4a291","other":"8d0984a32d4b044065588b4bbe48531a30881bb869399c81b90b349e57f07ffc","up":true},{"one":"391e49ed641480c2c40febdb23263264c83b2b6cdf3400f6526f7939c0245c17","other":"231cf4ad6466edcff849ba5f44069f51267dfda535d31a4d7241f5d83c1a521a","up":true},{"one":"b412abec3dd5145d9236498056db35e50e0e90697016c757724c15a2e9b187af","other":"bfb02423df06888f87e8fbde52c2373020896f2a3dea69ecd2efa89c1aa298ac","up":true},{"one":"35b6e31caedb996b4a87a6b12b9a29eaacb4aba4c334543ba84b26d6bcbd65f0","other":"24a154986fbc308c5f2e3fba99f3263743b669ad88ffdd2252cce5fe0c02c279","up":true},{"one":"8e5f934e6d0f0bf34425c3db2db405d238f6512cf132926ebd0b596fc721fbe4","other":"e7ac9c3a8ca80703b3499a376d57e2a9523b3bd3c5cf35b7a450f0af44680686","up":true},{"one":"8d0984a32d4b044065588b4bbe48531a30881bb869399c81b90b349e57f07ffc","other":"8f7dc84801ba8da80761fe1e0d2fe72a48e45fa753cc8bbe930bf9277a219432","up":true},{"one":"231cf4ad6466edcff849ba5f44069f51267dfda535d31a4d7241f5d83c1a521a","other":"24a154986fbc308c5f2e3fba99f3263743b669ad88ffdd2252cce5fe0c02c279","up":true},{"one":"9756cdaac61a9ec4d346e352862ddbdb627f56b5bab132a31348359f62652824","other":"8e5f934e6d0f0bf34425c3db2db405d238f6512cf132926ebd0b596fc721fbe4","up":true},{"one":"7514001adcdeb5c9a0e88fb54082e96a5c162430dfc56b6da858607880366ff5","other":"59a3ef9ba5d0a0137eab9442657cbaaae5bfcd6e77f9678b1b2616d44e442caf","up":true},{"one":"c71983aed3779037757e433a4f916be40f5199e89861a3998ccdd7e5e76f69b4","other":"de95f5a58a8880b9e98f71c687afa472882244c012439719b6b066930355c993","up":true},{"one":"5ec7e6ffb44f67eccc35adbf0923755cd5b6a8abcdd371db9b1be555780ca5a1","other":"7b889ec7b0674ca8f6508a35d920149f1826d410b796e52bfa72306b1fafbae4","up":true},{"one":"deeef9d38d9afb206ee7e6447e80befaf387c2af476a7e0c60c55c73dd36450d","other":"dab5098cf929579a87e26d30f9a8b001ad3158afe0f6231c50e58f33c6d62207","up":true},{"one":"dab5098cf929579a87e26d30f9a8b001ad3158afe0f6231c50e58f33c6d62207","other":"d81271c644a75f0bf9d785319aded8b7a0e9323f85e81e94ef951e8c66251e75","up":true},{"one":"d19a0c646122e8d0b8148da1f64b3cb781fe974687a071e5a8522e9a22bc6646","other":"d514861e573828617604db5b88f407b152409308657454f99fc2820b44a45088","up":true},{"one":"b17b568780b3a029d4292a742890d41be6a71031b15e6276792a77a9746f0699","other":"bfb02423df06888f87e8fbde52c2373020896f2a3dea69ecd2efa89c1aa298ac","up":true},{"one":"24a154986fbc308c5f2e3fba99f3263743b669ad88ffdd2252cce5fe0c02c279","other":"391e49ed641480c2c40febdb23263264c83b2b6cdf3400f6526f7939c0245c17","up":true},{"one":"de95f5a58a8880b9e98f71c687afa472882244c012439719b6b066930355c993","other":"d81271c644a75f0bf9d785319aded8b7a0e9323f85e81e94ef951e8c66251e75","up":true},{"one":"d668b8cc215cdbc92be719a33623494090ae0fb1a80f53bde0315b1a8fb3d139","other":"930de56c1ea44a1b39f2f99f6780ce284042d59238adb1047167c6329be4a291","up":true},{"one":"a1b5ab3013c0031a169e72847a4509e83b994086fdd82b07fb6ca0d7cfe9d537","other":"a0ca706ab2ba950ff9bb92123c66c95132a63b9c93975b9b6e0d485937dd8f65","up":true},{"one":"bfb02423df06888f87e8fbde52c2373020896f2a3dea69ecd2efa89c1aa298ac","other":"e7ac9c3a8ca80703b3499a376d57e2a9523b3bd3c5cf35b7a450f0af44680686","up":true},{"one":"7b889ec7b0674ca8f6508a35d920149f1826d410b796e52bfa72306b1fafbae4","other":"59a3ef9ba5d0a0137eab9442657cbaaae5bfcd6e77f9678b1b2616d44e442caf","up":true},{"one":"a0ca706ab2ba950ff9bb92123c66c95132a63b9c93975b9b6e0d485937dd8f65","other":"a4e6f50a17347a8475d6b704484b06869d9625993761acbde6a0ae5ac0b40762","up":true},{"one":"a82935d4ce212895fad97829cc8e04bd1ae1c53323af4e6d8e2c673b7dfaabb9","other":"a4e6f50a17347a8475d6b704484b06869d9625993761acbde6a0ae5ac0b40762","up":true},{"one":"d5355c5eced7ca0b210934d2e4f11ae09bec1c62246548ea092dc29e9987171f","other":"7b889ec7b0674ca8f6508a35d920149f1826d410b796e52bfa72306b1fafbae4","up":true},{"one":"e7ac9c3a8ca80703b3499a376d57e2a9523b3bd3c5cf35b7a450f0af44680686","other":"d19a0c646122e8d0b8148da1f64b3cb781fe974687a071e5a8522e9a22bc6646","up":true},{"one":"a1b5ab3013c0031a169e72847a4509e83b994086fdd82b07fb6ca0d7cfe9d537","other":"a4e6f50a17347a8475d6b704484b06869d9625993761acbde6a0ae5ac0b40762","up":true},{"one":"231cf4ad6466edcff849ba5f44069f51267dfda535d31a4d7241f5d83c1a521a","other":"7b889ec7b0674ca8f6508a35d920149f1826d410b796e52bfa72306b1fafbae4","up":true},{"one":"d19a0c646122e8d0b8148da1f64b3cb781fe974687a071e5a8522e9a22bc6646","other":"d5355c5eced7ca0b210934d2e4f11ae09bec1c62246548ea092dc29e9987171f","up":true},{"one":"930de56c1ea44a1b39f2f99f6780ce284042d59238adb1047167c6329be4a291","other":"914839ec95b100922429a3616522daffc51c1a4c39b297d1d8cec591567b4ccc","up":true},{"one":"d668b8cc215cdbc92be719a33623494090ae0fb1a80f53bde0315b1a8fb3d139","other":"c71983aed3779037757e433a4f916be40f5199e89861a3998ccdd7e5e76f69b4","up":true},{"one":"b412abec3dd5145d9236498056db35e50e0e90697016c757724c15a2e9b187af","other":"e7ac9c3a8ca80703b3499a376d57e2a9523b3bd3c5cf35b7a450f0af44680686","up":true},{"one":"391e49ed641480c2c40febdb23263264c83b2b6cdf3400f6526f7939c0245c17","other":"7b889ec7b0674ca8f6508a35d920149f1826d410b796e52bfa72306b1fafbae4","up":true},{"one":"9756cdaac61a9ec4d346e352862ddbdb627f56b5bab132a31348359f62652824","other":"914839ec95b100922429a3616522daffc51c1a4c39b297d1d8cec591567b4ccc","up":true},{"one":"8d0984a32d4b044065588b4bbe48531a30881bb869399c81b90b349e57f07ffc","other":"7b889ec7b0674ca8f6508a35d920149f1826d410b796e52bfa72306b1fafbae4","up":true},{"one":"b17b568780b3a029d4292a742890d41be6a71031b15e6276792a77a9746f0699","other":"7b889ec7b0674ca8f6508a35d920149f1826d410b796e52bfa72306b1fafbae4","up":true},{"one":"de95f5a58a8880b9e98f71c687afa472882244c012439719b6b066930355c993","other":"dab5098cf929579a87e26d30f9a8b001ad3158afe0f6231c50e58f33c6d62207","up":true},{"one":"24a154986fbc308c5f2e3fba99f3263743b669ad88ffdd2252cce5fe0c02c279","other":"7b889ec7b0674ca8f6508a35d920149f1826d410b796e52bfa72306b1fafbae4","up":true},{"one":"bfb02423df06888f87e8fbde52c2373020896f2a3dea69ecd2efa89c1aa298ac","other":"914839ec95b100922429a3616522daffc51c1a4c39b297d1d8cec591567b4ccc","up":true},{"one":"c71983aed3779037757e433a4f916be40f5199e89861a3998ccdd7e5e76f69b4","other":"dab5098cf929579a87e26d30f9a8b001ad3158afe0f6231c50e58f33c6d62207","up":true},{"one":"7514001adcdeb5c9a0e88fb54082e96a5c162430dfc56b6da858607880366ff5","other":"484875287058e78ed92396e00f34e75f907a1f42d5597b9e8c30afcbe50c1810","up":true},{"one":"5ec7e6ffb44f67eccc35adbf0923755cd5b6a8abcdd371db9b1be555780ca5a1","other":"484875287058e78ed92396e00f34e75f907a1f42d5597b9e8c30afcbe50c1810","up":true},{"one":"deeef9d38d9afb206ee7e6447e80befaf387c2af476a7e0c60c55c73dd36450d","other":"7b889ec7b0674ca8f6508a35d920149f1826d410b796e52bfa72306b1fafbae4","up":true},{"one":"dab5098cf929579a87e26d30f9a8b001ad3158afe0f6231c50e58f33c6d62207","other":"e7ac9c3a8ca80703b3499a376d57e2a9523b3bd3c5cf35b7a450f0af44680686","up":true},{"one":"a0ca706ab2ba950ff9bb92123c66c95132a63b9c93975b9b6e0d485937dd8f65","other":"7b889ec7b0674ca8f6508a35d920149f1826d410b796e52bfa72306b1fafbae4","up":true},{"one":"d514861e573828617604db5b88f407b152409308657454f99fc2820b44a45088","other":"7b889ec7b0674ca8f6508a35d920149f1826d410b796e52bfa72306b1fafbae4","up":true},{"one":"59a3ef9ba5d0a0137eab9442657cbaaae5bfcd6e77f9678b1b2616d44e442caf","other":"484875287058e78ed92396e00f34e75f907a1f42d5597b9e8c30afcbe50c1810","up":true},{"one":"a82935d4ce212895fad97829cc8e04bd1ae1c53323af4e6d8e2c673b7dfaabb9","other":"8d0984a32d4b044065588b4bbe48531a30881bb869399c81b90b349e57f07ffc","up":true},{"one":"d5355c5eced7ca0b210934d2e4f11ae09bec1c62246548ea092dc29e9987171f","other":"c71983aed3779037757e433a4f916be40f5199e89861a3998ccdd7e5e76f69b4","up":true},{"one":"e7ac9c3a8ca80703b3499a376d57e2a9523b3bd3c5cf35b7a450f0af44680686","other":"de95f5a58a8880b9e98f71c687afa472882244c012439719b6b066930355c993","up":true},{"one":"d19a0c646122e8d0b8148da1f64b3cb781fe974687a071e5a8522e9a22bc6646","other":"d668b8cc215cdbc92be719a33623494090ae0fb1a80f53bde0315b1a8fb3d139","up":true},{"one":"a1b5ab3013c0031a169e72847a4509e83b994086fdd82b07fb6ca0d7cfe9d537","other":"8e5f934e6d0f0bf34425c3db2db405d238f6512cf132926ebd0b596fc721fbe4","up":true},{"one":"d668b8cc215cdbc92be719a33623494090ae0fb1a80f53bde0315b1a8fb3d139","other":"dab5098cf929579a87e26d30f9a8b001ad3158afe0f6231c50e58f33c6d62207","up":true},{"one":"930de56c1ea44a1b39f2f99f6780ce284042d59238adb1047167c6329be4a291","other":"7b889ec7b0674ca8f6508a35d920149f1826d410b796e52bfa72306b1fafbae4","up":true},{"one":"d514861e573828617604db5b88f407b152409308657454f99fc2820b44a45088","other":"c71983aed3779037757e433a4f916be40f5199e89861a3998ccdd7e5e76f69b4","up":true},{"one":"b412abec3dd5145d9236498056db35e50e0e90697016c757724c15a2e9b187af","other":"914839ec95b100922429a3616522daffc51c1a4c39b297d1d8cec591567b4ccc","up":true},{"one":"b17b568780b3a029d4292a742890d41be6a71031b15e6276792a77a9746f0699","other":"e7ac9c3a8ca80703b3499a376d57e2a9523b3bd3c5cf35b7a450f0af44680686","up":true},{"one":"8d0984a32d4b044065588b4bbe48531a30881bb869399c81b90b349e57f07ffc","other":"e7ac9c3a8ca80703b3499a376d57e2a9523b3bd3c5cf35b7a450f0af44680686","up":true},{"one":"9756cdaac61a9ec4d346e352862ddbdb627f56b5bab132a31348359f62652824","other":"7b889ec7b0674ca8f6508a35d920149f1826d410b796e52bfa72306b1fafbae4","up":true},{"one":"de95f5a58a8880b9e98f71c687afa472882244c012439719b6b066930355c993","other":"d19a0c646122e8d0b8148da1f64b3cb781fe974687a071e5a8522e9a22bc6646","up":true},{"one":"d81271c644a75f0bf9d785319aded8b7a0e9323f85e81e94ef951e8c66251e75","other":"391e49ed641480c2c40febdb23263264c83b2b6cdf3400f6526f7939c0245c17","up":true},{"one":"c71983aed3779037757e433a4f916be40f5199e89861a3998ccdd7e5e76f69b4","other":"deeef9d38d9afb206ee7e6447e80befaf387c2af476a7e0c60c55c73dd36450d","up":true},{"one":"bfb02423df06888f87e8fbde52c2373020896f2a3dea69ecd2efa89c1aa298ac","other":"a82935d4ce212895fad97829cc8e04bd1ae1c53323af4e6d8e2c673b7dfaabb9","up":true},{"one":"7514001adcdeb5c9a0e88fb54082e96a5c162430dfc56b6da858607880366ff5","other":"391e49ed641480c2c40febdb23263264c83b2b6cdf3400f6526f7939c0245c17","up":true},{"one":"484875287058e78ed92396e00f34e75f907a1f42d5597b9e8c30afcbe50c1810","other":"391e49ed641480c2c40febdb23263264c83b2b6cdf3400f6526f7939c0245c17","up":true},{"one":"5ec7e6ffb44f67eccc35adbf0923755cd5b6a8abcdd371db9b1be555780ca5a1","other":"391e49ed641480c2c40febdb23263264c83b2b6cdf3400f6526f7939c0245c17","up":true},{"one":"d81271c644a75f0bf9d785319aded8b7a0e9323f85e81e94ef951e8c66251e75","other":"e7ac9c3a8ca80703b3499a376d57e2a9523b3bd3c5cf35b7a450f0af44680686","up":true},{"one":"a0ca706ab2ba950ff9bb92123c66c95132a63b9c93975b9b6e0d485937dd8f65","other":"914839ec95b100922429a3616522daffc51c1a4c39b297d1d8cec591567b4ccc","up":true},{"one":"deeef9d38d9afb206ee7e6447e80befaf387c2af476a7e0c60c55c73dd36450d","other":"e7ac9c3a8ca80703b3499a376d57e2a9523b3bd3c5cf35b7a450f0af44680686","up":true},{"one":"914839ec95b100922429a3616522daffc51c1a4c39b297d1d8cec591567b4ccc","other":"e7ac9c3a8ca80703b3499a376d57e2a9523b3bd3c5cf35b7a450f0af44680686","up":true},{"one":"8f7dc84801ba8da80761fe1e0d2fe72a48e45fa753cc8bbe930bf9277a219432","other":"b412abec3dd5145d9236498056db35e50e0e90697016c757724c15a2e9b187af","up":true},{"one":"a4e6f50a17347a8475d6b704484b06869d9625993761acbde6a0ae5ac0b40762","other":"914839ec95b100922429a3616522daffc51c1a4c39b297d1d8cec591567b4ccc","up":true},{"one":"914839ec95b100922429a3616522daffc51c1a4c39b297d1d8cec591567b4ccc","other":"8d0984a32d4b044065588b4bbe48531a30881bb869399c81b90b349e57f07ffc","up":true},{"one":"d5355c5eced7ca0b210934d2e4f11ae09bec1c62246548ea092dc29e9987171f","other":"de95f5a58a8880b9e98f71c687afa472882244c012439719b6b066930355c993","up":true},{"one":"e7ac9c3a8ca80703b3499a376d57e2a9523b3bd3c5cf35b7a450f0af44680686","other":"c71983aed3779037757e433a4f916be40f5199e89861a3998ccdd7e5e76f69b4","up":true},{"one":"a1b5ab3013c0031a169e72847a4509e83b994086fdd82b07fb6ca0d7cfe9d537","other":"bfb02423df06888f87e8fbde52c2373020896f2a3dea69ecd2efa89c1aa298ac","up":true},{"one":"d19a0c646122e8d0b8148da1f64b3cb781fe974687a071e5a8522e9a22bc6646","other":"7b889ec7b0674ca8f6508a35d920149f1826d410b796e52bfa72306b1fafbae4","up":true},{"one":"d514861e573828617604db5b88f407b152409308657454f99fc2820b44a45088","other":"dab5098cf929579a87e26d30f9a8b001ad3158afe0f6231c50e58f33c6d62207","up":true},{"one":"930de56c1ea44a1b39f2f99f6780ce284042d59238adb1047167c6329be4a291","other":"bfb02423df06888f87e8fbde52c2373020896f2a3dea69ecd2efa89c1aa298ac","up":true},{"one":"b412abec3dd5145d9236498056db35e50e0e90697016c757724c15a2e9b187af","other":"a1b5ab3013c0031a169e72847a4509e83b994086fdd82b07fb6ca0d7cfe9d537","up":true},{"one":"b17b568780b3a029d4292a742890d41be6a71031b15e6276792a77a9746f0699","other":"a1b5ab3013c0031a169e72847a4509e83b994086fdd82b07fb6ca0d7cfe9d537","up":true},{"one":"9756cdaac61a9ec4d346e352862ddbdb627f56b5bab132a31348359f62652824","other":"a82935d4ce212895fad97829cc8e04bd1ae1c53323af4e6d8e2c673b7dfaabb9","up":true},{"one":"c71983aed3779037757e433a4f916be40f5199e89861a3998ccdd7e5e76f69b4","other":"d19a0c646122e8d0b8148da1f64b3cb781fe974687a071e5a8522e9a22bc6646","up":true},{"one":"a4e6f50a17347a8475d6b704484b06869d9625993761acbde6a0ae5ac0b40762","other":"b412abec3dd5145d9236498056db35e50e0e90697016c757724c15a2e9b187af","up":true},{"one":"d81271c644a75f0bf9d785319aded8b7a0e9323f85e81e94ef951e8c66251e75","other":"d19a0c646122e8d0b8148da1f64b3cb781fe974687a071e5a8522e9a22bc6646","up":true},{"one":"deeef9d38d9afb206ee7e6447e80befaf387c2af476a7e0c60c55c73dd36450d","other":"d19a0c646122e8d0b8148da1f64b3cb781fe974687a071e5a8522e9a22bc6646","up":true},{"one":"a0ca706ab2ba950ff9bb92123c66c95132a63b9c93975b9b6e0d485937dd8f65","other":"b17b568780b3a029d4292a742890d41be6a71031b15e6276792a77a9746f0699","up":true}]} \ No newline at end of file diff --git a/swarm/network/stream/testing/snapshot_4.json b/swarm/network/stream/testing/snapshot_4.json deleted file mode 100644 index a8b61740749a..000000000000 --- a/swarm/network/stream/testing/snapshot_4.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes":[{"node":{"config":{"id":"73d6ad4a75069dced660fa4cb98143ee5573df7cb15d9a295acf1655e9683384","private_key":"e567b7d9c554e5102cdc99b6523bace02dbb8951415c8816d82ba2d2e97fa23b","name":"node01","services":["bzz","pss"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"6e8da86abb894ab35044c8c455147225df96cab498da067a118f1fb9a417f9e3","private_key":"c7526db70acd02f36d3b201ef3e1d85e38c52bee6931453213dbc5edec4d0976","name":"node02","services":["bzz","pss"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"8a1eb78ff13df318e7f8116dffee98cd7d9905650fa53f16766b754a63f387ac","private_key":"61b5728f59bc43080c3b8eb0458fb30d7723e2747355b6dc980f35f3ed431199","name":"node03","services":["bzz","pss"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"d7768334f79d626adb433f44b703a818555e3331056036ef3f8d1282586bf044","private_key":"075b07c29ceac4ffa2a114afd67b21dfc438126bc169bf7c154be6d81d86ed38","name":"node04","services":["bzz","pss"],"enable_msg_events":false,"port":0},"up":true}}],"conns":[{"one":"6e8da86abb894ab35044c8c455147225df96cab498da067a118f1fb9a417f9e3","other":"8a1eb78ff13df318e7f8116dffee98cd7d9905650fa53f16766b754a63f387ac","up":true},{"one":"73d6ad4a75069dced660fa4cb98143ee5573df7cb15d9a295acf1655e9683384","other":"6e8da86abb894ab35044c8c455147225df96cab498da067a118f1fb9a417f9e3","up":true},{"one":"8a1eb78ff13df318e7f8116dffee98cd7d9905650fa53f16766b754a63f387ac","other":"d7768334f79d626adb433f44b703a818555e3331056036ef3f8d1282586bf044","up":true}]} diff --git a/swarm/network/stream/testing/snapshot_64.json b/swarm/network/stream/testing/snapshot_64.json deleted file mode 100644 index 8785c9e087db..000000000000 --- a/swarm/network/stream/testing/snapshot_64.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes":[{"node":{"config":{"id":"de9eff7b7b80323f544746b9596e7da6b43b91e6c6d0660ad38df1e877ab8f99","private_key":"0736b1ccd6a1d7c513e312d29a8afa6e1231954e17a66886d0bbcf0dc191b170","name":"node_a48a667469a1344cb70f269b21fb3ca9e83823226749fd9c232be1b86ab9427888f2ecb20ddfa56da361463a061e192570f2f8da3fa967c473320ca85e382c53","services":["streamer"],"enable_msg_events":true,"port":62976},"up":true}},{"node":{"config":{"id":"66230e0bec41cfcf3667d7ddc7312888ae4887e8430c57dedafcdbadb6c12364","private_key":"edcc0061564101058d0caf2a7dd7cd178a2fd7f5de93263f1b2abac25ce28360","name":"node_8f6fb4a5baf715e001b060ce72378829279ce06a3247bd4a6faa3c725ee1b949570373e4231de82981020563e704297e77172506101b1444fcacfcc5f211827e","services":["streamer"],"enable_msg_events":true,"port":62977},"up":true}},{"node":{"config":{"id":"d10e58907f84eef0dfcfd3c81457b60eef9b1ea5b9a462e22f0c4e643a6e7140","private_key":"911635b150029a2a56d4a5b647162774df1ff5e6946c727ba21e3e8cad2c41ce","name":"node_e7b1563d6e089e030983b075b590abb8a3205e9bce953f562b755ba150a3edc2dc3377259d96c05cc7363a23aed613711ca049e26253fd9273ac4c0483087015","services":["streamer"],"enable_msg_events":true,"port":62978},"up":true}},{"node":{"config":{"id":"be6906254ba2d3748ff8ce2ebbabaa03bad1c92d2eadc1ab6470548cb8d7c524","private_key":"4bd5083735663222c8e8948125c8f606952ef887a4a07888d39d8aa6af0568e8","name":"node_d0718ce0dafea66ee16c79626e020355273cdd062c0fceb122e391c4ca6e4e408a1b0e8230c4ade5bc95803916bdd6d98c99d2b6ea70c32e17fa1f2c98741a90","services":["streamer"],"enable_msg_events":true,"port":62979},"up":true}},{"node":{"config":{"id":"dbc536e11d371786bf8c61eec755d2cb22242c27e8edd7985dae612a9d2cac05","private_key":"8cb2a950934e687c8aea0a72114b26989bccfcdf508f584a549d05091aef2c3f","name":"node_af3f9dc00ce3cc156000f16a06c55c123ccdfabed89c9aee4478ff439b6f52a89d0501cff626d36b7f1852737955f7a036b25cf259580e8f46a3e0e44340b539","services":["streamer"],"enable_msg_events":true,"port":62980},"up":true}},{"node":{"config":{"id":"3ff43d13b6ee84b128db4215c47c73909f6616039b87ca70abdd2108e29dddfb","private_key":"3c147cdf4e85087a9c0e37ff5d96de7d977ce3d3711090f64d2c3bea39790167","name":"node_95c3ca2362de2eb0ac8efdd243918ed216f5d124bc6f5b9d83a6ee1aaf8a0fbdbd70ca1710d1ed825d6871f13a15954725f1aa1b27b48307844c0bc27baec0db","services":["streamer"],"enable_msg_events":true,"port":62981},"up":true}},{"node":{"config":{"id":"e5eea2a0ac0175a0ad41239d8e36e537ce7d428d6b44e23e8e33ff0be11bac72","private_key":"fb505af88aa3fc8134b3ad647d78f182e0aee007261c066024d9fdda0bd9d297","name":"node_c73f8fe9f54b89634cb0e8e332407a3a90cbbd347c0abc93823db13a7a39c46e7bd30d3f557316d92dd731cfc686afc308448682ff06c77246e3fb02b9a025ba","services":["streamer"],"enable_msg_events":true,"port":62982},"up":true}},{"node":{"config":{"id":"5c3ca7f91551934bbfa4020433d7b19e40d04d9291c2b26686ef85225f872bbf","private_key":"833d044f5a00d0018f8b2a7d4d825831f351dbe3f3e7074d96103c3597bb0a37","name":"node_9529ff2b0692ad2608b55ec8ec0d1c9ccb8ee300412d99d389009162a76f7eec28d1090cbfb571aa8396b365074770b17106bfa3354857878dff164f42686142","services":["streamer"],"enable_msg_events":true,"port":62983},"up":true}},{"node":{"config":{"id":"1abcab4e898966c42c11d1a92b1c1911fc779089c5a20ea1ddf005f76632b171","private_key":"f08b159c68739d221adc0060225f8feb89dc3aa1f016cef59508d2787a4232a9","name":"node_5b456e429f7adae30366206e681ba46439a3372fec3806f666425cb3f12983c0b427e089fb163b70e6a56220b1529ee392ff701bc2f4621d51e23aa6305150e8","services":["streamer"],"enable_msg_events":true,"port":62984},"up":true}},{"node":{"config":{"id":"df9ad5c2cd2375a5a227841841a47876765f6552a8b2cb9fa8cdedaeca2371ed","private_key":"bbadcfc9e554aabf08f9cdea8f1174f4f47b85c5a615efa6f0d5a4db7e6e9f67","name":"node_5e92b025cc087c8823423c287f274a32a3dc0065afdf189475ba96b0da7b785cd493b7f2a7f3bb8195c73025bfbb402cbb4e0f6ae0d5e0caab223193cf6f2e5c","services":["streamer"],"enable_msg_events":true,"port":62985},"up":true}},{"node":{"config":{"id":"d8d872ef5b1632eabca51f1d2755fa5a3f16dc487d4d8be960930ee4462a2633","private_key":"91980cc1a6aa75dabe27a4bed1a4a2299ed286387f066d12c1a05ab514245966","name":"node_f6e4b1dc1fa95ceae2b932b178afdc2dd515832a191e0f49c916d5a04548d58ef9f8c1dda3fbc93a45e6d82517e6a573310b7bfb14bc38c57000aec9ff010391","services":["streamer"],"enable_msg_events":true,"port":62986},"up":true}},{"node":{"config":{"id":"e3c20117761e1b3c3d7c3d133e1bf68e594ee25950cd4311a8d964923c4164da","private_key":"9f2513f57cafcbb35e549fae622387037b8da11772eef7bbb45d44f81712155b","name":"node_707a9ae4bc8b37fff43dc3ba83ecb735bd428b720a751a11112452eec1ca49c09139bc437666add2823dd205e45cc793f8657c66ccbc6911cc60771d4512e038","services":["streamer"],"enable_msg_events":true,"port":62987},"up":true}},{"node":{"config":{"id":"544ad921e03de7d1d656038e23452dd3a9c7ef041520f17f8318326ecc58683b","private_key":"a198f5ac31e6bef23d8f266075f8363ce66698d0f8b1193072f813b2a1717236","name":"node_78216c6660fff0b96e38084941a2f237996732118033bf842e5f9e533b22b36c2da996df28e866424311b7b6414d4627db1f22a235ae777f7de34d9696043ecb","services":["streamer"],"enable_msg_events":true,"port":62988},"up":true}},{"node":{"config":{"id":"56ac3ac4cc04e94c93d986a0ed9da8b68d97f13c8b5ec35d56703592fc05bc43","private_key":"9f8eddf3160bc4b13285e9587f6736c82305632b55fe833a12f1f821c0f311e4","name":"node_d472383c453e6b0f1a632010bdd6c42a9668d328a8f8071e0407069f6fe413e2a0fd0551a09cc56ee5245d472e39a7b6295491dd256ae42e200a257f297743e3","services":["streamer"],"enable_msg_events":true,"port":62989},"up":true}},{"node":{"config":{"id":"f0ee0274e24a7cc3d1b0d38cd46d205ccbf791d0cc038d022f2a0c50ae6a5cd5","private_key":"601ed94a775c7129de2ddd82e23249e13176478817e5376a1c18389de0016283","name":"node_351ceaf2e95e098a8e15e81c82073b523f641848326d1bbd379f40f886b50ce2c377c4e3a2dfc2662d6f25ac04dede41063a075cf00be1cf36f95b0267717ab8","services":["streamer"],"enable_msg_events":true,"port":62990},"up":true}},{"node":{"config":{"id":"3c93b64143bf1bf130545806917472f3ca16ad6f223f0388f445ddc353140042","private_key":"dd64b8659254425a4553501bcc275b1dc0a6c7bc79a538f780716cd64d46a8c2","name":"node_8da9cdb3c78776ed230497b21d4ac172f0ce73e921dfddd483da7bc6bbcd1951dd78fd5f70a0acbad855b2984e4c4bdb408c4bc25cab40684fe5a4408ec7ab9c","services":["streamer"],"enable_msg_events":true,"port":62991},"up":true}},{"node":{"config":{"id":"83870b048b374ed527d4b25256900d4b53fad11d900b54e366f9e3fe7ae50079","private_key":"7216dc13943bfd0a8c2e93d621a44a2dd4baab7b68fa87be06f2e649f533f72c","name":"node_0a45a0aa4b4eee71b2828c794281d698019667288cf51311ffae7d8ea895f8ae29f7958dbfcda8763d9d8e9c097dccc5f92ac9d09fb4ccbde178d7f5ebda4d35","services":["streamer"],"enable_msg_events":true,"port":62992},"up":true}},{"node":{"config":{"id":"72f779e91460990aee45e99b0565246a2620d4939cc3ab0fe021fefe71aae761","private_key":"01a2779483d6e089da8b3943f81cff48c8bfd1bd7d0281bceb152e41a7f5acb7","name":"node_f70c92be150cb3f65f6bd904c8c161bfb0cea717bab0b24da63714de83e0872c8c369960ada1629607a1e166a19b6cfa887603e4868e7912ae5512744e8cc1c0","services":["streamer"],"enable_msg_events":true,"port":62993},"up":true}},{"node":{"config":{"id":"f03f47bf8f6241cbda878f301783e30cac333e8cea3d6bf9bc2be3b7fc2cf1c3","private_key":"2954d35fce94938f074043b9f9dd87157a6730bdd8e3a1b6f19b8a55ad1505df","name":"node_d563716fbe2068cc940f7b12dc2bacb345c95f01e78e3b1f0105a441f300fdb00cc72e677aec102360fb904b63055d8581555f5b74b8f2392706569d8357c53f","services":["streamer"],"enable_msg_events":true,"port":62994},"up":true}},{"node":{"config":{"id":"b4bee8054b3b7bc3ef0748937602699173983a764814e85f173916fd70f1e3ed","private_key":"6f87c529d5b17354bd3ba6b59d6ff3a0f71550a41d949d702e27f50957cfc3ba","name":"node_46bb2acc087482d40ab2ccbb387723158411ff4931423414e4d09891be8a31d11929e939e4be94b0cc355c49883e1835785ba1955d865f78748c2a0a2f6eee0a","services":["streamer"],"enable_msg_events":true,"port":62995},"up":true}},{"node":{"config":{"id":"c678643c5249eb90385aad201d6756f9f364024938d4c9f59f4aa51d64d48bd7","private_key":"ea386dc625e681a5d5d7c4e8ede4b3ffb8f2f13061c65acdde2750e2a19ef417","name":"node_8da75cb4e63d968a7999ca4b49572b5ae592a91fa442d44bb7c42fb4b174a365e1affa1dbc45b2a0406444505f178100768e7783831f2886d53e7d1b8a3a7291","services":["streamer"],"enable_msg_events":true,"port":62996},"up":true}},{"node":{"config":{"id":"bfa0a02816da29b40215fb3c26de9360505e4c69fca55086324f0e35de7486a5","private_key":"e9b9261c569da5418d3aeabf28dff1faada23ae1a3a1552035aefe56bdf3136d","name":"node_6d1af44ab259cc080a14c07bf5efabfd70cb847fe338f016c00138c84bddf6a6ce94047d1bffcfc7e9bc772dd7a080468a84655d5351d0056f97f13358b91547","services":["streamer"],"enable_msg_events":true,"port":62997},"up":true}},{"node":{"config":{"id":"c6c479f1c83f5158eb77d57e6321d5ed062964870775f51254461010c8882c10","private_key":"19334395c71250dd3eeeef4eb4032e2769839d57cd6080f2f51e1de3630789fe","name":"node_0dcef49b94fdcff024d706030fd4f513d0c6ad5f00b06a8b67abc454bbe5c2c74e939d491c0700e47ecee5cd7c7084603f62e626c34b454868b8f22d8c2dfb10","services":["streamer"],"enable_msg_events":true,"port":62998},"up":true}},{"node":{"config":{"id":"2d831f78c7d670e4e1f19a47c80ec2df144da210ff3aaa00ca6c4990e4da0922","private_key":"d587a8503da404e7a74e24d731daf63b9585ae41b555035979da013ab723b4b8","name":"node_e7d4478b412ef497741943736946a3b0679d21ae0c4cbdde6809c7236dcf1a40291088d872e469f06da19ed4ef77b9844aee7066c40fcd45b0931aff847de1d7","services":["streamer"],"enable_msg_events":true,"port":62999},"up":true}},{"node":{"config":{"id":"fbbe14109dc4fa8251e852a181f3663b4245c93b737fa9c4921a53ed9949019d","private_key":"724511e7e57416a6b4627079b0b9079fb4fd5649dd018d7f3a64dbf6a1dd9adc","name":"node_7722d7d06c66afb0642d5f9ae0b22ed812c632f2d62ce64315da40eb003feb3fae1b337c10df21d297beabe51b3879f2c0a725f08ee78a8e09c75f86bd0e2092","services":["streamer"],"enable_msg_events":true,"port":63000},"up":true}},{"node":{"config":{"id":"30d80772ad22d78f8a4e948a06677b5676aadb8a9c040f3ffb59a7e608e0929e","private_key":"922f3ca85ccc577429654b566f1561a301bbad054e4f3a8f312db841329899c9","name":"node_e479930845e1dac2d5908894548cc06c132fc5820810c6f46d9adc2bc252dd40b64642a3b2005ad3688d2637c43967f570bd153db9ee6742e1b23dbfb72537fa","services":["streamer"],"enable_msg_events":true,"port":63001},"up":true}},{"node":{"config":{"id":"634c2562544e0979530cc4dba918c849f24f85c87813e592c54e1f6d8d73bb6f","private_key":"c94a8e8e535d7431b0610ebfaa70d76c37bf6fbcaaa83ec4be0bed5527780670","name":"node_61b32cded1b5ea3e2ca545a55c84251daaaacf51e6fed98fbdf4c5b6b91a554081e1e577aaaaaa4f911780f92703c1d715695d86c1be641b0bd1794f4107c2be","services":["streamer"],"enable_msg_events":true,"port":63002},"up":true}},{"node":{"config":{"id":"7a1f03dc26ef21a5bd1673eb8418af7a7cbcb2ab1654882ebdcda2d1e6ad4836","private_key":"7a1378d2cad0ac18e37694b3ec5c08580aa1887916e98030c66accf6197f615b","name":"node_9e51eb48f59886e70eb8d02e1c0c4423de3f0ce79c7a62bfd91bc2048cf643f40a91e2d2a1a8a19a9e4ba4a96aed0965aefcd941c068208abe8bf5029eb27d55","services":["streamer"],"enable_msg_events":true,"port":63003},"up":true}},{"node":{"config":{"id":"36c28c0237be6b2131280452c3113affcae28f6c7066811a8e1ee1a0a01decc3","private_key":"6e834fb9046bc38179a4c8be1cd4c2c540df1f73aa208e9794701234202211ca","name":"node_7a1f97c41c9efd7a2903af78f72d02cfff3631dc6260d3ceb2364c2a8e06965710e8feae6aa9f99b48b2475da10b2f9581c76d47b4cd2bfee09c43dcb5e3e5d9","services":["streamer"],"enable_msg_events":true,"port":63004},"up":true}},{"node":{"config":{"id":"6c4bdfe4650525d5efc1a4ca8d92ebbea33b1877bfac69656e81adbbad4f286a","private_key":"819e80fcd487bf15502fff3d31651efcd9b5e7d5e18ca4d836ce53736bf23d11","name":"node_7a45fa46f161fae3ef9013e9ab81145f4a76fa6ae22160424d6dc111acbd477ce43d4c10db89cb7451c97ea1fbd78428997d01fd17e1851f8b559b404ede6da7","services":["streamer"],"enable_msg_events":true,"port":63005},"up":true}},{"node":{"config":{"id":"1087495bf04e6967257f84b8e5de084e2000b9d748216e31c9f33fc9caedb715","private_key":"c2fff99bd9d70ab2149a1ace8ad0dc26b4b78cbfd60fe9215e0f69885e0cede6","name":"node_07138ce506125910af81bd1bae11c22bfab9a81e1e057cc3d84769475c5da9873eb3f11edc6aca83ef486bfb63eba15a6233676d29d93ac9e774f10cd42c8cbd","services":["streamer"],"enable_msg_events":true,"port":63006},"up":true}},{"node":{"config":{"id":"23e4f18d302b6a545530b89eca007c141c751cdcde471bc3378bf72fd68b91c6","private_key":"5d8862df181e186646d6fa8accaceed89b18a384ce5e63de34ecd1bd80d7f0c9","name":"node_f360c6599a0911337c7b96365fda1b2f64890bdb4483bb7e86bca15484f24c4efc0d1f6f56aa0c4fa5c205e6c1dbbf20d2ca3c2f8734f703fe6d672fa76a0d45","services":["streamer"],"enable_msg_events":true,"port":63007},"up":true}},{"node":{"config":{"id":"43900b2a084097a0ddf8bf4d0375624f731bd14c85300007c87269986a12eb3b","private_key":"411b4485e7f793c1db7387d9e8f179d02d939de2702166248953f8f0813cb5f0","name":"node_f5272b71c228eb14b9e0e221deefc0a91353a5f17d01475f57fad54bb8e50e375f58019c7200a9ca45b0f1da6538708c61242e478d917499871df7a9d5857f1a","services":["streamer"],"enable_msg_events":true,"port":63008},"up":true}},{"node":{"config":{"id":"0d48477e33d7cfc4a7660048e4b1ece9f37e9759e96e6f1f47a86de091b0ee97","private_key":"9b1297e565c13eeb81e2b19c31f635253081d34a65eb1ebf889f30635e4eccea","name":"node_e53cfad611bc38a1090d28e4cc58e4738ccaa83a00bf5447cd30ec70e492dea5811b67e79b9ca8856e79801741a675c6f54eb7a1c39e95ab735164855eae0fb7","services":["streamer"],"enable_msg_events":true,"port":63009},"up":true}},{"node":{"config":{"id":"77687d9e478a52555674f12aa654ad931a3ee83920b9cdc8fecc03653ed7e386","private_key":"eb698ae5696bf1cef4f4e6cb8ef2eb5e79cd298fcb55e0449bbaab6d048e09b6","name":"node_4f1a219f31e135208bf4caa314a14d8d48a5682941793431539efb5666b06bb5e3e9cb29b019ca414fb32961460556f2d99a04b12dc8ecbf05f43efe108803f5","services":["streamer"],"enable_msg_events":true,"port":63010},"up":true}},{"node":{"config":{"id":"61c24980b9ef4ee7fc33c51310f981a8e74e4276509a545a3f471370082dab4b","private_key":"cc474d341e32e674a1c1d607789df8bb6097ab392eace2d366da2496c5413cef","name":"node_25c64a52c298035be2184243082b5eba8a3a5d55c5334cea76e177d722945c8bf014a4ce9ef2ed76dc17a44234c97697337c6e001261533e72cc8266ee0e2fe4","services":["streamer"],"enable_msg_events":true,"port":63011},"up":true}},{"node":{"config":{"id":"29077dd2f4869b69ffc3ed40e82e4ef051292c3461888b5faf7b4c4b14d4928f","private_key":"4ec4a7d75323558dd822f09ab9f992afd016604bc77aec05f89cfdae6f50611b","name":"node_1691538e4cc41441c18fc92969c0c59f51da063814e8c45311605405284293503ee1570722cc7ae12f9cd63b899afaf5b3e66ca012115996074e8aaa71fda48c","services":["streamer"],"enable_msg_events":true,"port":63012},"up":true}},{"node":{"config":{"id":"2f4492429ab2ecd080a36451153990587de2458ebd9421fb989d8e27d4a88422","private_key":"b049d01349524a669db50fd817ef70811ea5cb5517adaa016f4ae426184188e8","name":"node_8e1a66c9029330c25b9abe95a11878f41cef1a32b567a931ffa781c2f70a1336efa03ba1a66dac0080304803c1239be443991a251f24d0e9c2e26aebeb5437f8","services":["streamer"],"enable_msg_events":true,"port":63013},"up":true}},{"node":{"config":{"id":"1be4ba2f068e060a0aabac445094f1dc8b22fe1e5575c98df8d8783aa3d95177","private_key":"9eb7b1e41bd77e46a9746723ab4367fcdb9950d5f99c32bb1efddc9b5c60daf7","name":"node_c8e9cc707ce2a278371d1b4f64714cdae425b7c3606d18af6588dacaca35c7a171a07629247af351e667d2ddd9e71598f862125c827cc869c463cf87e4daeed0","services":["streamer"],"enable_msg_events":true,"port":63014},"up":true}},{"node":{"config":{"id":"f066d5d314253a872ab27df90d85ba5824da06032f92ce8bf20020b61971e199","private_key":"229b6c150e858a0e81d1c720a9697bda4c9f26e531d85db8837ca5d1a24ff8dd","name":"node_43b91c24dd7fd217a76f00ab97920e4223bd2ee085abe7ac34e915e8676400e1643d3bbb4a864c8a6df1298bf229cabd1bdc9879eafdcbcab064708e8266e88d","services":["streamer"],"enable_msg_events":true,"port":63015},"up":true}},{"node":{"config":{"id":"332f7b60a1fbb6cd1e251dc68d1093b7f0bb05c1eefce5e61f124dff9445f14d","private_key":"ab42d800bd5b0bcaedd3009d4805e827ca6c41f54fb7fd38cc021f9c72bf3aed","name":"node_14136fa967f2d4825845ba4a43a5921d8fcf03f9c71c5595037e000b2ef6ff0a5eaad0980aea0a170cafda3529f982aea1107f58aa42485550c9480e86f5e9f3","services":["streamer"],"enable_msg_events":true,"port":63016},"up":true}},{"node":{"config":{"id":"a070c42951b523531f065cb8376eb8ca0c431b473ce9ebc44a3fc383f1f878b3","private_key":"15e0330ce9f82833faa08bbacae92d3eff516eeb4bfd1be0b7d8ecc8a311d8a8","name":"node_39d0aa13a6d7e709c9b914cb16f007c3f16af40532afa47c8cf9abf6d604d4380883103c995ec696ca593c4dc9bf0290979b965c9c6ade7c616d3973c5f0b1f0","services":["streamer"],"enable_msg_events":true,"port":63017},"up":true}},{"node":{"config":{"id":"9ecf217e2076c229f6d15bfe29df85aa129f1fb12d2a8307907327b5fe2f80c5","private_key":"a2ffbdb8b486d91c6d2b579b5ae3f1a8f09feaf0451b375d8985f0ddea952926","name":"node_e73af1e2a834ee132564dd2e983c877b3ae230808d2b42da2ec91472eee6ee6595c439800ce980186b4b0d9a56b96babf747006c07b07513543eead8e102fff7","services":["streamer"],"enable_msg_events":true,"port":63018},"up":true}},{"node":{"config":{"id":"c0f0bec8a3506280df599d57abc414d007696967381b9690bcf63a093586307c","private_key":"48bdb744f1c9b25e7b2170b88fb956619976670186b82382029c84f3ef780930","name":"node_c0f2f1409cf83d7a777f6af95931181eb5a36b14a156f444075071c896fc5b435638ead23c65a242105db527c809e2c46f7805c862c0eb5d444749ddf22a5060","services":["streamer"],"enable_msg_events":true,"port":63019},"up":true}},{"node":{"config":{"id":"14645ab9b2ebbf4436466df07ff28e68b92774c6b5a0d311a174765e350c7089","private_key":"49376794edff5cc56583fc3ce9e6987e0210c537f798bfc00cd523927052bbf2","name":"node_e021950ce89ce2bd79649aad2ed504789636b09870d0652fefa5e16631cb32a5d41c6cc4a540b7a0fca6bc3d5adf9b372a22cf714adc7b3e9b968d4c840017d4","services":["streamer"],"enable_msg_events":true,"port":63020},"up":true}},{"node":{"config":{"id":"44b1544b5795a1c77094832bae47c84eb38482b1e1aaefbaecacbc7ef9d10e3e","private_key":"b59aef84a0b7166aab7dc4edd8bfc66f56b77097cbbb4e937277ecd23c051473","name":"node_16bd57b6590df0c69c440d178da8359b0a3e00b9c5362155eae42709bdc4d0488e437fa7e8396d5b429cebe356ebd1407039b21eefa8d9164b357845132edb28","services":["streamer"],"enable_msg_events":true,"port":63021},"up":true}},{"node":{"config":{"id":"9f2d72c6e30ec363bdcf9ecf28fcf5553f98357680d8530581fcd815389005ec","private_key":"961cb451a05a1f043539e0417e0c612286d8a86116a4bd3ac4f301444bc0abc7","name":"node_393eb8d2acf9244f1cdc220711c00d8fa7619394893643bc32e1ee0f821e5a2afed07badbdf97d710843c4ea6de3f74c113ad4c548afd23534d6dea5c37c6d9d","services":["streamer"],"enable_msg_events":true,"port":63022},"up":true}},{"node":{"config":{"id":"ada8540c63062347d3162850cf2a4820a20ed96514fb01b95d995bbfff97a212","private_key":"a56a9d4ff921fba9e03ecffbd71d75f97cc9b7c5f389cf54b9bf66e689bb60b5","name":"node_62b8e67a4ca956c1cf2ba7a538d39bfe8bccf747fe65be48c88270ad44ee7711c0e9a43bb3821feffc492dba9b58397768c755810121add9c980c9a2c696feb0","services":["streamer"],"enable_msg_events":true,"port":63023},"up":true}},{"node":{"config":{"id":"be9f880ccfd8dd0f5fde587c4e9fbdcb2d3551df0c4504d9938d4301a9276e72","private_key":"f80db180b4c58031447688bd4b5f9d32cfee3dc85506153782a0368873110317","name":"node_17c7eb5833687a98087daadda564e54b1c770f946efb7efd69370a9caf6e6939e408f04a3fa1aa042d9463992a711786e504072fae56d007d696851696fc5643","services":["streamer"],"enable_msg_events":true,"port":63024},"up":true}},{"node":{"config":{"id":"ce614a2fd326ce57b0ead5750d0a08f3c7dd94e1d2935861697e3df4652a22b6","private_key":"5750e8b20da0147e055f965d6ba4ee434812f658663383ebfe7e88f13fdf59ab","name":"node_f5efffc8e894cecc065f6892ad2a846c8fc4982d1c8c7fc729421d5108d519c501cc9f2f7a0046ec08d47cf2369753aaf287dfc3ef7ecd34d24d294efb933658","services":["streamer"],"enable_msg_events":true,"port":63025},"up":true}},{"node":{"config":{"id":"358eef3cf9964bf89e056afe0f4b46ce85bcb079bab922a4ae9dfa555001abfc","private_key":"f841751579683b856a59eb75f3493a4b3e39183d8818a2638559100bb6f5e66a","name":"node_7c273dd4218e9e5b92e19d250832a424ff2ac5482ad70953746de780100669eb143247e15ccfca14cce9a49a003c82be71ca034a2900bed4c89fb10bdbfcc7c3","services":["streamer"],"enable_msg_events":true,"port":63026},"up":true}},{"node":{"config":{"id":"5cdc4493f01d6ebec3070b028275bd94ede61db21a4f5d3b282a366d1d3484b8","private_key":"be9f2207e4e7a93758129c0d6fa34877b685210cf0e8f8d233e5425d8fa461f9","name":"node_8959c5cf825ebab14efeacf2807ad9498256a9e89a2ef00690c9ee23a3e934e111c52a5ddcc791b93f49424ac008095d8cb0da6905dfdeb62309fc4f6672e59e","services":["streamer"],"enable_msg_events":true,"port":63027},"up":true}},{"node":{"config":{"id":"76933265c3d872b2d20e7a961f3f4e858afe11aee773b5caf678553e56ad3537","private_key":"4a245ec64e442d4cad2468d96914df5728633c568ed3d063990ac8932be63222","name":"node_dcf2401cbc26f3db27bb85c24984b1d755b3503fe400c139a0bedbe5de24db3f27c8f4f40adc1880b6142d20e67a40f3d45d36ba7fce122ffa44703e65622198","services":["streamer"],"enable_msg_events":true,"port":63028},"up":true}},{"node":{"config":{"id":"577b9ea47bc00cde25b11ca03861df0ccfaad8ce281500efede88d25e012b1e3","private_key":"1771927f1f06912915f5498e857816fd8f73b2bde10f88ba4a856684bcc2a6fa","name":"node_38b3adde25c6407f29c2a721f1f5c082cc94ddad09fc5afda2bbb2309212a9b54b1553731a65ec8b58769fc11d488169c8000ee15fe8966bd44a3879e0c9e534","services":["streamer"],"enable_msg_events":true,"port":63029},"up":true}},{"node":{"config":{"id":"dfe389ee7fe03c809d7a4afa1f04dde9ab9a50d8c6ca5bf2489debb2e8329541","private_key":"986bc0252067c4c3ace80099cbc843d6f16f7483719e9ecf7a3a0f876a461ee8","name":"node_6593097e880701f3d0573df7bff9ba7af40f4f83b828bfc11bdabea62fb94ed39c8233d8d40275c2af9ceddae44514e029a546fe9df73abbd3821d37933aba49","services":["streamer"],"enable_msg_events":true,"port":63030},"up":true}},{"node":{"config":{"id":"3ea2554db02021f0413529426d7a7b0ac9abe80643993f6d80fdfff93a765c40","private_key":"39848c3aa21a67751f95ffc8938b36264eeee342816d41b37816f2164e59858e","name":"node_578666c4113e196a7c738fdbae0bd68ade450283eb835efd06591798f1893f7f7d9cde0691faff670b5ae24e58cbc2f549d2196f244dddc00e82852c38e8539e","services":["streamer"],"enable_msg_events":true,"port":63031},"up":true}},{"node":{"config":{"id":"68d45eec895b91f081026e86ed164092dad8d4087918cd50c6f77767d9577f6d","private_key":"d5dee04f1435fa7ab6d65e89de8c4c6bf156111bfb9ee5de0ab617cc3be19681","name":"node_e1082b4577aa370a5171b7602282208697dcead854f0c82acb5628d179fa5990a0fc85c5343a47177aab81c7fa6c1be8a27a3484acddc353582ddeb61892ef94","services":["streamer"],"enable_msg_events":true,"port":63032},"up":true}},{"node":{"config":{"id":"cfa6cfc1f856e69430370d5c52130519502c7a9e4fb14c7c8d9b309612a3c1bb","private_key":"044a1acc0d565f7080db7b625baefe8a5f7aef9009a7bcd338fc31a02d704d8c","name":"node_a4fbd2ffc25d2c3bd37beecd637dda5983cb5817ecb6da62aec16ea59aaf7e71966649211461564ba808174361898a1d2265f1a42e94434bc00d5d3e04b67a8a","services":["streamer"],"enable_msg_events":true,"port":63033},"up":true}},{"node":{"config":{"id":"5d056a38c3a7c3bfcd431b767662903454f41429d2e03b9b30273e5b1b8e5c97","private_key":"8be74288552caed392476d39780f806f2c21b50a9990c5a3a5ed37397b218a6d","name":"node_eb08a7774ca5252237d059a8bbad68044c83b2c332024be98bdc0c58508867a80cd34cb4162bd55a47fecc93301c8b2a7a1edbfd807ebb5623499cac80ea42de","services":["streamer"],"enable_msg_events":true,"port":63034},"up":true}},{"node":{"config":{"id":"7a56b4203ee1cad781b23d2a78694f63eb7dfe6d9ac24fc181e6a63516c9efa5","private_key":"e644c54ce52ddb2788fc683264f175a24485082eb88ebe0a83c94ecce77d7175","name":"node_6eeb951b8f6d5ac519132910222777d5a409325e6c64d89708a00e55cb0fe85889aac1baa724e29fa7f064323b164a9c40c5114a65736e3dd16f0336283d9c7b","services":["streamer"],"enable_msg_events":true,"port":63035},"up":true}},{"node":{"config":{"id":"51db0d8fefeedfab3394f8f398b6d8f4540b24bc6cbd8e66bc5a7480db0228f5","private_key":"77445377572b915c2e3802f70fc9eae8d29e767f54f80a29701ae5d1fe0c8677","name":"node_6419279b600da3e2ee8d0b2a5b0319bc6cd496086955b49a8d2168fd4c35d5688291bc9313076b1b8e070f25dc593788d72b8f8c1d2ad828741a45deea35e58d","services":["streamer"],"enable_msg_events":true,"port":63036},"up":true}},{"node":{"config":{"id":"3c38aba43a15a61b680fa767353cce7e4165cbc996968f990001d7595ff243c8","private_key":"f6b20f29a1cdcad8f8bd88acc4f6ffba7b3027e46ab60784e2c2b77edb3d3630","name":"node_032b1ce6881413b7ade1918e56691c7acd9f24111f47792ca50c33e570afba433caa132ad2326d03f9c0544fdc09fa0ca65f61de9fe61d6dd92c5d25a8df69af","services":["streamer"],"enable_msg_events":true,"port":63037},"up":true}},{"node":{"config":{"id":"9dc2c4166fa9410d7be67cbb5ac572aa42a9a78c2efe5ea601ddd37c356196da","private_key":"25bb03224302b9c89585681c4bb30dc9d95fe417e5c0f13627154083cda7492b","name":"node_86d55ece15e6bb4da7601f6587654505f760ee2b87abdb115091eefa93f61cf29fd695601bb8c6ae2c76400de24b8bca9ca32b50e4b849ae76f5320b03276bfc","services":["streamer"],"enable_msg_events":true,"port":63038},"up":true}},{"node":{"config":{"id":"37debce0cbe19a2ab88f15dffa390df392938e7060970b93eae968a8c2497650","private_key":"a4eb160b838f392946a844b9fbc36006c459ff6f9ef46a832d000f17f2aa834c","name":"node_31cbd7c029d69715e59f31bccf362cd76e0a87839f1f63c6623be437d2af227a38f12c5fb9c659cc543976833caf22a52f5191c7c465a9ba36ac4235c12add72","services":["streamer"],"enable_msg_events":true,"port":63039},"up":true}}],"conns":[{"one":"de9eff7b7b80323f544746b9596e7da6b43b91e6c6d0660ad38df1e877ab8f99","other":"66230e0bec41cfcf3667d7ddc7312888ae4887e8430c57dedafcdbadb6c12364","up":true},{"one":"66230e0bec41cfcf3667d7ddc7312888ae4887e8430c57dedafcdbadb6c12364","other":"d10e58907f84eef0dfcfd3c81457b60eef9b1ea5b9a462e22f0c4e643a6e7140","up":true},{"one":"83870b048b374ed527d4b25256900d4b53fad11d900b54e366f9e3fe7ae50079","other":"72f779e91460990aee45e99b0565246a2620d4939cc3ab0fe021fefe71aae761","up":true},{"one":"d10e58907f84eef0dfcfd3c81457b60eef9b1ea5b9a462e22f0c4e643a6e7140","other":"be6906254ba2d3748ff8ce2ebbabaa03bad1c92d2eadc1ab6470548cb8d7c524","up":true},{"one":"fbbe14109dc4fa8251e852a181f3663b4245c93b737fa9c4921a53ed9949019d","other":"30d80772ad22d78f8a4e948a06677b5676aadb8a9c040f3ffb59a7e608e0929e","up":true},{"one":"23e4f18d302b6a545530b89eca007c141c751cdcde471bc3378bf72fd68b91c6","other":"43900b2a084097a0ddf8bf4d0375624f731bd14c85300007c87269986a12eb3b","up":true},{"one":"30d80772ad22d78f8a4e948a06677b5676aadb8a9c040f3ffb59a7e608e0929e","other":"634c2562544e0979530cc4dba918c849f24f85c87813e592c54e1f6d8d73bb6f","up":true},{"one":"634c2562544e0979530cc4dba918c849f24f85c87813e592c54e1f6d8d73bb6f","other":"7a1f03dc26ef21a5bd1673eb8418af7a7cbcb2ab1654882ebdcda2d1e6ad4836","up":true},{"one":"7a1f03dc26ef21a5bd1673eb8418af7a7cbcb2ab1654882ebdcda2d1e6ad4836","other":"36c28c0237be6b2131280452c3113affcae28f6c7066811a8e1ee1a0a01decc3","up":true},{"one":"36c28c0237be6b2131280452c3113affcae28f6c7066811a8e1ee1a0a01decc3","other":"6c4bdfe4650525d5efc1a4ca8d92ebbea33b1877bfac69656e81adbbad4f286a","up":true},{"one":"dbc536e11d371786bf8c61eec755d2cb22242c27e8edd7985dae612a9d2cac05","other":"3ff43d13b6ee84b128db4215c47c73909f6616039b87ca70abdd2108e29dddfb","up":true},{"one":"6c4bdfe4650525d5efc1a4ca8d92ebbea33b1877bfac69656e81adbbad4f286a","other":"1087495bf04e6967257f84b8e5de084e2000b9d748216e31c9f33fc9caedb715","up":true},{"one":"3ff43d13b6ee84b128db4215c47c73909f6616039b87ca70abdd2108e29dddfb","other":"e5eea2a0ac0175a0ad41239d8e36e537ce7d428d6b44e23e8e33ff0be11bac72","up":true},{"one":"1087495bf04e6967257f84b8e5de084e2000b9d748216e31c9f33fc9caedb715","other":"23e4f18d302b6a545530b89eca007c141c751cdcde471bc3378bf72fd68b91c6","up":true},{"one":"be6906254ba2d3748ff8ce2ebbabaa03bad1c92d2eadc1ab6470548cb8d7c524","other":"dbc536e11d371786bf8c61eec755d2cb22242c27e8edd7985dae612a9d2cac05","up":true},{"one":"72f779e91460990aee45e99b0565246a2620d4939cc3ab0fe021fefe71aae761","other":"f03f47bf8f6241cbda878f301783e30cac333e8cea3d6bf9bc2be3b7fc2cf1c3","up":true},{"one":"f03f47bf8f6241cbda878f301783e30cac333e8cea3d6bf9bc2be3b7fc2cf1c3","other":"b4bee8054b3b7bc3ef0748937602699173983a764814e85f173916fd70f1e3ed","up":true},{"one":"b4bee8054b3b7bc3ef0748937602699173983a764814e85f173916fd70f1e3ed","other":"c678643c5249eb90385aad201d6756f9f364024938d4c9f59f4aa51d64d48bd7","up":true},{"one":"c678643c5249eb90385aad201d6756f9f364024938d4c9f59f4aa51d64d48bd7","other":"bfa0a02816da29b40215fb3c26de9360505e4c69fca55086324f0e35de7486a5","up":true},{"one":"1abcab4e898966c42c11d1a92b1c1911fc779089c5a20ea1ddf005f76632b171","other":"df9ad5c2cd2375a5a227841841a47876765f6552a8b2cb9fa8cdedaeca2371ed","up":true},{"one":"e3c20117761e1b3c3d7c3d133e1bf68e594ee25950cd4311a8d964923c4164da","other":"544ad921e03de7d1d656038e23452dd3a9c7ef041520f17f8318326ecc58683b","up":true},{"one":"544ad921e03de7d1d656038e23452dd3a9c7ef041520f17f8318326ecc58683b","other":"56ac3ac4cc04e94c93d986a0ed9da8b68d97f13c8b5ec35d56703592fc05bc43","up":true},{"one":"56ac3ac4cc04e94c93d986a0ed9da8b68d97f13c8b5ec35d56703592fc05bc43","other":"f0ee0274e24a7cc3d1b0d38cd46d205ccbf791d0cc038d022f2a0c50ae6a5cd5","up":true},{"one":"f0ee0274e24a7cc3d1b0d38cd46d205ccbf791d0cc038d022f2a0c50ae6a5cd5","other":"3c93b64143bf1bf130545806917472f3ca16ad6f223f0388f445ddc353140042","up":true},{"one":"3c93b64143bf1bf130545806917472f3ca16ad6f223f0388f445ddc353140042","other":"83870b048b374ed527d4b25256900d4b53fad11d900b54e366f9e3fe7ae50079","up":true},{"one":"e5eea2a0ac0175a0ad41239d8e36e537ce7d428d6b44e23e8e33ff0be11bac72","other":"5c3ca7f91551934bbfa4020433d7b19e40d04d9291c2b26686ef85225f872bbf","up":true},{"one":"bfa0a02816da29b40215fb3c26de9360505e4c69fca55086324f0e35de7486a5","other":"c6c479f1c83f5158eb77d57e6321d5ed062964870775f51254461010c8882c10","up":true},{"one":"c6c479f1c83f5158eb77d57e6321d5ed062964870775f51254461010c8882c10","other":"2d831f78c7d670e4e1f19a47c80ec2df144da210ff3aaa00ca6c4990e4da0922","up":true},{"one":"2d831f78c7d670e4e1f19a47c80ec2df144da210ff3aaa00ca6c4990e4da0922","other":"fbbe14109dc4fa8251e852a181f3663b4245c93b737fa9c4921a53ed9949019d","up":true},{"one":"df9ad5c2cd2375a5a227841841a47876765f6552a8b2cb9fa8cdedaeca2371ed","other":"d8d872ef5b1632eabca51f1d2755fa5a3f16dc487d4d8be960930ee4462a2633","up":true},{"one":"5c3ca7f91551934bbfa4020433d7b19e40d04d9291c2b26686ef85225f872bbf","other":"1abcab4e898966c42c11d1a92b1c1911fc779089c5a20ea1ddf005f76632b171","up":true},{"one":"d8d872ef5b1632eabca51f1d2755fa5a3f16dc487d4d8be960930ee4462a2633","other":"e3c20117761e1b3c3d7c3d133e1bf68e594ee25950cd4311a8d964923c4164da","up":true},{"one":"37debce0cbe19a2ab88f15dffa390df392938e7060970b93eae968a8c2497650","other":"de9eff7b7b80323f544746b9596e7da6b43b91e6c6d0660ad38df1e877ab8f99","up":true},{"one":"43900b2a084097a0ddf8bf4d0375624f731bd14c85300007c87269986a12eb3b","other":"0d48477e33d7cfc4a7660048e4b1ece9f37e9759e96e6f1f47a86de091b0ee97","up":true},{"one":"f066d5d314253a872ab27df90d85ba5824da06032f92ce8bf20020b61971e199","other":"332f7b60a1fbb6cd1e251dc68d1093b7f0bb05c1eefce5e61f124dff9445f14d","up":true},{"one":"0d48477e33d7cfc4a7660048e4b1ece9f37e9759e96e6f1f47a86de091b0ee97","other":"77687d9e478a52555674f12aa654ad931a3ee83920b9cdc8fecc03653ed7e386","up":true},{"one":"77687d9e478a52555674f12aa654ad931a3ee83920b9cdc8fecc03653ed7e386","other":"61c24980b9ef4ee7fc33c51310f981a8e74e4276509a545a3f471370082dab4b","up":true},{"one":"332f7b60a1fbb6cd1e251dc68d1093b7f0bb05c1eefce5e61f124dff9445f14d","other":"a070c42951b523531f065cb8376eb8ca0c431b473ce9ebc44a3fc383f1f878b3","up":true},{"one":"61c24980b9ef4ee7fc33c51310f981a8e74e4276509a545a3f471370082dab4b","other":"29077dd2f4869b69ffc3ed40e82e4ef051292c3461888b5faf7b4c4b14d4928f","up":true},{"one":"a070c42951b523531f065cb8376eb8ca0c431b473ce9ebc44a3fc383f1f878b3","other":"9ecf217e2076c229f6d15bfe29df85aa129f1fb12d2a8307907327b5fe2f80c5","up":true},{"one":"29077dd2f4869b69ffc3ed40e82e4ef051292c3461888b5faf7b4c4b14d4928f","other":"2f4492429ab2ecd080a36451153990587de2458ebd9421fb989d8e27d4a88422","up":true},{"one":"9ecf217e2076c229f6d15bfe29df85aa129f1fb12d2a8307907327b5fe2f80c5","other":"c0f0bec8a3506280df599d57abc414d007696967381b9690bcf63a093586307c","up":true},{"one":"2f4492429ab2ecd080a36451153990587de2458ebd9421fb989d8e27d4a88422","other":"1be4ba2f068e060a0aabac445094f1dc8b22fe1e5575c98df8d8783aa3d95177","up":true},{"one":"c0f0bec8a3506280df599d57abc414d007696967381b9690bcf63a093586307c","other":"14645ab9b2ebbf4436466df07ff28e68b92774c6b5a0d311a174765e350c7089","up":true},{"one":"1be4ba2f068e060a0aabac445094f1dc8b22fe1e5575c98df8d8783aa3d95177","other":"f066d5d314253a872ab27df90d85ba5824da06032f92ce8bf20020b61971e199","up":true},{"one":"14645ab9b2ebbf4436466df07ff28e68b92774c6b5a0d311a174765e350c7089","other":"44b1544b5795a1c77094832bae47c84eb38482b1e1aaefbaecacbc7ef9d10e3e","up":true},{"one":"44b1544b5795a1c77094832bae47c84eb38482b1e1aaefbaecacbc7ef9d10e3e","other":"9f2d72c6e30ec363bdcf9ecf28fcf5553f98357680d8530581fcd815389005ec","up":true},{"one":"9f2d72c6e30ec363bdcf9ecf28fcf5553f98357680d8530581fcd815389005ec","other":"ada8540c63062347d3162850cf2a4820a20ed96514fb01b95d995bbfff97a212","up":true},{"one":"ada8540c63062347d3162850cf2a4820a20ed96514fb01b95d995bbfff97a212","other":"be9f880ccfd8dd0f5fde587c4e9fbdcb2d3551df0c4504d9938d4301a9276e72","up":true},{"one":"be9f880ccfd8dd0f5fde587c4e9fbdcb2d3551df0c4504d9938d4301a9276e72","other":"ce614a2fd326ce57b0ead5750d0a08f3c7dd94e1d2935861697e3df4652a22b6","up":true},{"one":"ce614a2fd326ce57b0ead5750d0a08f3c7dd94e1d2935861697e3df4652a22b6","other":"358eef3cf9964bf89e056afe0f4b46ce85bcb079bab922a4ae9dfa555001abfc","up":true},{"one":"358eef3cf9964bf89e056afe0f4b46ce85bcb079bab922a4ae9dfa555001abfc","other":"5cdc4493f01d6ebec3070b028275bd94ede61db21a4f5d3b282a366d1d3484b8","up":true},{"one":"5cdc4493f01d6ebec3070b028275bd94ede61db21a4f5d3b282a366d1d3484b8","other":"76933265c3d872b2d20e7a961f3f4e858afe11aee773b5caf678553e56ad3537","up":true},{"one":"76933265c3d872b2d20e7a961f3f4e858afe11aee773b5caf678553e56ad3537","other":"577b9ea47bc00cde25b11ca03861df0ccfaad8ce281500efede88d25e012b1e3","up":true},{"one":"577b9ea47bc00cde25b11ca03861df0ccfaad8ce281500efede88d25e012b1e3","other":"dfe389ee7fe03c809d7a4afa1f04dde9ab9a50d8c6ca5bf2489debb2e8329541","up":true},{"one":"dfe389ee7fe03c809d7a4afa1f04dde9ab9a50d8c6ca5bf2489debb2e8329541","other":"3ea2554db02021f0413529426d7a7b0ac9abe80643993f6d80fdfff93a765c40","up":true},{"one":"3ea2554db02021f0413529426d7a7b0ac9abe80643993f6d80fdfff93a765c40","other":"68d45eec895b91f081026e86ed164092dad8d4087918cd50c6f77767d9577f6d","up":true},{"one":"68d45eec895b91f081026e86ed164092dad8d4087918cd50c6f77767d9577f6d","other":"cfa6cfc1f856e69430370d5c52130519502c7a9e4fb14c7c8d9b309612a3c1bb","up":true},{"one":"cfa6cfc1f856e69430370d5c52130519502c7a9e4fb14c7c8d9b309612a3c1bb","other":"5d056a38c3a7c3bfcd431b767662903454f41429d2e03b9b30273e5b1b8e5c97","up":true},{"one":"be6906254ba2d3748ff8ce2ebbabaa03bad1c92d2eadc1ab6470548cb8d7c524","other":"66230e0bec41cfcf3667d7ddc7312888ae4887e8430c57dedafcdbadb6c12364","up":true},{"one":"5d056a38c3a7c3bfcd431b767662903454f41429d2e03b9b30273e5b1b8e5c97","other":"7a56b4203ee1cad781b23d2a78694f63eb7dfe6d9ac24fc181e6a63516c9efa5","up":true},{"one":"7a56b4203ee1cad781b23d2a78694f63eb7dfe6d9ac24fc181e6a63516c9efa5","other":"51db0d8fefeedfab3394f8f398b6d8f4540b24bc6cbd8e66bc5a7480db0228f5","up":true},{"one":"51db0d8fefeedfab3394f8f398b6d8f4540b24bc6cbd8e66bc5a7480db0228f5","other":"3c38aba43a15a61b680fa767353cce7e4165cbc996968f990001d7595ff243c8","up":true},{"one":"3c38aba43a15a61b680fa767353cce7e4165cbc996968f990001d7595ff243c8","other":"9dc2c4166fa9410d7be67cbb5ac572aa42a9a78c2efe5ea601ddd37c356196da","up":true},{"one":"9dc2c4166fa9410d7be67cbb5ac572aa42a9a78c2efe5ea601ddd37c356196da","other":"37debce0cbe19a2ab88f15dffa390df392938e7060970b93eae968a8c2497650","up":true},{"one":"de9eff7b7b80323f544746b9596e7da6b43b91e6c6d0660ad38df1e877ab8f99","other":"d10e58907f84eef0dfcfd3c81457b60eef9b1ea5b9a462e22f0c4e643a6e7140","up":true},{"one":"b4bee8054b3b7bc3ef0748937602699173983a764814e85f173916fd70f1e3ed","other":"bfa0a02816da29b40215fb3c26de9360505e4c69fca55086324f0e35de7486a5","up":true},{"one":"56ac3ac4cc04e94c93d986a0ed9da8b68d97f13c8b5ec35d56703592fc05bc43","other":"3c93b64143bf1bf130545806917472f3ca16ad6f223f0388f445ddc353140042","up":true},{"one":"544ad921e03de7d1d656038e23452dd3a9c7ef041520f17f8318326ecc58683b","other":"3c93b64143bf1bf130545806917472f3ca16ad6f223f0388f445ddc353140042","up":true},{"one":"6c4bdfe4650525d5efc1a4ca8d92ebbea33b1877bfac69656e81adbbad4f286a","other":"61c24980b9ef4ee7fc33c51310f981a8e74e4276509a545a3f471370082dab4b","up":true},{"one":"83870b048b374ed527d4b25256900d4b53fad11d900b54e366f9e3fe7ae50079","other":"f0ee0274e24a7cc3d1b0d38cd46d205ccbf791d0cc038d022f2a0c50ae6a5cd5","up":true},{"one":"f066d5d314253a872ab27df90d85ba5824da06032f92ce8bf20020b61971e199","other":"29077dd2f4869b69ffc3ed40e82e4ef051292c3461888b5faf7b4c4b14d4928f","up":true},{"one":"f03f47bf8f6241cbda878f301783e30cac333e8cea3d6bf9bc2be3b7fc2cf1c3","other":"fbbe14109dc4fa8251e852a181f3663b4245c93b737fa9c4921a53ed9949019d","up":true},{"one":"a070c42951b523531f065cb8376eb8ca0c431b473ce9ebc44a3fc383f1f878b3","other":"c0f0bec8a3506280df599d57abc414d007696967381b9690bcf63a093586307c","up":true},{"one":"44b1544b5795a1c77094832bae47c84eb38482b1e1aaefbaecacbc7ef9d10e3e","other":"5cdc4493f01d6ebec3070b028275bd94ede61db21a4f5d3b282a366d1d3484b8","up":true},{"one":"9f2d72c6e30ec363bdcf9ecf28fcf5553f98357680d8530581fcd815389005ec","other":"9ecf217e2076c229f6d15bfe29df85aa129f1fb12d2a8307907327b5fe2f80c5","up":true},{"one":"dfe389ee7fe03c809d7a4afa1f04dde9ab9a50d8c6ca5bf2489debb2e8329541","other":"cfa6cfc1f856e69430370d5c52130519502c7a9e4fb14c7c8d9b309612a3c1bb","up":true},{"one":"be9f880ccfd8dd0f5fde587c4e9fbdcb2d3551df0c4504d9938d4301a9276e72","other":"a070c42951b523531f065cb8376eb8ca0c431b473ce9ebc44a3fc383f1f878b3","up":true},{"one":"577b9ea47bc00cde25b11ca03861df0ccfaad8ce281500efede88d25e012b1e3","other":"5cdc4493f01d6ebec3070b028275bd94ede61db21a4f5d3b282a366d1d3484b8","up":true},{"one":"37debce0cbe19a2ab88f15dffa390df392938e7060970b93eae968a8c2497650","other":"66230e0bec41cfcf3667d7ddc7312888ae4887e8430c57dedafcdbadb6c12364","up":true},{"one":"2d831f78c7d670e4e1f19a47c80ec2df144da210ff3aaa00ca6c4990e4da0922","other":"23e4f18d302b6a545530b89eca007c141c751cdcde471bc3378bf72fd68b91c6","up":true},{"one":"76933265c3d872b2d20e7a961f3f4e858afe11aee773b5caf678553e56ad3537","other":"44b1544b5795a1c77094832bae47c84eb38482b1e1aaefbaecacbc7ef9d10e3e","up":true},{"one":"3c93b64143bf1bf130545806917472f3ca16ad6f223f0388f445ddc353140042","other":"72f779e91460990aee45e99b0565246a2620d4939cc3ab0fe021fefe71aae761","up":true},{"one":"df9ad5c2cd2375a5a227841841a47876765f6552a8b2cb9fa8cdedaeca2371ed","other":"d10e58907f84eef0dfcfd3c81457b60eef9b1ea5b9a462e22f0c4e643a6e7140","up":true},{"one":"3ff43d13b6ee84b128db4215c47c73909f6616039b87ca70abdd2108e29dddfb","other":"1abcab4e898966c42c11d1a92b1c1911fc779089c5a20ea1ddf005f76632b171","up":true},{"one":"634c2562544e0979530cc4dba918c849f24f85c87813e592c54e1f6d8d73bb6f","other":"61c24980b9ef4ee7fc33c51310f981a8e74e4276509a545a3f471370082dab4b","up":true},{"one":"de9eff7b7b80323f544746b9596e7da6b43b91e6c6d0660ad38df1e877ab8f99","other":"df9ad5c2cd2375a5a227841841a47876765f6552a8b2cb9fa8cdedaeca2371ed","up":true},{"one":"dbc536e11d371786bf8c61eec755d2cb22242c27e8edd7985dae612a9d2cac05","other":"d8d872ef5b1632eabca51f1d2755fa5a3f16dc487d4d8be960930ee4462a2633","up":true},{"one":"be6906254ba2d3748ff8ce2ebbabaa03bad1c92d2eadc1ab6470548cb8d7c524","other":"9dc2c4166fa9410d7be67cbb5ac572aa42a9a78c2efe5ea601ddd37c356196da","up":true},{"one":"3ff43d13b6ee84b128db4215c47c73909f6616039b87ca70abdd2108e29dddfb","other":"3c93b64143bf1bf130545806917472f3ca16ad6f223f0388f445ddc353140042","up":true},{"one":"544ad921e03de7d1d656038e23452dd3a9c7ef041520f17f8318326ecc58683b","other":"5c3ca7f91551934bbfa4020433d7b19e40d04d9291c2b26686ef85225f872bbf","up":true},{"one":"ada8540c63062347d3162850cf2a4820a20ed96514fb01b95d995bbfff97a212","other":"a070c42951b523531f065cb8376eb8ca0c431b473ce9ebc44a3fc383f1f878b3","up":true},{"one":"df9ad5c2cd2375a5a227841841a47876765f6552a8b2cb9fa8cdedaeca2371ed","other":"9dc2c4166fa9410d7be67cbb5ac572aa42a9a78c2efe5ea601ddd37c356196da","up":true},{"one":"56ac3ac4cc04e94c93d986a0ed9da8b68d97f13c8b5ec35d56703592fc05bc43","other":"5c3ca7f91551934bbfa4020433d7b19e40d04d9291c2b26686ef85225f872bbf","up":true},{"one":"3c93b64143bf1bf130545806917472f3ca16ad6f223f0388f445ddc353140042","other":"30d80772ad22d78f8a4e948a06677b5676aadb8a9c040f3ffb59a7e608e0929e","up":true},{"one":"e5eea2a0ac0175a0ad41239d8e36e537ce7d428d6b44e23e8e33ff0be11bac72","other":"dbc536e11d371786bf8c61eec755d2cb22242c27e8edd7985dae612a9d2cac05","up":true},{"one":"83870b048b374ed527d4b25256900d4b53fad11d900b54e366f9e3fe7ae50079","other":"b4bee8054b3b7bc3ef0748937602699173983a764814e85f173916fd70f1e3ed","up":true},{"one":"f03f47bf8f6241cbda878f301783e30cac333e8cea3d6bf9bc2be3b7fc2cf1c3","other":"f0ee0274e24a7cc3d1b0d38cd46d205ccbf791d0cc038d022f2a0c50ae6a5cd5","up":true},{"one":"61c24980b9ef4ee7fc33c51310f981a8e74e4276509a545a3f471370082dab4b","other":"7a1f03dc26ef21a5bd1673eb8418af7a7cbcb2ab1654882ebdcda2d1e6ad4836","up":true},{"one":"2d831f78c7d670e4e1f19a47c80ec2df144da210ff3aaa00ca6c4990e4da0922","other":"30d80772ad22d78f8a4e948a06677b5676aadb8a9c040f3ffb59a7e608e0929e","up":true},{"one":"fbbe14109dc4fa8251e852a181f3663b4245c93b737fa9c4921a53ed9949019d","other":"e3c20117761e1b3c3d7c3d133e1bf68e594ee25950cd4311a8d964923c4164da","up":true},{"one":"ce614a2fd326ce57b0ead5750d0a08f3c7dd94e1d2935861697e3df4652a22b6","other":"cfa6cfc1f856e69430370d5c52130519502c7a9e4fb14c7c8d9b309612a3c1bb","up":true},{"one":"634c2562544e0979530cc4dba918c849f24f85c87813e592c54e1f6d8d73bb6f","other":"6c4bdfe4650525d5efc1a4ca8d92ebbea33b1877bfac69656e81adbbad4f286a","up":true},{"one":"43900b2a084097a0ddf8bf4d0375624f731bd14c85300007c87269986a12eb3b","other":"6c4bdfe4650525d5efc1a4ca8d92ebbea33b1877bfac69656e81adbbad4f286a","up":true},{"one":"6c4bdfe4650525d5efc1a4ca8d92ebbea33b1877bfac69656e81adbbad4f286a","other":"7a1f03dc26ef21a5bd1673eb8418af7a7cbcb2ab1654882ebdcda2d1e6ad4836","up":true},{"one":"b4bee8054b3b7bc3ef0748937602699173983a764814e85f173916fd70f1e3ed","other":"f0ee0274e24a7cc3d1b0d38cd46d205ccbf791d0cc038d022f2a0c50ae6a5cd5","up":true},{"one":"7a56b4203ee1cad781b23d2a78694f63eb7dfe6d9ac24fc181e6a63516c9efa5","other":"76933265c3d872b2d20e7a961f3f4e858afe11aee773b5caf678553e56ad3537","up":true},{"one":"9ecf217e2076c229f6d15bfe29df85aa129f1fb12d2a8307907327b5fe2f80c5","other":"ada8540c63062347d3162850cf2a4820a20ed96514fb01b95d995bbfff97a212","up":true},{"one":"dfe389ee7fe03c809d7a4afa1f04dde9ab9a50d8c6ca5bf2489debb2e8329541","other":"dbc536e11d371786bf8c61eec755d2cb22242c27e8edd7985dae612a9d2cac05","up":true},{"one":"72f779e91460990aee45e99b0565246a2620d4939cc3ab0fe021fefe71aae761","other":"7a1f03dc26ef21a5bd1673eb8418af7a7cbcb2ab1654882ebdcda2d1e6ad4836","up":true},{"one":"7a56b4203ee1cad781b23d2a78694f63eb7dfe6d9ac24fc181e6a63516c9efa5","other":"66230e0bec41cfcf3667d7ddc7312888ae4887e8430c57dedafcdbadb6c12364","up":true},{"one":"37debce0cbe19a2ab88f15dffa390df392938e7060970b93eae968a8c2497650","other":"3c38aba43a15a61b680fa767353cce7e4165cbc996968f990001d7595ff243c8","up":true},{"one":"9f2d72c6e30ec363bdcf9ecf28fcf5553f98357680d8530581fcd815389005ec","other":"a070c42951b523531f065cb8376eb8ca0c431b473ce9ebc44a3fc383f1f878b3","up":true},{"one":"77687d9e478a52555674f12aa654ad931a3ee83920b9cdc8fecc03653ed7e386","other":"7a1f03dc26ef21a5bd1673eb8418af7a7cbcb2ab1654882ebdcda2d1e6ad4836","up":true},{"one":"ada8540c63062347d3162850cf2a4820a20ed96514fb01b95d995bbfff97a212","other":"66230e0bec41cfcf3667d7ddc7312888ae4887e8430c57dedafcdbadb6c12364","up":true},{"one":"61c24980b9ef4ee7fc33c51310f981a8e74e4276509a545a3f471370082dab4b","other":"a070c42951b523531f065cb8376eb8ca0c431b473ce9ebc44a3fc383f1f878b3","up":true},{"one":"577b9ea47bc00cde25b11ca03861df0ccfaad8ce281500efede88d25e012b1e3","other":"51db0d8fefeedfab3394f8f398b6d8f4540b24bc6cbd8e66bc5a7480db0228f5","up":true},{"one":"f066d5d314253a872ab27df90d85ba5824da06032f92ce8bf20020b61971e199","other":"fbbe14109dc4fa8251e852a181f3663b4245c93b737fa9c4921a53ed9949019d","up":true},{"one":"44b1544b5795a1c77094832bae47c84eb38482b1e1aaefbaecacbc7ef9d10e3e","other":"5d056a38c3a7c3bfcd431b767662903454f41429d2e03b9b30273e5b1b8e5c97","up":true},{"one":"be9f880ccfd8dd0f5fde587c4e9fbdcb2d3551df0c4504d9938d4301a9276e72","other":"66230e0bec41cfcf3667d7ddc7312888ae4887e8430c57dedafcdbadb6c12364","up":true},{"one":"ce614a2fd326ce57b0ead5750d0a08f3c7dd94e1d2935861697e3df4652a22b6","other":"c0f0bec8a3506280df599d57abc414d007696967381b9690bcf63a093586307c","up":true},{"one":"332f7b60a1fbb6cd1e251dc68d1093b7f0bb05c1eefce5e61f124dff9445f14d","other":"358eef3cf9964bf89e056afe0f4b46ce85bcb079bab922a4ae9dfa555001abfc","up":true},{"one":"23e4f18d302b6a545530b89eca007c141c751cdcde471bc3378bf72fd68b91c6","other":"29077dd2f4869b69ffc3ed40e82e4ef051292c3461888b5faf7b4c4b14d4928f","up":true},{"one":"76933265c3d872b2d20e7a961f3f4e858afe11aee773b5caf678553e56ad3537","other":"66230e0bec41cfcf3667d7ddc7312888ae4887e8430c57dedafcdbadb6c12364","up":true},{"one":"1abcab4e898966c42c11d1a92b1c1911fc779089c5a20ea1ddf005f76632b171","other":"3c93b64143bf1bf130545806917472f3ca16ad6f223f0388f445ddc353140042","up":true},{"one":"43900b2a084097a0ddf8bf4d0375624f731bd14c85300007c87269986a12eb3b","other":"634c2562544e0979530cc4dba918c849f24f85c87813e592c54e1f6d8d73bb6f","up":true},{"one":"bfa0a02816da29b40215fb3c26de9360505e4c69fca55086324f0e35de7486a5","other":"83870b048b374ed527d4b25256900d4b53fad11d900b54e366f9e3fe7ae50079","up":true},{"one":"14645ab9b2ebbf4436466df07ff28e68b92774c6b5a0d311a174765e350c7089","other":"1be4ba2f068e060a0aabac445094f1dc8b22fe1e5575c98df8d8783aa3d95177","up":true},{"one":"e3c20117761e1b3c3d7c3d133e1bf68e594ee25950cd4311a8d964923c4164da","other":"f03f47bf8f6241cbda878f301783e30cac333e8cea3d6bf9bc2be3b7fc2cf1c3","up":true},{"one":"c678643c5249eb90385aad201d6756f9f364024938d4c9f59f4aa51d64d48bd7","other":"c6c479f1c83f5158eb77d57e6321d5ed062964870775f51254461010c8882c10","up":true},{"one":"5c3ca7f91551934bbfa4020433d7b19e40d04d9291c2b26686ef85225f872bbf","other":"51db0d8fefeedfab3394f8f398b6d8f4540b24bc6cbd8e66bc5a7480db0228f5","up":true},{"one":"c6c479f1c83f5158eb77d57e6321d5ed062964870775f51254461010c8882c10","other":"d8d872ef5b1632eabca51f1d2755fa5a3f16dc487d4d8be960930ee4462a2633","up":true},{"one":"51db0d8fefeedfab3394f8f398b6d8f4540b24bc6cbd8e66bc5a7480db0228f5","other":"544ad921e03de7d1d656038e23452dd3a9c7ef041520f17f8318326ecc58683b","up":true},{"one":"1be4ba2f068e060a0aabac445094f1dc8b22fe1e5575c98df8d8783aa3d95177","other":"1abcab4e898966c42c11d1a92b1c1911fc779089c5a20ea1ddf005f76632b171","up":true},{"one":"de9eff7b7b80323f544746b9596e7da6b43b91e6c6d0660ad38df1e877ab8f99","other":"dfe389ee7fe03c809d7a4afa1f04dde9ab9a50d8c6ca5bf2489debb2e8329541","up":true},{"one":"e3c20117761e1b3c3d7c3d133e1bf68e594ee25950cd4311a8d964923c4164da","other":"f066d5d314253a872ab27df90d85ba5824da06032f92ce8bf20020b61971e199","up":true},{"one":"5c3ca7f91551934bbfa4020433d7b19e40d04d9291c2b26686ef85225f872bbf","other":"7a1f03dc26ef21a5bd1673eb8418af7a7cbcb2ab1654882ebdcda2d1e6ad4836","up":true},{"one":"be6906254ba2d3748ff8ce2ebbabaa03bad1c92d2eadc1ab6470548cb8d7c524","other":"ada8540c63062347d3162850cf2a4820a20ed96514fb01b95d995bbfff97a212","up":true},{"one":"544ad921e03de7d1d656038e23452dd3a9c7ef041520f17f8318326ecc58683b","other":"634c2562544e0979530cc4dba918c849f24f85c87813e592c54e1f6d8d73bb6f","up":true},{"one":"dbc536e11d371786bf8c61eec755d2cb22242c27e8edd7985dae612a9d2cac05","other":"df9ad5c2cd2375a5a227841841a47876765f6552a8b2cb9fa8cdedaeca2371ed","up":true},{"one":"3c38aba43a15a61b680fa767353cce7e4165cbc996968f990001d7595ff243c8","other":"3c93b64143bf1bf130545806917472f3ca16ad6f223f0388f445ddc353140042","up":true},{"one":"1abcab4e898966c42c11d1a92b1c1911fc779089c5a20ea1ddf005f76632b171","other":"3c38aba43a15a61b680fa767353cce7e4165cbc996968f990001d7595ff243c8","up":true},{"one":"56ac3ac4cc04e94c93d986a0ed9da8b68d97f13c8b5ec35d56703592fc05bc43","other":"66230e0bec41cfcf3667d7ddc7312888ae4887e8430c57dedafcdbadb6c12364","up":true},{"one":"2d831f78c7d670e4e1f19a47c80ec2df144da210ff3aaa00ca6c4990e4da0922","other":"2f4492429ab2ecd080a36451153990587de2458ebd9421fb989d8e27d4a88422","up":true},{"one":"3ff43d13b6ee84b128db4215c47c73909f6616039b87ca70abdd2108e29dddfb","other":"3c38aba43a15a61b680fa767353cce7e4165cbc996968f990001d7595ff243c8","up":true},{"one":"c0f0bec8a3506280df599d57abc414d007696967381b9690bcf63a093586307c","other":"cfa6cfc1f856e69430370d5c52130519502c7a9e4fb14c7c8d9b309612a3c1bb","up":true},{"one":"fbbe14109dc4fa8251e852a181f3663b4245c93b737fa9c4921a53ed9949019d","other":"f0ee0274e24a7cc3d1b0d38cd46d205ccbf791d0cc038d022f2a0c50ae6a5cd5","up":true},{"one":"1087495bf04e6967257f84b8e5de084e2000b9d748216e31c9f33fc9caedb715","other":"14645ab9b2ebbf4436466df07ff28e68b92774c6b5a0d311a174765e350c7089","up":true},{"one":"72f779e91460990aee45e99b0565246a2620d4939cc3ab0fe021fefe71aae761","other":"66230e0bec41cfcf3667d7ddc7312888ae4887e8430c57dedafcdbadb6c12364","up":true},{"one":"e5eea2a0ac0175a0ad41239d8e36e537ce7d428d6b44e23e8e33ff0be11bac72","other":"f03f47bf8f6241cbda878f301783e30cac333e8cea3d6bf9bc2be3b7fc2cf1c3","up":true},{"one":"f03f47bf8f6241cbda878f301783e30cac333e8cea3d6bf9bc2be3b7fc2cf1c3","other":"f066d5d314253a872ab27df90d85ba5824da06032f92ce8bf20020b61971e199","up":true},{"one":"bfa0a02816da29b40215fb3c26de9360505e4c69fca55086324f0e35de7486a5","other":"9dc2c4166fa9410d7be67cbb5ac572aa42a9a78c2efe5ea601ddd37c356196da","up":true},{"one":"c6c479f1c83f5158eb77d57e6321d5ed062964870775f51254461010c8882c10","other":"df9ad5c2cd2375a5a227841841a47876765f6552a8b2cb9fa8cdedaeca2371ed","up":true},{"one":"df9ad5c2cd2375a5a227841841a47876765f6552a8b2cb9fa8cdedaeca2371ed","other":"dfe389ee7fe03c809d7a4afa1f04dde9ab9a50d8c6ca5bf2489debb2e8329541","up":true},{"one":"83870b048b374ed527d4b25256900d4b53fad11d900b54e366f9e3fe7ae50079","other":"9dc2c4166fa9410d7be67cbb5ac572aa42a9a78c2efe5ea601ddd37c356196da","up":true},{"one":"c678643c5249eb90385aad201d6756f9f364024938d4c9f59f4aa51d64d48bd7","other":"d8d872ef5b1632eabca51f1d2755fa5a3f16dc487d4d8be960930ee4462a2633","up":true},{"one":"cfa6cfc1f856e69430370d5c52130519502c7a9e4fb14c7c8d9b309612a3c1bb","other":"df9ad5c2cd2375a5a227841841a47876765f6552a8b2cb9fa8cdedaeca2371ed","up":true},{"one":"f066d5d314253a872ab27df90d85ba5824da06032f92ce8bf20020b61971e199","other":"c0f0bec8a3506280df599d57abc414d007696967381b9690bcf63a093586307c","up":true},{"one":"be9f880ccfd8dd0f5fde587c4e9fbdcb2d3551df0c4504d9938d4301a9276e72","other":"83870b048b374ed527d4b25256900d4b53fad11d900b54e366f9e3fe7ae50079","up":true},{"one":"b4bee8054b3b7bc3ef0748937602699173983a764814e85f173916fd70f1e3ed","other":"9dc2c4166fa9410d7be67cbb5ac572aa42a9a78c2efe5ea601ddd37c356196da","up":true},{"one":"1087495bf04e6967257f84b8e5de084e2000b9d748216e31c9f33fc9caedb715","other":"1be4ba2f068e060a0aabac445094f1dc8b22fe1e5575c98df8d8783aa3d95177","up":true},{"one":"6c4bdfe4650525d5efc1a4ca8d92ebbea33b1877bfac69656e81adbbad4f286a","other":"b4bee8054b3b7bc3ef0748937602699173983a764814e85f173916fd70f1e3ed","up":true},{"one":"332f7b60a1fbb6cd1e251dc68d1093b7f0bb05c1eefce5e61f124dff9445f14d","other":"36c28c0237be6b2131280452c3113affcae28f6c7066811a8e1ee1a0a01decc3","up":true},{"one":"23e4f18d302b6a545530b89eca007c141c751cdcde471bc3378bf72fd68b91c6","other":"2f4492429ab2ecd080a36451153990587de2458ebd9421fb989d8e27d4a88422","up":true},{"one":"c0f0bec8a3506280df599d57abc414d007696967381b9690bcf63a093586307c","other":"fbbe14109dc4fa8251e852a181f3663b4245c93b737fa9c4921a53ed9949019d","up":true},{"one":"43900b2a084097a0ddf8bf4d0375624f731bd14c85300007c87269986a12eb3b","other":"544ad921e03de7d1d656038e23452dd3a9c7ef041520f17f8318326ecc58683b","up":true},{"one":"14645ab9b2ebbf4436466df07ff28e68b92774c6b5a0d311a174765e350c7089","other":"0d48477e33d7cfc4a7660048e4b1ece9f37e9759e96e6f1f47a86de091b0ee97","up":true},{"one":"634c2562544e0979530cc4dba918c849f24f85c87813e592c54e1f6d8d73bb6f","other":"a070c42951b523531f065cb8376eb8ca0c431b473ce9ebc44a3fc383f1f878b3","up":true},{"one":"77687d9e478a52555674f12aa654ad931a3ee83920b9cdc8fecc03653ed7e386","other":"72f779e91460990aee45e99b0565246a2620d4939cc3ab0fe021fefe71aae761","up":true},{"one":"61c24980b9ef4ee7fc33c51310f981a8e74e4276509a545a3f471370082dab4b","other":"544ad921e03de7d1d656038e23452dd3a9c7ef041520f17f8318326ecc58683b","up":true},{"one":"9f2d72c6e30ec363bdcf9ecf28fcf5553f98357680d8530581fcd815389005ec","other":"83870b048b374ed527d4b25256900d4b53fad11d900b54e366f9e3fe7ae50079","up":true},{"one":"9ecf217e2076c229f6d15bfe29df85aa129f1fb12d2a8307907327b5fe2f80c5","other":"be9f880ccfd8dd0f5fde587c4e9fbdcb2d3551df0c4504d9938d4301a9276e72","up":true},{"one":"9dc2c4166fa9410d7be67cbb5ac572aa42a9a78c2efe5ea601ddd37c356196da","other":"9ecf217e2076c229f6d15bfe29df85aa129f1fb12d2a8307907327b5fe2f80c5","up":true},{"one":"ada8540c63062347d3162850cf2a4820a20ed96514fb01b95d995bbfff97a212","other":"e5eea2a0ac0175a0ad41239d8e36e537ce7d428d6b44e23e8e33ff0be11bac72","up":true},{"one":"577b9ea47bc00cde25b11ca03861df0ccfaad8ce281500efede88d25e012b1e3","other":"5c3ca7f91551934bbfa4020433d7b19e40d04d9291c2b26686ef85225f872bbf","up":true},{"one":"2f4492429ab2ecd080a36451153990587de2458ebd9421fb989d8e27d4a88422","other":"f066d5d314253a872ab27df90d85ba5824da06032f92ce8bf20020b61971e199","up":true},{"one":"dfe389ee7fe03c809d7a4afa1f04dde9ab9a50d8c6ca5bf2489debb2e8329541","other":"d8d872ef5b1632eabca51f1d2755fa5a3f16dc487d4d8be960930ee4462a2633","up":true},{"one":"cfa6cfc1f856e69430370d5c52130519502c7a9e4fb14c7c8d9b309612a3c1bb","other":"de9eff7b7b80323f544746b9596e7da6b43b91e6c6d0660ad38df1e877ab8f99","up":true},{"one":"d8d872ef5b1632eabca51f1d2755fa5a3f16dc487d4d8be960930ee4462a2633","other":"de9eff7b7b80323f544746b9596e7da6b43b91e6c6d0660ad38df1e877ab8f99","up":true},{"one":"7a56b4203ee1cad781b23d2a78694f63eb7dfe6d9ac24fc181e6a63516c9efa5","other":"68d45eec895b91f081026e86ed164092dad8d4087918cd50c6f77767d9577f6d","up":true},{"one":"76933265c3d872b2d20e7a961f3f4e858afe11aee773b5caf678553e56ad3537","other":"f066d5d314253a872ab27df90d85ba5824da06032f92ce8bf20020b61971e199","up":true},{"one":"37debce0cbe19a2ab88f15dffa390df392938e7060970b93eae968a8c2497650","other":"3c93b64143bf1bf130545806917472f3ca16ad6f223f0388f445ddc353140042","up":true},{"one":"9dc2c4166fa9410d7be67cbb5ac572aa42a9a78c2efe5ea601ddd37c356196da","other":"ada8540c63062347d3162850cf2a4820a20ed96514fb01b95d995bbfff97a212","up":true},{"one":"51db0d8fefeedfab3394f8f398b6d8f4540b24bc6cbd8e66bc5a7480db0228f5","other":"56ac3ac4cc04e94c93d986a0ed9da8b68d97f13c8b5ec35d56703592fc05bc43","up":true},{"one":"f0ee0274e24a7cc3d1b0d38cd46d205ccbf791d0cc038d022f2a0c50ae6a5cd5","other":"e3c20117761e1b3c3d7c3d133e1bf68e594ee25950cd4311a8d964923c4164da","up":true},{"one":"de9eff7b7b80323f544746b9596e7da6b43b91e6c6d0660ad38df1e877ab8f99","other":"dbc536e11d371786bf8c61eec755d2cb22242c27e8edd7985dae612a9d2cac05","up":true},{"one":"3c38aba43a15a61b680fa767353cce7e4165cbc996968f990001d7595ff243c8","other":"358eef3cf9964bf89e056afe0f4b46ce85bcb079bab922a4ae9dfa555001abfc","up":true},{"one":"1abcab4e898966c42c11d1a92b1c1911fc779089c5a20ea1ddf005f76632b171","other":"30d80772ad22d78f8a4e948a06677b5676aadb8a9c040f3ffb59a7e608e0929e","up":true},{"one":"3ff43d13b6ee84b128db4215c47c73909f6616039b87ca70abdd2108e29dddfb","other":"30d80772ad22d78f8a4e948a06677b5676aadb8a9c040f3ffb59a7e608e0929e","up":true},{"one":"5cdc4493f01d6ebec3070b028275bd94ede61db21a4f5d3b282a366d1d3484b8","other":"5c3ca7f91551934bbfa4020433d7b19e40d04d9291c2b26686ef85225f872bbf","up":true},{"one":"be6906254ba2d3748ff8ce2ebbabaa03bad1c92d2eadc1ab6470548cb8d7c524","other":"9ecf217e2076c229f6d15bfe29df85aa129f1fb12d2a8307907327b5fe2f80c5","up":true},{"one":"e5eea2a0ac0175a0ad41239d8e36e537ce7d428d6b44e23e8e33ff0be11bac72","other":"fbbe14109dc4fa8251e852a181f3663b4245c93b737fa9c4921a53ed9949019d","up":true},{"one":"e3c20117761e1b3c3d7c3d133e1bf68e594ee25950cd4311a8d964923c4164da","other":"ada8540c63062347d3162850cf2a4820a20ed96514fb01b95d995bbfff97a212","up":true},{"one":"d8d872ef5b1632eabca51f1d2755fa5a3f16dc487d4d8be960930ee4462a2633","other":"7a1f03dc26ef21a5bd1673eb8418af7a7cbcb2ab1654882ebdcda2d1e6ad4836","up":true},{"one":"56ac3ac4cc04e94c93d986a0ed9da8b68d97f13c8b5ec35d56703592fc05bc43","other":"634c2562544e0979530cc4dba918c849f24f85c87813e592c54e1f6d8d73bb6f","up":true},{"one":"dbc536e11d371786bf8c61eec755d2cb22242c27e8edd7985dae612a9d2cac05","other":"d10e58907f84eef0dfcfd3c81457b60eef9b1ea5b9a462e22f0c4e643a6e7140","up":true},{"one":"df9ad5c2cd2375a5a227841841a47876765f6552a8b2cb9fa8cdedaeca2371ed","other":"fbbe14109dc4fa8251e852a181f3663b4245c93b737fa9c4921a53ed9949019d","up":true},{"one":"3ea2554db02021f0413529426d7a7b0ac9abe80643993f6d80fdfff93a765c40","other":"3c38aba43a15a61b680fa767353cce7e4165cbc996968f990001d7595ff243c8","up":true},{"one":"29077dd2f4869b69ffc3ed40e82e4ef051292c3461888b5faf7b4c4b14d4928f","other":"2d831f78c7d670e4e1f19a47c80ec2df144da210ff3aaa00ca6c4990e4da0922","up":true},{"one":"f0ee0274e24a7cc3d1b0d38cd46d205ccbf791d0cc038d022f2a0c50ae6a5cd5","other":"c678643c5249eb90385aad201d6756f9f364024938d4c9f59f4aa51d64d48bd7","up":true},{"one":"5d056a38c3a7c3bfcd431b767662903454f41429d2e03b9b30273e5b1b8e5c97","other":"5c3ca7f91551934bbfa4020433d7b19e40d04d9291c2b26686ef85225f872bbf","up":true},{"one":"3c93b64143bf1bf130545806917472f3ca16ad6f223f0388f445ddc353140042","other":"36c28c0237be6b2131280452c3113affcae28f6c7066811a8e1ee1a0a01decc3","up":true},{"one":"544ad921e03de7d1d656038e23452dd3a9c7ef041520f17f8318326ecc58683b","other":"44b1544b5795a1c77094832bae47c84eb38482b1e1aaefbaecacbc7ef9d10e3e","up":true},{"one":"1087495bf04e6967257f84b8e5de084e2000b9d748216e31c9f33fc9caedb715","other":"1abcab4e898966c42c11d1a92b1c1911fc779089c5a20ea1ddf005f76632b171","up":true},{"one":"23e4f18d302b6a545530b89eca007c141c751cdcde471bc3378bf72fd68b91c6","other":"b4bee8054b3b7bc3ef0748937602699173983a764814e85f173916fd70f1e3ed","up":true},{"one":"6c4bdfe4650525d5efc1a4ca8d92ebbea33b1877bfac69656e81adbbad4f286a","other":"be9f880ccfd8dd0f5fde587c4e9fbdcb2d3551df0c4504d9938d4301a9276e72","up":true},{"one":"83870b048b374ed527d4b25256900d4b53fad11d900b54e366f9e3fe7ae50079","other":"9ecf217e2076c229f6d15bfe29df85aa129f1fb12d2a8307907327b5fe2f80c5","up":true},{"one":"bfa0a02816da29b40215fb3c26de9360505e4c69fca55086324f0e35de7486a5","other":"a070c42951b523531f065cb8376eb8ca0c431b473ce9ebc44a3fc383f1f878b3","up":true},{"one":"43900b2a084097a0ddf8bf4d0375624f731bd14c85300007c87269986a12eb3b","other":"61c24980b9ef4ee7fc33c51310f981a8e74e4276509a545a3f471370082dab4b","up":true},{"one":"36c28c0237be6b2131280452c3113affcae28f6c7066811a8e1ee1a0a01decc3","other":"3ff43d13b6ee84b128db4215c47c73909f6616039b87ca70abdd2108e29dddfb","up":true},{"one":"fbbe14109dc4fa8251e852a181f3663b4245c93b737fa9c4921a53ed9949019d","other":"9dc2c4166fa9410d7be67cbb5ac572aa42a9a78c2efe5ea601ddd37c356196da","up":true},{"one":"634c2562544e0979530cc4dba918c849f24f85c87813e592c54e1f6d8d73bb6f","other":"66230e0bec41cfcf3667d7ddc7312888ae4887e8430c57dedafcdbadb6c12364","up":true},{"one":"b4bee8054b3b7bc3ef0748937602699173983a764814e85f173916fd70f1e3ed","other":"a070c42951b523531f065cb8376eb8ca0c431b473ce9ebc44a3fc383f1f878b3","up":true},{"one":"c678643c5249eb90385aad201d6756f9f364024938d4c9f59f4aa51d64d48bd7","other":"d10e58907f84eef0dfcfd3c81457b60eef9b1ea5b9a462e22f0c4e643a6e7140","up":true},{"one":"2d831f78c7d670e4e1f19a47c80ec2df144da210ff3aaa00ca6c4990e4da0922","other":"44b1544b5795a1c77094832bae47c84eb38482b1e1aaefbaecacbc7ef9d10e3e","up":true},{"one":"d10e58907f84eef0dfcfd3c81457b60eef9b1ea5b9a462e22f0c4e643a6e7140","other":"dfe389ee7fe03c809d7a4afa1f04dde9ab9a50d8c6ca5bf2489debb2e8329541","up":true},{"one":"77687d9e478a52555674f12aa654ad931a3ee83920b9cdc8fecc03653ed7e386","other":"634c2562544e0979530cc4dba918c849f24f85c87813e592c54e1f6d8d73bb6f","up":true},{"one":"9ecf217e2076c229f6d15bfe29df85aa129f1fb12d2a8307907327b5fe2f80c5","other":"6c4bdfe4650525d5efc1a4ca8d92ebbea33b1877bfac69656e81adbbad4f286a","up":true},{"one":"29077dd2f4869b69ffc3ed40e82e4ef051292c3461888b5faf7b4c4b14d4928f","other":"0d48477e33d7cfc4a7660048e4b1ece9f37e9759e96e6f1f47a86de091b0ee97","up":true},{"one":"61c24980b9ef4ee7fc33c51310f981a8e74e4276509a545a3f471370082dab4b","other":"44b1544b5795a1c77094832bae47c84eb38482b1e1aaefbaecacbc7ef9d10e3e","up":true},{"one":"2f4492429ab2ecd080a36451153990587de2458ebd9421fb989d8e27d4a88422","other":"f03f47bf8f6241cbda878f301783e30cac333e8cea3d6bf9bc2be3b7fc2cf1c3","up":true},{"one":"9dc2c4166fa9410d7be67cbb5ac572aa42a9a78c2efe5ea601ddd37c356196da","other":"9f2d72c6e30ec363bdcf9ecf28fcf5553f98357680d8530581fcd815389005ec","up":true},{"one":"332f7b60a1fbb6cd1e251dc68d1093b7f0bb05c1eefce5e61f124dff9445f14d","other":"577b9ea47bc00cde25b11ca03861df0ccfaad8ce281500efede88d25e012b1e3","up":true},{"one":"0d48477e33d7cfc4a7660048e4b1ece9f37e9759e96e6f1f47a86de091b0ee97","other":"1be4ba2f068e060a0aabac445094f1dc8b22fe1e5575c98df8d8783aa3d95177","up":true},{"one":"37debce0cbe19a2ab88f15dffa390df392938e7060970b93eae968a8c2497650","other":"36c28c0237be6b2131280452c3113affcae28f6c7066811a8e1ee1a0a01decc3","up":true},{"one":"5d056a38c3a7c3bfcd431b767662903454f41429d2e03b9b30273e5b1b8e5c97","other":"51db0d8fefeedfab3394f8f398b6d8f4540b24bc6cbd8e66bc5a7480db0228f5","up":true},{"one":"66230e0bec41cfcf3667d7ddc7312888ae4887e8430c57dedafcdbadb6c12364","other":"68d45eec895b91f081026e86ed164092dad8d4087918cd50c6f77767d9577f6d","up":true},{"one":"76933265c3d872b2d20e7a961f3f4e858afe11aee773b5caf678553e56ad3537","other":"72f779e91460990aee45e99b0565246a2620d4939cc3ab0fe021fefe71aae761","up":true},{"one":"577b9ea47bc00cde25b11ca03861df0ccfaad8ce281500efede88d25e012b1e3","other":"56ac3ac4cc04e94c93d986a0ed9da8b68d97f13c8b5ec35d56703592fc05bc43","up":true},{"one":"3ea2554db02021f0413529426d7a7b0ac9abe80643993f6d80fdfff93a765c40","other":"3c93b64143bf1bf130545806917472f3ca16ad6f223f0388f445ddc353140042","up":true},{"one":"7a56b4203ee1cad781b23d2a78694f63eb7dfe6d9ac24fc181e6a63516c9efa5","other":"f066d5d314253a872ab27df90d85ba5824da06032f92ce8bf20020b61971e199","up":true},{"one":"cfa6cfc1f856e69430370d5c52130519502c7a9e4fb14c7c8d9b309612a3c1bb","other":"b4bee8054b3b7bc3ef0748937602699173983a764814e85f173916fd70f1e3ed","up":true},{"one":"14645ab9b2ebbf4436466df07ff28e68b92774c6b5a0d311a174765e350c7089","other":"29077dd2f4869b69ffc3ed40e82e4ef051292c3461888b5faf7b4c4b14d4928f","up":true},{"one":"ada8540c63062347d3162850cf2a4820a20ed96514fb01b95d995bbfff97a212","other":"fbbe14109dc4fa8251e852a181f3663b4245c93b737fa9c4921a53ed9949019d","up":true},{"one":"7a1f03dc26ef21a5bd1673eb8418af7a7cbcb2ab1654882ebdcda2d1e6ad4836","other":"b4bee8054b3b7bc3ef0748937602699173983a764814e85f173916fd70f1e3ed","up":true},{"one":"ce614a2fd326ce57b0ead5750d0a08f3c7dd94e1d2935861697e3df4652a22b6","other":"e5eea2a0ac0175a0ad41239d8e36e537ce7d428d6b44e23e8e33ff0be11bac72","up":true},{"one":"5cdc4493f01d6ebec3070b028275bd94ede61db21a4f5d3b282a366d1d3484b8","other":"5d056a38c3a7c3bfcd431b767662903454f41429d2e03b9b30273e5b1b8e5c97","up":true},{"one":"68d45eec895b91f081026e86ed164092dad8d4087918cd50c6f77767d9577f6d","other":"76933265c3d872b2d20e7a961f3f4e858afe11aee773b5caf678553e56ad3537","up":true},{"one":"d10e58907f84eef0dfcfd3c81457b60eef9b1ea5b9a462e22f0c4e643a6e7140","other":"d8d872ef5b1632eabca51f1d2755fa5a3f16dc487d4d8be960930ee4462a2633","up":true},{"one":"30d80772ad22d78f8a4e948a06677b5676aadb8a9c040f3ffb59a7e608e0929e","other":"332f7b60a1fbb6cd1e251dc68d1093b7f0bb05c1eefce5e61f124dff9445f14d","up":true},{"one":"66230e0bec41cfcf3667d7ddc7312888ae4887e8430c57dedafcdbadb6c12364","other":"5c3ca7f91551934bbfa4020433d7b19e40d04d9291c2b26686ef85225f872bbf","up":true},{"one":"be6906254ba2d3748ff8ce2ebbabaa03bad1c92d2eadc1ab6470548cb8d7c524","other":"b4bee8054b3b7bc3ef0748937602699173983a764814e85f173916fd70f1e3ed","up":true},{"one":"5c3ca7f91551934bbfa4020433d7b19e40d04d9291c2b26686ef85225f872bbf","other":"72f779e91460990aee45e99b0565246a2620d4939cc3ab0fe021fefe71aae761","up":true},{"one":"1abcab4e898966c42c11d1a92b1c1911fc779089c5a20ea1ddf005f76632b171","other":"36c28c0237be6b2131280452c3113affcae28f6c7066811a8e1ee1a0a01decc3","up":true},{"one":"3ff43d13b6ee84b128db4215c47c73909f6616039b87ca70abdd2108e29dddfb","other":"72f779e91460990aee45e99b0565246a2620d4939cc3ab0fe021fefe71aae761","up":true},{"one":"e5eea2a0ac0175a0ad41239d8e36e537ce7d428d6b44e23e8e33ff0be11bac72","other":"f066d5d314253a872ab27df90d85ba5824da06032f92ce8bf20020b61971e199","up":true},{"one":"3c93b64143bf1bf130545806917472f3ca16ad6f223f0388f445ddc353140042","other":"23e4f18d302b6a545530b89eca007c141c751cdcde471bc3378bf72fd68b91c6","up":true},{"one":"61c24980b9ef4ee7fc33c51310f981a8e74e4276509a545a3f471370082dab4b","other":"66230e0bec41cfcf3667d7ddc7312888ae4887e8430c57dedafcdbadb6c12364","up":true},{"one":"c6c479f1c83f5158eb77d57e6321d5ed062964870775f51254461010c8882c10","other":"d10e58907f84eef0dfcfd3c81457b60eef9b1ea5b9a462e22f0c4e643a6e7140","up":true},{"one":"358eef3cf9964bf89e056afe0f4b46ce85bcb079bab922a4ae9dfa555001abfc","other":"37debce0cbe19a2ab88f15dffa390df392938e7060970b93eae968a8c2497650","up":true},{"one":"b4bee8054b3b7bc3ef0748937602699173983a764814e85f173916fd70f1e3ed","other":"be9f880ccfd8dd0f5fde587c4e9fbdcb2d3551df0c4504d9938d4301a9276e72","up":true},{"one":"fbbe14109dc4fa8251e852a181f3663b4245c93b737fa9c4921a53ed9949019d","other":"9f2d72c6e30ec363bdcf9ecf28fcf5553f98357680d8530581fcd815389005ec","up":true},{"one":"30d80772ad22d78f8a4e948a06677b5676aadb8a9c040f3ffb59a7e608e0929e","other":"36c28c0237be6b2131280452c3113affcae28f6c7066811a8e1ee1a0a01decc3","up":true},{"one":"36c28c0237be6b2131280452c3113affcae28f6c7066811a8e1ee1a0a01decc3","other":"358eef3cf9964bf89e056afe0f4b46ce85bcb079bab922a4ae9dfa555001abfc","up":true},{"one":"6c4bdfe4650525d5efc1a4ca8d92ebbea33b1877bfac69656e81adbbad4f286a","other":"66230e0bec41cfcf3667d7ddc7312888ae4887e8430c57dedafcdbadb6c12364","up":true},{"one":"c678643c5249eb90385aad201d6756f9f364024938d4c9f59f4aa51d64d48bd7","other":"c0f0bec8a3506280df599d57abc414d007696967381b9690bcf63a093586307c","up":true},{"one":"f03f47bf8f6241cbda878f301783e30cac333e8cea3d6bf9bc2be3b7fc2cf1c3","other":"d10e58907f84eef0dfcfd3c81457b60eef9b1ea5b9a462e22f0c4e643a6e7140","up":true},{"one":"29077dd2f4869b69ffc3ed40e82e4ef051292c3461888b5faf7b4c4b14d4928f","other":"3c93b64143bf1bf130545806917472f3ca16ad6f223f0388f445ddc353140042","up":true},{"one":"be9f880ccfd8dd0f5fde587c4e9fbdcb2d3551df0c4504d9938d4301a9276e72","other":"be6906254ba2d3748ff8ce2ebbabaa03bad1c92d2eadc1ab6470548cb8d7c524","up":true},{"one":"9ecf217e2076c229f6d15bfe29df85aa129f1fb12d2a8307907327b5fe2f80c5","other":"634c2562544e0979530cc4dba918c849f24f85c87813e592c54e1f6d8d73bb6f","up":true},{"one":"2d831f78c7d670e4e1f19a47c80ec2df144da210ff3aaa00ca6c4990e4da0922","other":"14645ab9b2ebbf4436466df07ff28e68b92774c6b5a0d311a174765e350c7089","up":true},{"one":"0d48477e33d7cfc4a7660048e4b1ece9f37e9759e96e6f1f47a86de091b0ee97","other":"1abcab4e898966c42c11d1a92b1c1911fc779089c5a20ea1ddf005f76632b171","up":true},{"one":"43900b2a084097a0ddf8bf4d0375624f731bd14c85300007c87269986a12eb3b","other":"44b1544b5795a1c77094832bae47c84eb38482b1e1aaefbaecacbc7ef9d10e3e","up":true},{"one":"f066d5d314253a872ab27df90d85ba5824da06032f92ce8bf20020b61971e199","other":"be9f880ccfd8dd0f5fde587c4e9fbdcb2d3551df0c4504d9938d4301a9276e72","up":true},{"one":"358eef3cf9964bf89e056afe0f4b46ce85bcb079bab922a4ae9dfa555001abfc","other":"3ea2554db02021f0413529426d7a7b0ac9abe80643993f6d80fdfff93a765c40","up":true},{"one":"77687d9e478a52555674f12aa654ad931a3ee83920b9cdc8fecc03653ed7e386","other":"be9f880ccfd8dd0f5fde587c4e9fbdcb2d3551df0c4504d9938d4301a9276e72","up":true},{"one":"332f7b60a1fbb6cd1e251dc68d1093b7f0bb05c1eefce5e61f124dff9445f14d","other":"37debce0cbe19a2ab88f15dffa390df392938e7060970b93eae968a8c2497650","up":true},{"one":"c0f0bec8a3506280df599d57abc414d007696967381b9690bcf63a093586307c","other":"d10e58907f84eef0dfcfd3c81457b60eef9b1ea5b9a462e22f0c4e643a6e7140","up":true},{"one":"14645ab9b2ebbf4436466df07ff28e68b92774c6b5a0d311a174765e350c7089","other":"2f4492429ab2ecd080a36451153990587de2458ebd9421fb989d8e27d4a88422","up":true},{"one":"44b1544b5795a1c77094832bae47c84eb38482b1e1aaefbaecacbc7ef9d10e3e","other":"5c3ca7f91551934bbfa4020433d7b19e40d04d9291c2b26686ef85225f872bbf","up":true},{"one":"9f2d72c6e30ec363bdcf9ecf28fcf5553f98357680d8530581fcd815389005ec","other":"e5eea2a0ac0175a0ad41239d8e36e537ce7d428d6b44e23e8e33ff0be11bac72","up":true},{"one":"5d056a38c3a7c3bfcd431b767662903454f41429d2e03b9b30273e5b1b8e5c97","other":"14645ab9b2ebbf4436466df07ff28e68b92774c6b5a0d311a174765e350c7089","up":true},{"one":"37debce0cbe19a2ab88f15dffa390df392938e7060970b93eae968a8c2497650","other":"30d80772ad22d78f8a4e948a06677b5676aadb8a9c040f3ffb59a7e608e0929e","up":true},{"one":"577b9ea47bc00cde25b11ca03861df0ccfaad8ce281500efede88d25e012b1e3","other":"544ad921e03de7d1d656038e23452dd3a9c7ef041520f17f8318326ecc58683b","up":true},{"one":"1be4ba2f068e060a0aabac445094f1dc8b22fe1e5575c98df8d8783aa3d95177","other":"577b9ea47bc00cde25b11ca03861df0ccfaad8ce281500efede88d25e012b1e3","up":true},{"one":"51db0d8fefeedfab3394f8f398b6d8f4540b24bc6cbd8e66bc5a7480db0228f5","other":"f066d5d314253a872ab27df90d85ba5824da06032f92ce8bf20020b61971e199","up":true},{"one":"cfa6cfc1f856e69430370d5c52130519502c7a9e4fb14c7c8d9b309612a3c1bb","other":"be6906254ba2d3748ff8ce2ebbabaa03bad1c92d2eadc1ab6470548cb8d7c524","up":true},{"one":"3c38aba43a15a61b680fa767353cce7e4165cbc996968f990001d7595ff243c8","other":"332f7b60a1fbb6cd1e251dc68d1093b7f0bb05c1eefce5e61f124dff9445f14d","up":true},{"one":"76933265c3d872b2d20e7a961f3f4e858afe11aee773b5caf678553e56ad3537","other":"77687d9e478a52555674f12aa654ad931a3ee83920b9cdc8fecc03653ed7e386","up":true},{"one":"7a56b4203ee1cad781b23d2a78694f63eb7dfe6d9ac24fc181e6a63516c9efa5","other":"72f779e91460990aee45e99b0565246a2620d4939cc3ab0fe021fefe71aae761","up":true},{"one":"72f779e91460990aee45e99b0565246a2620d4939cc3ab0fe021fefe71aae761","other":"43900b2a084097a0ddf8bf4d0375624f731bd14c85300007c87269986a12eb3b","up":true},{"one":"d10e58907f84eef0dfcfd3c81457b60eef9b1ea5b9a462e22f0c4e643a6e7140","other":"f066d5d314253a872ab27df90d85ba5824da06032f92ce8bf20020b61971e199","up":true},{"one":"3ff43d13b6ee84b128db4215c47c73909f6616039b87ca70abdd2108e29dddfb","other":"7a1f03dc26ef21a5bd1673eb8418af7a7cbcb2ab1654882ebdcda2d1e6ad4836","up":true},{"one":"56ac3ac4cc04e94c93d986a0ed9da8b68d97f13c8b5ec35d56703592fc05bc43","other":"44b1544b5795a1c77094832bae47c84eb38482b1e1aaefbaecacbc7ef9d10e3e","up":true},{"one":"3c93b64143bf1bf130545806917472f3ca16ad6f223f0388f445ddc353140042","other":"332f7b60a1fbb6cd1e251dc68d1093b7f0bb05c1eefce5e61f124dff9445f14d","up":true},{"one":"66230e0bec41cfcf3667d7ddc7312888ae4887e8430c57dedafcdbadb6c12364","other":"44b1544b5795a1c77094832bae47c84eb38482b1e1aaefbaecacbc7ef9d10e3e","up":true},{"one":"be6906254ba2d3748ff8ce2ebbabaa03bad1c92d2eadc1ab6470548cb8d7c524","other":"bfa0a02816da29b40215fb3c26de9360505e4c69fca55086324f0e35de7486a5","up":true},{"one":"0d48477e33d7cfc4a7660048e4b1ece9f37e9759e96e6f1f47a86de091b0ee97","other":"1087495bf04e6967257f84b8e5de084e2000b9d748216e31c9f33fc9caedb715","up":true},{"one":"72f779e91460990aee45e99b0565246a2620d4939cc3ab0fe021fefe71aae761","other":"44b1544b5795a1c77094832bae47c84eb38482b1e1aaefbaecacbc7ef9d10e3e","up":true},{"one":"1abcab4e898966c42c11d1a92b1c1911fc779089c5a20ea1ddf005f76632b171","other":"332f7b60a1fbb6cd1e251dc68d1093b7f0bb05c1eefce5e61f124dff9445f14d","up":true},{"one":"36c28c0237be6b2131280452c3113affcae28f6c7066811a8e1ee1a0a01decc3","other":"be9f880ccfd8dd0f5fde587c4e9fbdcb2d3551df0c4504d9938d4301a9276e72","up":true},{"one":"577b9ea47bc00cde25b11ca03861df0ccfaad8ce281500efede88d25e012b1e3","other":"43900b2a084097a0ddf8bf4d0375624f731bd14c85300007c87269986a12eb3b","up":true},{"one":"68d45eec895b91f081026e86ed164092dad8d4087918cd50c6f77767d9577f6d","other":"634c2562544e0979530cc4dba918c849f24f85c87813e592c54e1f6d8d73bb6f","up":true},{"one":"76933265c3d872b2d20e7a961f3f4e858afe11aee773b5caf678553e56ad3537","other":"1be4ba2f068e060a0aabac445094f1dc8b22fe1e5575c98df8d8783aa3d95177","up":true},{"one":"ce614a2fd326ce57b0ead5750d0a08f3c7dd94e1d2935861697e3df4652a22b6","other":"fbbe14109dc4fa8251e852a181f3663b4245c93b737fa9c4921a53ed9949019d","up":true},{"one":"dfe389ee7fe03c809d7a4afa1f04dde9ab9a50d8c6ca5bf2489debb2e8329541","other":"83870b048b374ed527d4b25256900d4b53fad11d900b54e366f9e3fe7ae50079","up":true},{"one":"7a56b4203ee1cad781b23d2a78694f63eb7dfe6d9ac24fc181e6a63516c9efa5","other":"61c24980b9ef4ee7fc33c51310f981a8e74e4276509a545a3f471370082dab4b","up":true},{"one":"3ea2554db02021f0413529426d7a7b0ac9abe80643993f6d80fdfff93a765c40","other":"1be4ba2f068e060a0aabac445094f1dc8b22fe1e5575c98df8d8783aa3d95177","up":true},{"one":"51db0d8fefeedfab3394f8f398b6d8f4540b24bc6cbd8e66bc5a7480db0228f5","other":"f03f47bf8f6241cbda878f301783e30cac333e8cea3d6bf9bc2be3b7fc2cf1c3","up":true},{"one":"3c38aba43a15a61b680fa767353cce7e4165cbc996968f990001d7595ff243c8","other":"30d80772ad22d78f8a4e948a06677b5676aadb8a9c040f3ffb59a7e608e0929e","up":true},{"one":"f0ee0274e24a7cc3d1b0d38cd46d205ccbf791d0cc038d022f2a0c50ae6a5cd5","other":"f066d5d314253a872ab27df90d85ba5824da06032f92ce8bf20020b61971e199","up":true},{"one":"2f4492429ab2ecd080a36451153990587de2458ebd9421fb989d8e27d4a88422","other":"577b9ea47bc00cde25b11ca03861df0ccfaad8ce281500efede88d25e012b1e3","up":true},{"one":"77687d9e478a52555674f12aa654ad931a3ee83920b9cdc8fecc03653ed7e386","other":"b4bee8054b3b7bc3ef0748937602699173983a764814e85f173916fd70f1e3ed","up":true},{"one":"1be4ba2f068e060a0aabac445094f1dc8b22fe1e5575c98df8d8783aa3d95177","other":"5cdc4493f01d6ebec3070b028275bd94ede61db21a4f5d3b282a366d1d3484b8","up":true},{"one":"de9eff7b7b80323f544746b9596e7da6b43b91e6c6d0660ad38df1e877ab8f99","other":"83870b048b374ed527d4b25256900d4b53fad11d900b54e366f9e3fe7ae50079","up":true},{"one":"43900b2a084097a0ddf8bf4d0375624f731bd14c85300007c87269986a12eb3b","other":"56ac3ac4cc04e94c93d986a0ed9da8b68d97f13c8b5ec35d56703592fc05bc43","up":true},{"one":"de9eff7b7b80323f544746b9596e7da6b43b91e6c6d0660ad38df1e877ab8f99","other":"9ecf217e2076c229f6d15bfe29df85aa129f1fb12d2a8307907327b5fe2f80c5","up":true},{"one":"23e4f18d302b6a545530b89eca007c141c751cdcde471bc3378bf72fd68b91c6","other":"3ea2554db02021f0413529426d7a7b0ac9abe80643993f6d80fdfff93a765c40","up":true},{"one":"dbc536e11d371786bf8c61eec755d2cb22242c27e8edd7985dae612a9d2cac05","other":"c0f0bec8a3506280df599d57abc414d007696967381b9690bcf63a093586307c","up":true},{"one":"d8d872ef5b1632eabca51f1d2755fa5a3f16dc487d4d8be960930ee4462a2633","other":"ada8540c63062347d3162850cf2a4820a20ed96514fb01b95d995bbfff97a212","up":true},{"one":"f03f47bf8f6241cbda878f301783e30cac333e8cea3d6bf9bc2be3b7fc2cf1c3","other":"dbc536e11d371786bf8c61eec755d2cb22242c27e8edd7985dae612a9d2cac05","up":true},{"one":"c6c479f1c83f5158eb77d57e6321d5ed062964870775f51254461010c8882c10","other":"c0f0bec8a3506280df599d57abc414d007696967381b9690bcf63a093586307c","up":true},{"one":"b4bee8054b3b7bc3ef0748937602699173983a764814e85f173916fd70f1e3ed","other":"ada8540c63062347d3162850cf2a4820a20ed96514fb01b95d995bbfff97a212","up":true},{"one":"3c93b64143bf1bf130545806917472f3ca16ad6f223f0388f445ddc353140042","other":"2f4492429ab2ecd080a36451153990587de2458ebd9421fb989d8e27d4a88422","up":true},{"one":"c678643c5249eb90385aad201d6756f9f364024938d4c9f59f4aa51d64d48bd7","other":"0d48477e33d7cfc4a7660048e4b1ece9f37e9759e96e6f1f47a86de091b0ee97","up":true},{"one":"2d831f78c7d670e4e1f19a47c80ec2df144da210ff3aaa00ca6c4990e4da0922","other":"1be4ba2f068e060a0aabac445094f1dc8b22fe1e5575c98df8d8783aa3d95177","up":true},{"one":"23e4f18d302b6a545530b89eca007c141c751cdcde471bc3378bf72fd68b91c6","other":"3ff43d13b6ee84b128db4215c47c73909f6616039b87ca70abdd2108e29dddfb","up":true},{"one":"36c28c0237be6b2131280452c3113affcae28f6c7066811a8e1ee1a0a01decc3","other":"b4bee8054b3b7bc3ef0748937602699173983a764814e85f173916fd70f1e3ed","up":true},{"one":"1087495bf04e6967257f84b8e5de084e2000b9d748216e31c9f33fc9caedb715","other":"b4bee8054b3b7bc3ef0748937602699173983a764814e85f173916fd70f1e3ed","up":true},{"one":"43900b2a084097a0ddf8bf4d0375624f731bd14c85300007c87269986a12eb3b","other":"51db0d8fefeedfab3394f8f398b6d8f4540b24bc6cbd8e66bc5a7480db0228f5","up":true},{"one":"0d48477e33d7cfc4a7660048e4b1ece9f37e9759e96e6f1f47a86de091b0ee97","other":"b4bee8054b3b7bc3ef0748937602699173983a764814e85f173916fd70f1e3ed","up":true},{"one":"bfa0a02816da29b40215fb3c26de9360505e4c69fca55086324f0e35de7486a5","other":"9ecf217e2076c229f6d15bfe29df85aa129f1fb12d2a8307907327b5fe2f80c5","up":true},{"one":"2f4492429ab2ecd080a36451153990587de2458ebd9421fb989d8e27d4a88422","other":"544ad921e03de7d1d656038e23452dd3a9c7ef041520f17f8318326ecc58683b","up":true},{"one":"f066d5d314253a872ab27df90d85ba5824da06032f92ce8bf20020b61971e199","other":"ada8540c63062347d3162850cf2a4820a20ed96514fb01b95d995bbfff97a212","up":true},{"one":"29077dd2f4869b69ffc3ed40e82e4ef051292c3461888b5faf7b4c4b14d4928f","other":"3ff43d13b6ee84b128db4215c47c73909f6616039b87ca70abdd2108e29dddfb","up":true},{"one":"c0f0bec8a3506280df599d57abc414d007696967381b9690bcf63a093586307c","other":"df9ad5c2cd2375a5a227841841a47876765f6552a8b2cb9fa8cdedaeca2371ed","up":true},{"one":"a070c42951b523531f065cb8376eb8ca0c431b473ce9ebc44a3fc383f1f878b3","other":"be6906254ba2d3748ff8ce2ebbabaa03bad1c92d2eadc1ab6470548cb8d7c524","up":true},{"one":"332f7b60a1fbb6cd1e251dc68d1093b7f0bb05c1eefce5e61f124dff9445f14d","other":"0d48477e33d7cfc4a7660048e4b1ece9f37e9759e96e6f1f47a86de091b0ee97","up":true},{"one":"14645ab9b2ebbf4436466df07ff28e68b92774c6b5a0d311a174765e350c7089","other":"1abcab4e898966c42c11d1a92b1c1911fc779089c5a20ea1ddf005f76632b171","up":true},{"one":"ce614a2fd326ce57b0ead5750d0a08f3c7dd94e1d2935861697e3df4652a22b6","other":"d10e58907f84eef0dfcfd3c81457b60eef9b1ea5b9a462e22f0c4e643a6e7140","up":true},{"one":"358eef3cf9964bf89e056afe0f4b46ce85bcb079bab922a4ae9dfa555001abfc","other":"1abcab4e898966c42c11d1a92b1c1911fc779089c5a20ea1ddf005f76632b171","up":true},{"one":"5cdc4493f01d6ebec3070b028275bd94ede61db21a4f5d3b282a366d1d3484b8","other":"f03f47bf8f6241cbda878f301783e30cac333e8cea3d6bf9bc2be3b7fc2cf1c3","up":true},{"one":"68d45eec895b91f081026e86ed164092dad8d4087918cd50c6f77767d9577f6d","other":"6c4bdfe4650525d5efc1a4ca8d92ebbea33b1877bfac69656e81adbbad4f286a","up":true},{"one":"37debce0cbe19a2ab88f15dffa390df392938e7060970b93eae968a8c2497650","other":"14645ab9b2ebbf4436466df07ff28e68b92774c6b5a0d311a174765e350c7089","up":true},{"one":"cfa6cfc1f856e69430370d5c52130519502c7a9e4fb14c7c8d9b309612a3c1bb","other":"fbbe14109dc4fa8251e852a181f3663b4245c93b737fa9c4921a53ed9949019d","up":true},{"one":"de9eff7b7b80323f544746b9596e7da6b43b91e6c6d0660ad38df1e877ab8f99","other":"9dc2c4166fa9410d7be67cbb5ac572aa42a9a78c2efe5ea601ddd37c356196da","up":true},{"one":"36c28c0237be6b2131280452c3113affcae28f6c7066811a8e1ee1a0a01decc3","other":"2f4492429ab2ecd080a36451153990587de2458ebd9421fb989d8e27d4a88422","up":true},{"one":"e5eea2a0ac0175a0ad41239d8e36e537ce7d428d6b44e23e8e33ff0be11bac72","other":"e3c20117761e1b3c3d7c3d133e1bf68e594ee25950cd4311a8d964923c4164da","up":true},{"one":"77687d9e478a52555674f12aa654ad931a3ee83920b9cdc8fecc03653ed7e386","other":"5d056a38c3a7c3bfcd431b767662903454f41429d2e03b9b30273e5b1b8e5c97","up":true},{"one":"3ff43d13b6ee84b128db4215c47c73909f6616039b87ca70abdd2108e29dddfb","other":"332f7b60a1fbb6cd1e251dc68d1093b7f0bb05c1eefce5e61f124dff9445f14d","up":true},{"one":"bfa0a02816da29b40215fb3c26de9360505e4c69fca55086324f0e35de7486a5","other":"0d48477e33d7cfc4a7660048e4b1ece9f37e9759e96e6f1f47a86de091b0ee97","up":true},{"one":"30d80772ad22d78f8a4e948a06677b5676aadb8a9c040f3ffb59a7e608e0929e","other":"358eef3cf9964bf89e056afe0f4b46ce85bcb079bab922a4ae9dfa555001abfc","up":true},{"one":"68d45eec895b91f081026e86ed164092dad8d4087918cd50c6f77767d9577f6d","other":"61c24980b9ef4ee7fc33c51310f981a8e74e4276509a545a3f471370082dab4b","up":true},{"one":"37debce0cbe19a2ab88f15dffa390df392938e7060970b93eae968a8c2497650","other":"1be4ba2f068e060a0aabac445094f1dc8b22fe1e5575c98df8d8783aa3d95177","up":true},{"one":"51db0d8fefeedfab3394f8f398b6d8f4540b24bc6cbd8e66bc5a7480db0228f5","other":"44b1544b5795a1c77094832bae47c84eb38482b1e1aaefbaecacbc7ef9d10e3e","up":true},{"one":"cfa6cfc1f856e69430370d5c52130519502c7a9e4fb14c7c8d9b309612a3c1bb","other":"f066d5d314253a872ab27df90d85ba5824da06032f92ce8bf20020b61971e199","up":true},{"one":"ada8540c63062347d3162850cf2a4820a20ed96514fb01b95d995bbfff97a212","other":"bfa0a02816da29b40215fb3c26de9360505e4c69fca55086324f0e35de7486a5","up":true},{"one":"be9f880ccfd8dd0f5fde587c4e9fbdcb2d3551df0c4504d9938d4301a9276e72","other":"bfa0a02816da29b40215fb3c26de9360505e4c69fca55086324f0e35de7486a5","up":true},{"one":"ce614a2fd326ce57b0ead5750d0a08f3c7dd94e1d2935861697e3df4652a22b6","other":"c678643c5249eb90385aad201d6756f9f364024938d4c9f59f4aa51d64d48bd7","up":true},{"one":"5cdc4493f01d6ebec3070b028275bd94ede61db21a4f5d3b282a366d1d3484b8","other":"fbbe14109dc4fa8251e852a181f3663b4245c93b737fa9c4921a53ed9949019d","up":true},{"one":"dfe389ee7fe03c809d7a4afa1f04dde9ab9a50d8c6ca5bf2489debb2e8329541","other":"9dc2c4166fa9410d7be67cbb5ac572aa42a9a78c2efe5ea601ddd37c356196da","up":true},{"one":"3ea2554db02021f0413529426d7a7b0ac9abe80643993f6d80fdfff93a765c40","other":"1abcab4e898966c42c11d1a92b1c1911fc779089c5a20ea1ddf005f76632b171","up":true},{"one":"de9eff7b7b80323f544746b9596e7da6b43b91e6c6d0660ad38df1e877ab8f99","other":"fbbe14109dc4fa8251e852a181f3663b4245c93b737fa9c4921a53ed9949019d","up":true},{"one":"e5eea2a0ac0175a0ad41239d8e36e537ce7d428d6b44e23e8e33ff0be11bac72","other":"f0ee0274e24a7cc3d1b0d38cd46d205ccbf791d0cc038d022f2a0c50ae6a5cd5","up":true},{"one":"bfa0a02816da29b40215fb3c26de9360505e4c69fca55086324f0e35de7486a5","other":"1be4ba2f068e060a0aabac445094f1dc8b22fe1e5575c98df8d8783aa3d95177","up":true},{"one":"c678643c5249eb90385aad201d6756f9f364024938d4c9f59f4aa51d64d48bd7","other":"1be4ba2f068e060a0aabac445094f1dc8b22fe1e5575c98df8d8783aa3d95177","up":true},{"one":"c6c479f1c83f5158eb77d57e6321d5ed062964870775f51254461010c8882c10","other":"cfa6cfc1f856e69430370d5c52130519502c7a9e4fb14c7c8d9b309612a3c1bb","up":true},{"one":"36c28c0237be6b2131280452c3113affcae28f6c7066811a8e1ee1a0a01decc3","other":"2d831f78c7d670e4e1f19a47c80ec2df144da210ff3aaa00ca6c4990e4da0922","up":true},{"one":"43900b2a084097a0ddf8bf4d0375624f731bd14c85300007c87269986a12eb3b","other":"5d056a38c3a7c3bfcd431b767662903454f41429d2e03b9b30273e5b1b8e5c97","up":true},{"one":"ce614a2fd326ce57b0ead5750d0a08f3c7dd94e1d2935861697e3df4652a22b6","other":"c6c479f1c83f5158eb77d57e6321d5ed062964870775f51254461010c8882c10","up":true},{"one":"358eef3cf9964bf89e056afe0f4b46ce85bcb079bab922a4ae9dfa555001abfc","other":"23e4f18d302b6a545530b89eca007c141c751cdcde471bc3378bf72fd68b91c6","up":true},{"one":"332f7b60a1fbb6cd1e251dc68d1093b7f0bb05c1eefce5e61f124dff9445f14d","other":"14645ab9b2ebbf4436466df07ff28e68b92774c6b5a0d311a174765e350c7089","up":true},{"one":"dfe389ee7fe03c809d7a4afa1f04dde9ab9a50d8c6ca5bf2489debb2e8329541","other":"fbbe14109dc4fa8251e852a181f3663b4245c93b737fa9c4921a53ed9949019d","up":true},{"one":"7a56b4203ee1cad781b23d2a78694f63eb7dfe6d9ac24fc181e6a63516c9efa5","other":"7a1f03dc26ef21a5bd1673eb8418af7a7cbcb2ab1654882ebdcda2d1e6ad4836","up":true},{"one":"3c38aba43a15a61b680fa767353cce7e4165cbc996968f990001d7595ff243c8","other":"36c28c0237be6b2131280452c3113affcae28f6c7066811a8e1ee1a0a01decc3","up":true},{"one":"3ff43d13b6ee84b128db4215c47c73909f6616039b87ca70abdd2108e29dddfb","other":"3ea2554db02021f0413529426d7a7b0ac9abe80643993f6d80fdfff93a765c40","up":true},{"one":"c6c479f1c83f5158eb77d57e6321d5ed062964870775f51254461010c8882c10","other":"e3c20117761e1b3c3d7c3d133e1bf68e594ee25950cd4311a8d964923c4164da","up":true},{"one":"332f7b60a1fbb6cd1e251dc68d1093b7f0bb05c1eefce5e61f124dff9445f14d","other":"23e4f18d302b6a545530b89eca007c141c751cdcde471bc3378bf72fd68b91c6","up":true},{"one":"7a56b4203ee1cad781b23d2a78694f63eb7dfe6d9ac24fc181e6a63516c9efa5","other":"77687d9e478a52555674f12aa654ad931a3ee83920b9cdc8fecc03653ed7e386","up":true},{"one":"68d45eec895b91f081026e86ed164092dad8d4087918cd50c6f77767d9577f6d","other":"43900b2a084097a0ddf8bf4d0375624f731bd14c85300007c87269986a12eb3b","up":true},{"one":"3c38aba43a15a61b680fa767353cce7e4165cbc996968f990001d7595ff243c8","other":"23e4f18d302b6a545530b89eca007c141c751cdcde471bc3378bf72fd68b91c6","up":true},{"one":"cfa6cfc1f856e69430370d5c52130519502c7a9e4fb14c7c8d9b309612a3c1bb","other":"c678643c5249eb90385aad201d6756f9f364024938d4c9f59f4aa51d64d48bd7","up":true},{"one":"37debce0cbe19a2ab88f15dffa390df392938e7060970b93eae968a8c2497650","other":"2f4492429ab2ecd080a36451153990587de2458ebd9421fb989d8e27d4a88422","up":true},{"one":"c6c479f1c83f5158eb77d57e6321d5ed062964870775f51254461010c8882c10","other":"e5eea2a0ac0175a0ad41239d8e36e537ce7d428d6b44e23e8e33ff0be11bac72","up":true},{"one":"43900b2a084097a0ddf8bf4d0375624f731bd14c85300007c87269986a12eb3b","other":"5cdc4493f01d6ebec3070b028275bd94ede61db21a4f5d3b282a366d1d3484b8","up":true},{"one":"44b1544b5795a1c77094832bae47c84eb38482b1e1aaefbaecacbc7ef9d10e3e","other":"577b9ea47bc00cde25b11ca03861df0ccfaad8ce281500efede88d25e012b1e3","up":true},{"one":"7a56b4203ee1cad781b23d2a78694f63eb7dfe6d9ac24fc181e6a63516c9efa5","other":"0d48477e33d7cfc4a7660048e4b1ece9f37e9759e96e6f1f47a86de091b0ee97","up":true},{"one":"3c38aba43a15a61b680fa767353cce7e4165cbc996968f990001d7595ff243c8","other":"2d831f78c7d670e4e1f19a47c80ec2df144da210ff3aaa00ca6c4990e4da0922","up":true},{"one":"43900b2a084097a0ddf8bf4d0375624f731bd14c85300007c87269986a12eb3b","other":"5c3ca7f91551934bbfa4020433d7b19e40d04d9291c2b26686ef85225f872bbf","up":true},{"one":"43900b2a084097a0ddf8bf4d0375624f731bd14c85300007c87269986a12eb3b","other":"bfa0a02816da29b40215fb3c26de9360505e4c69fca55086324f0e35de7486a5","up":false},{"one":"7a1f03dc26ef21a5bd1673eb8418af7a7cbcb2ab1654882ebdcda2d1e6ad4836","other":"76933265c3d872b2d20e7a961f3f4e858afe11aee773b5caf678553e56ad3537","up":true}]} \ No newline at end of file diff --git a/swarm/network/stream/visualized_snapshot_sync_sim_test.go b/swarm/network/stream/visualized_snapshot_sync_sim_test.go deleted file mode 100644 index d0fda9dcb765..000000000000 --- a/swarm/network/stream/visualized_snapshot_sync_sim_test.go +++ /dev/null @@ -1,353 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build withserver - -package stream - -import ( - "bytes" - "context" - "errors" - "fmt" - "io" - "sync" - "testing" - "time" - - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/protocols" - "github.com/ubiq/go-ubiq/p2p/simulations" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" - "github.com/ubiq/go-ubiq/rlp" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/network/simulation" - "github.com/ubiq/go-ubiq/swarm/state" - "github.com/ubiq/go-ubiq/swarm/storage" -) - -/* -The tests in this file need to be executed with - - -tags=withserver - -Also, they will stall if executed stand-alone, because they wait -for the visualization frontend to send a POST /runsim message. -*/ - -//setup the sim, evaluate nodeCount and chunkCount and create the sim -func setupSim(serviceMap map[string]simulation.ServiceFunc) (int, int, *simulation.Simulation) { - nodeCount := *nodes - chunkCount := *chunks - - if nodeCount == 0 || chunkCount == 0 { - nodeCount = 32 - chunkCount = 1 - } - - //setup the simulation with server, which means the sim won't run - //until it receives a POST /runsim from the frontend - sim := simulation.New(serviceMap).WithServer(":8888") - return nodeCount, chunkCount, sim -} - -//This test requests bogus hashes into the network -func TestNonExistingHashesWithServer(t *testing.T) { - - nodeCount, _, sim := setupSim(retrievalSimServiceMap) - defer sim.Close() - - err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount)) - if err != nil { - panic(err) - } - - //in order to get some meaningful visualization, it is beneficial - //to define a minimum duration of this test - testDuration := 20 * time.Second - - result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) (err error) { - disconnected := watchDisconnections(ctx, sim) - defer func() { - if err != nil { - if yes, ok := disconnected.Load().(bool); ok && yes { - err = errors.New("disconnect events received") - } - } - }() - - //check on the node's FileStore (netstore) - id := sim.Net.GetRandomUpNode().ID() - item, ok := sim.NodeItem(id, bucketKeyFileStore) - if !ok { - return errors.New("No filestore") - } - fileStore := item.(*storage.FileStore) - //create a bogus hash - fakeHash := storage.GenerateRandomChunk(1000).Address() - //try to retrieve it - will propagate RetrieveRequestMsg into the network - reader, _ := fileStore.Retrieve(context.TODO(), fakeHash) - if _, err := reader.Size(ctx, nil); err != nil { - log.Debug("expected error for non-existing chunk") - } - //sleep so that the frontend can have something to display - time.Sleep(testDuration) - - return nil - }) - if result.Error != nil { - sendSimTerminatedEvent(sim) - t.Fatal(result.Error) - } - - sendSimTerminatedEvent(sim) - -} - -//send a termination event to the frontend -func sendSimTerminatedEvent(sim *simulation.Simulation) { - evt := &simulations.Event{ - Type: EventTypeSimTerminated, - Control: false, - } - sim.Net.Events().Send(evt) -} - -//This test is the same as the snapshot sync test, -//but with a HTTP server -//It also sends some custom events so that the frontend -//can visualize messages like SendOfferedMsg, WantedHashesMsg, DeliveryMsg -func TestSnapshotSyncWithServer(t *testing.T) { - //t.Skip("temporarily disabled as simulations.WaitTillHealthy cannot be trusted") - - //define a wrapper object to be able to pass around data - wrapper := &netWrapper{} - - nodeCount := *nodes - chunkCount := *chunks - - if nodeCount == 0 || chunkCount == 0 { - nodeCount = 32 - chunkCount = 1 - } - - log.Info(fmt.Sprintf("Running the simulation with %d nodes and %d chunks", nodeCount, chunkCount)) - - sim := simulation.New(map[string]simulation.ServiceFunc{ - "streamer": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) { - addr, netStore, delivery, clean, err := newNetStoreAndDeliveryWithRequestFunc(ctx, bucket, dummyRequestFromPeers) - if err != nil { - return nil, nil, err - } - - r := NewRegistry(addr.ID(), delivery, netStore, state.NewInmemoryStore(), &RegistryOptions{ - Retrieval: RetrievalDisabled, - Syncing: SyncingAutoSubscribe, - SyncUpdateDelay: 3 * time.Second, - }, nil) - - tr := &testRegistry{ - Registry: r, - w: wrapper, - } - - bucket.Store(bucketKeyRegistry, tr) - - cleanup = func() { - tr.Close() - clean() - } - - return tr, cleanup, nil - }, - }).WithServer(":8888") //start with the HTTP server - - nodeCount, chunkCount, sim := setupSim(simServiceMap) - defer sim.Close() - - log.Info("Initializing test config") - - conf := &synctestConfig{} - //map of discover ID to indexes of chunks expected at that ID - conf.idToChunksMap = make(map[enode.ID][]int) - //map of overlay address to discover ID - conf.addrToIDMap = make(map[string]enode.ID) - //array where the generated chunk hashes will be stored - conf.hashes = make([]storage.Address, 0) - //pass the network to the wrapper object - wrapper.setNetwork(sim.Net) - err := sim.UploadSnapshot(fmt.Sprintf("testing/snapshot_%d.json", nodeCount)) - if err != nil { - panic(err) - } - - //run the sim - result := runSim(conf, ctx, sim, chunkCount) - - //send terminated event - evt := &simulations.Event{ - Type: EventTypeSimTerminated, - Control: false, - } - go sim.Net.Events().Send(evt) - - if result.Error != nil { - panic(result.Error) - } - log.Info("Simulation ended") -} - -//testRegistry embeds registry -//it allows to replace the protocol run function -type testRegistry struct { - *Registry - w *netWrapper -} - -//Protocols replaces the protocol's run function -func (tr *testRegistry) Protocols() []p2p.Protocol { - regProto := tr.Registry.Protocols() - //set the `stream` protocol's run function with the testRegistry's one - regProto[0].Run = tr.runProto - return regProto -} - -//runProto is the new overwritten protocol's run function for this test -func (tr *testRegistry) runProto(p *p2p.Peer, rw p2p.MsgReadWriter) error { - //create a custom rw message ReadWriter - testRw := &testMsgReadWriter{ - MsgReadWriter: rw, - Peer: p, - w: tr.w, - Registry: tr.Registry, - } - //now run the actual upper layer `Registry`'s protocol function - return tr.runProtocol(p, testRw) -} - -//testMsgReadWriter is a custom rw -//it will allow us to re-use the message twice -type testMsgReadWriter struct { - *Registry - p2p.MsgReadWriter - *p2p.Peer - w *netWrapper -} - -//netWrapper wrapper object so we can pass data around -type netWrapper struct { - net *simulations.Network -} - -//set the network to the wrapper for later use (used inside the custom rw) -func (w *netWrapper) setNetwork(n *simulations.Network) { - w.net = n -} - -//get he network from the wrapper (used inside the custom rw) -func (w *netWrapper) getNetwork() *simulations.Network { - return w.net -} - -// ReadMsg reads a message from the underlying MsgReadWriter and emits a -// "message received" event -//we do this because we are interested in the Payload of the message for custom use -//in this test, but messages can only be consumed once (stream io.Reader) -func (ev *testMsgReadWriter) ReadMsg() (p2p.Msg, error) { - //read the message from the underlying rw - msg, err := ev.MsgReadWriter.ReadMsg() - if err != nil { - return msg, err - } - - //don't do anything with message codes we actually are not needing/reading - subCodes := []uint64{1, 2, 10} - found := false - for _, c := range subCodes { - if c == msg.Code { - found = true - } - } - //just return if not a msg code we are interested in - if !found { - return msg, nil - } - - //we use a io.TeeReader so that we can read the message twice - //the Payload is a io.Reader, so if we read from it, the actual protocol handler - //cannot access it anymore. - //But we need that handler to be able to consume the message as normal, - //as if we would not do anything here with that message - var buf bytes.Buffer - tee := io.TeeReader(msg.Payload, &buf) - - mcp := &p2p.Msg{ - Code: msg.Code, - Size: msg.Size, - ReceivedAt: msg.ReceivedAt, - Payload: tee, - } - //assign the copy for later use - msg.Payload = &buf - - //now let's look into the message - var wmsg protocols.WrappedMsg - err = mcp.Decode(&wmsg) - if err != nil { - log.Error(err.Error()) - return msg, err - } - //create a new message from the code - val, ok := ev.Registry.GetSpec().NewMsg(mcp.Code) - if !ok { - return msg, errors.New(fmt.Sprintf("Invalid message code: %v", msg.Code)) - } - //decode it - if err := rlp.DecodeBytes(wmsg.Payload, val); err != nil { - return msg, errors.New(fmt.Sprintf("Decoding error <= %v: %v", msg, err)) - } - //now for every message type we are interested in, create a custom event and send it - var evt *simulations.Event - switch val := val.(type) { - case *OfferedHashesMsg: - evt = &simulations.Event{ - Type: EventTypeChunkOffered, - Node: ev.w.getNetwork().GetNode(ev.ID()), - Control: false, - Data: val.Hashes, - } - case *WantedHashesMsg: - evt = &simulations.Event{ - Type: EventTypeChunkWanted, - Node: ev.w.getNetwork().GetNode(ev.ID()), - Control: false, - } - case *ChunkDeliveryMsgSyncing: - evt = &simulations.Event{ - Type: EventTypeChunkDelivered, - Node: ev.w.getNetwork().GetNode(ev.ID()), - Control: false, - Data: val.Addr.String(), - } - } - if evt != nil { - //send custom event to feed; frontend will listen to it and display - ev.w.getNetwork().Events().Send(evt) - } - return msg, nil -} diff --git a/swarm/network_test.go b/swarm/network_test.go deleted file mode 100644 index 1ffb5e9c71fe..000000000000 --- a/swarm/network_test.go +++ /dev/null @@ -1,502 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package swarm - -import ( - "context" - "flag" - "fmt" - "io/ioutil" - "math/rand" - "os" - "sync" - "sync/atomic" - "testing" - "time" - - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" - "github.com/ubiq/go-ubiq/swarm/api" - "github.com/ubiq/go-ubiq/swarm/network/simulation" - "github.com/ubiq/go-ubiq/swarm/storage" - colorable "github.com/mattn/go-colorable" -) - -var ( - loglevel = flag.Int("loglevel", 2, "verbosity of logs") - longrunning = flag.Bool("longrunning", false, "do run long-running tests") - waitKademlia = flag.Bool("waitkademlia", false, "wait for healthy kademlia before checking files availability") -) - -func init() { - rand.Seed(time.Now().UnixNano()) - - flag.Parse() - - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))) -} - -// TestSwarmNetwork runs a series of test simulations with -// static and dynamic Swarm nodes in network simulation, by -// uploading files to every node and retrieving them. -func TestSwarmNetwork(t *testing.T) { - for _, tc := range []struct { - name string - steps []testSwarmNetworkStep - options *testSwarmNetworkOptions - disabled bool - }{ - { - name: "10_nodes", - steps: []testSwarmNetworkStep{ - { - nodeCount: 10, - }, - }, - options: &testSwarmNetworkOptions{ - Timeout: 45 * time.Second, - }, - }, - { - name: "10_nodes_skip_check", - steps: []testSwarmNetworkStep{ - { - nodeCount: 10, - }, - }, - options: &testSwarmNetworkOptions{ - Timeout: 45 * time.Second, - SkipCheck: true, - }, - }, - { - name: "50_nodes", - steps: []testSwarmNetworkStep{ - { - nodeCount: 50, - }, - }, - options: &testSwarmNetworkOptions{ - Timeout: 3 * time.Minute, - }, - disabled: !*longrunning, - }, - { - name: "50_nodes_skip_check", - steps: []testSwarmNetworkStep{ - { - nodeCount: 50, - }, - }, - options: &testSwarmNetworkOptions{ - Timeout: 3 * time.Minute, - SkipCheck: true, - }, - disabled: !*longrunning, - }, - { - name: "inc_node_count", - steps: []testSwarmNetworkStep{ - { - nodeCount: 2, - }, - { - nodeCount: 5, - }, - { - nodeCount: 10, - }, - }, - options: &testSwarmNetworkOptions{ - Timeout: 90 * time.Second, - }, - disabled: !*longrunning, - }, - { - name: "dec_node_count", - steps: []testSwarmNetworkStep{ - { - nodeCount: 10, - }, - { - nodeCount: 6, - }, - { - nodeCount: 3, - }, - }, - options: &testSwarmNetworkOptions{ - Timeout: 90 * time.Second, - }, - disabled: !*longrunning, - }, - { - name: "dec_inc_node_count", - steps: []testSwarmNetworkStep{ - { - nodeCount: 3, - }, - { - nodeCount: 1, - }, - { - nodeCount: 5, - }, - }, - options: &testSwarmNetworkOptions{ - Timeout: 90 * time.Second, - }, - }, - { - name: "inc_dec_node_count", - steps: []testSwarmNetworkStep{ - { - nodeCount: 3, - }, - { - nodeCount: 5, - }, - { - nodeCount: 25, - }, - { - nodeCount: 10, - }, - { - nodeCount: 4, - }, - }, - options: &testSwarmNetworkOptions{ - Timeout: 5 * time.Minute, - }, - disabled: !*longrunning, - }, - { - name: "inc_dec_node_count_skip_check", - steps: []testSwarmNetworkStep{ - { - nodeCount: 3, - }, - { - nodeCount: 5, - }, - { - nodeCount: 25, - }, - { - nodeCount: 10, - }, - { - nodeCount: 4, - }, - }, - options: &testSwarmNetworkOptions{ - Timeout: 5 * time.Minute, - SkipCheck: true, - }, - disabled: !*longrunning, - }, - } { - if tc.disabled { - continue - } - t.Run(tc.name, func(t *testing.T) { - testSwarmNetwork(t, tc.options, tc.steps...) - }) - } -} - -// testSwarmNetworkStep is the configuration -// for the state of the simulation network. -type testSwarmNetworkStep struct { - // number of swarm nodes that must be in the Up state - nodeCount int -} - -// file represents the file uploaded on a particular node. -type file struct { - addr storage.Address - data string - nodeID enode.ID -} - -// check represents a reference to a file that is retrieved -// from a particular node. -type check struct { - key string - nodeID enode.ID -} - -// testSwarmNetworkOptions contains optional parameters for running -// testSwarmNetwork. -type testSwarmNetworkOptions struct { - Timeout time.Duration - SkipCheck bool -} - -// testSwarmNetwork is a helper function used for testing different -// static and dynamic Swarm network simulations. -// It is responsible for: -// - Setting up a Swarm network simulation, and updates the number of nodes within the network on every step according to steps. -// - Uploading a unique file to every node on every step. -// - May wait for Kademlia on every node to be healthy. -// - Checking if a file is retrievable from all nodes. -func testSwarmNetwork(t *testing.T, o *testSwarmNetworkOptions, steps ...testSwarmNetworkStep) { - - if o == nil { - o = new(testSwarmNetworkOptions) - } - - sim := simulation.New(map[string]simulation.ServiceFunc{ - "swarm": func(ctx *adapters.ServiceContext, bucket *sync.Map) (s node.Service, cleanup func(), err error) { - config := api.NewConfig() - - dir, err := ioutil.TempDir("", "swarm-network-test-node") - if err != nil { - return nil, nil, err - } - cleanup = func() { - err := os.RemoveAll(dir) - if err != nil { - log.Error("cleaning up swarm temp dir", "err", err) - } - } - - config.Path = dir - - privkey, err := crypto.GenerateKey() - if err != nil { - return nil, cleanup, err - } - - config.Init(privkey) - config.DeliverySkipCheck = o.SkipCheck - config.Port = "" - - swarm, err := NewSwarm(config, nil) - if err != nil { - return nil, cleanup, err - } - bucket.Store(simulation.BucketKeyKademlia, swarm.bzz.Hive.Kademlia) - log.Info("new swarm", "bzzKey", config.BzzKey, "baseAddr", fmt.Sprintf("%x", swarm.bzz.BaseAddr())) - return swarm, cleanup, nil - }, - }) - defer sim.Close() - - ctx := context.Background() - if o.Timeout > 0 { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, o.Timeout) - defer cancel() - } - - files := make([]file, 0) - - for i, step := range steps { - log.Debug("test sync step", "n", i+1, "nodes", step.nodeCount) - - change := step.nodeCount - len(sim.UpNodeIDs()) - - if change > 0 { - _, err := sim.AddNodesAndConnectChain(change) - if err != nil { - t.Fatal(err) - } - } else if change < 0 { - _, err := sim.StopRandomNodes(-change) - if err != nil { - t.Fatal(err) - } - } else { - t.Logf("step %v: no change in nodes", i) - continue - } - - var checkStatusM sync.Map - var nodeStatusM sync.Map - var totalFoundCount uint64 - - result := sim.Run(ctx, func(ctx context.Context, sim *simulation.Simulation) error { - nodeIDs := sim.UpNodeIDs() - rand.Shuffle(len(nodeIDs), func(i, j int) { - nodeIDs[i], nodeIDs[j] = nodeIDs[j], nodeIDs[i] - }) - for _, id := range nodeIDs { - key, data, err := uploadFile(sim.Service("swarm", id).(*Swarm)) - if err != nil { - return err - } - log.Trace("file uploaded", "node", id, "key", key.String()) - files = append(files, file{ - addr: key, - data: data, - nodeID: id, - }) - } - - if *waitKademlia { - if _, err := sim.WaitTillHealthy(ctx); err != nil { - return err - } - } - - // File retrieval check is repeated until all uploaded files are retrieved from all nodes - // or until the timeout is reached. - for { - if retrieve(sim, files, &checkStatusM, &nodeStatusM, &totalFoundCount) == 0 { - return nil - } - } - }) - - if result.Error != nil { - t.Fatal(result.Error) - } - log.Debug("done: test sync step", "n", i+1, "nodes", step.nodeCount) - } -} - -// uploadFile, uploads a short file to the swarm instance -// using the api.Put method. -func uploadFile(swarm *Swarm) (storage.Address, string, error) { - b := make([]byte, 8) - _, err := rand.Read(b) - if err != nil { - return nil, "", err - } - // File data is very short, but it is ensured that its - // uniqueness is very certain. - data := fmt.Sprintf("test content %s %x", time.Now().Round(0), b) - ctx := context.TODO() - k, wait, err := swarm.api.Put(ctx, data, "text/plain", false) - if err != nil { - return nil, "", err - } - if wait != nil { - err = wait(ctx) - } - return k, data, err -} - -// retrieve is the function that is used for checking the availability of -// uploaded files in testSwarmNetwork test helper function. -func retrieve( - sim *simulation.Simulation, - files []file, - checkStatusM *sync.Map, - nodeStatusM *sync.Map, - totalFoundCount *uint64, -) (missing uint64) { - rand.Shuffle(len(files), func(i, j int) { - files[i], files[j] = files[j], files[i] - }) - - var totalWg sync.WaitGroup - errc := make(chan error) - - nodeIDs := sim.UpNodeIDs() - - totalCheckCount := len(nodeIDs) * len(files) - - for _, id := range nodeIDs { - if _, ok := nodeStatusM.Load(id); ok { - continue - } - start := time.Now() - var checkCount uint64 - var foundCount uint64 - - totalWg.Add(1) - - var wg sync.WaitGroup - - swarm := sim.Service("swarm", id).(*Swarm) - for _, f := range files { - - checkKey := check{ - key: f.addr.String(), - nodeID: id, - } - if n, ok := checkStatusM.Load(checkKey); ok && n.(int) == 0 { - continue - } - - checkCount++ - wg.Add(1) - go func(f file, id enode.ID) { - defer wg.Done() - - log.Debug("api get: check file", "node", id.String(), "key", f.addr.String(), "total files found", atomic.LoadUint64(totalFoundCount)) - - r, _, _, _, err := swarm.api.Get(context.TODO(), api.NOOPDecrypt, f.addr, "/") - if err != nil { - errc <- fmt.Errorf("api get: node %s, key %s, kademlia %s: %v", id, f.addr, swarm.bzz.Hive, err) - return - } - d, err := ioutil.ReadAll(r) - if err != nil { - errc <- fmt.Errorf("api get: read response: node %s, key %s: kademlia %s: %v", id, f.addr, swarm.bzz.Hive, err) - return - } - data := string(d) - if data != f.data { - errc <- fmt.Errorf("file contend missmatch: node %s, key %s, expected %q, got %q", id, f.addr, f.data, data) - return - } - checkStatusM.Store(checkKey, 0) - atomic.AddUint64(&foundCount, 1) - log.Info("api get: file found", "node", id.String(), "key", f.addr.String(), "content", data, "files found", atomic.LoadUint64(&foundCount)) - }(f, id) - } - - go func(id enode.ID) { - defer totalWg.Done() - wg.Wait() - - atomic.AddUint64(totalFoundCount, foundCount) - - if foundCount == checkCount { - log.Info("all files are found for node", "id", id.String(), "duration", time.Since(start)) - nodeStatusM.Store(id, 0) - return - } - log.Debug("files missing for node", "id", id.String(), "check", checkCount, "found", foundCount) - }(id) - - } - - go func() { - totalWg.Wait() - close(errc) - }() - - var errCount int - for err := range errc { - if err != nil { - errCount++ - } - log.Warn(err.Error()) - } - - log.Info("check stats", "total check count", totalCheckCount, "total files found", atomic.LoadUint64(totalFoundCount), "total errors", errCount) - - return uint64(totalCheckCount) - atomic.LoadUint64(totalFoundCount) -} diff --git a/swarm/pot/address.go b/swarm/pot/address.go deleted file mode 100644 index 276000306530..000000000000 --- a/swarm/pot/address.go +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Package pot see doc.go -package pot - -import ( - "encoding/binary" - "fmt" - "math/rand" - "strconv" - "strings" - - "github.com/ubiq/go-ubiq/common" -) - -var ( - zerosBin = Address{}.Bin() -) - -// Address is an alias for common.Hash -type Address common.Hash - -// NewAddressFromBytes constructs an Address from a byte slice -func NewAddressFromBytes(b []byte) Address { - h := common.Hash{} - copy(h[:], b) - return Address(h) -} - -func (a Address) String() string { - return fmt.Sprintf("%x", a[:]) -} - -// MarshalJSON Address serialisation -func (a *Address) MarshalJSON() (out []byte, err error) { - return []byte(`"` + a.String() + `"`), nil -} - -// UnmarshalJSON Address deserialisation -func (a *Address) UnmarshalJSON(value []byte) error { - *a = Address(common.HexToHash(string(value[1 : len(value)-1]))) - return nil -} - -// Bin returns the string form of the binary representation of an address (only first 8 bits) -func (a Address) Bin() string { - return ToBin(a[:]) -} - -// ToBin converts a byteslice to the string binary representation -func ToBin(a []byte) string { - var bs []string - for _, b := range a { - bs = append(bs, fmt.Sprintf("%08b", b)) - } - return strings.Join(bs, "") -} - -// Bytes returns the Address as a byte slice -func (a Address) Bytes() []byte { - return a[:] -} - -// ProxCmp compares the distances a->target and b->target. -// Returns -1 if a is closer to target, 1 if b is closer to target -// and 0 if they are equal. -func ProxCmp(a, x, y interface{}) int { - return proxCmp(ToBytes(a), ToBytes(x), ToBytes(y)) -} - -func proxCmp(a, x, y []byte) int { - for i := range a { - dx := x[i] ^ a[i] - dy := y[i] ^ a[i] - if dx > dy { - return 1 - } else if dx < dy { - return -1 - } - } - return 0 -} - -// RandomAddressAt (address, prox) generates a random address -// at proximity order prox relative to address -// if prox is negative a random address is generated -func RandomAddressAt(self Address, prox int) (addr Address) { - addr = self - pos := -1 - if prox >= 0 { - pos = prox / 8 - trans := prox % 8 - transbytea := byte(0) - for j := 0; j <= trans; j++ { - transbytea |= 1 << uint8(7-j) - } - flipbyte := byte(1 << uint8(7-trans)) - transbyteb := transbytea ^ byte(255) - randbyte := byte(rand.Intn(255)) - addr[pos] = ((addr[pos] & transbytea) ^ flipbyte) | randbyte&transbyteb - } - for i := pos + 1; i < len(addr); i++ { - addr[i] = byte(rand.Intn(255)) - } - - return -} - -// RandomAddress generates a random address -func RandomAddress() Address { - return RandomAddressAt(Address{}, -1) -} - -// NewAddressFromString creates a byte slice from a string in binary representation -func NewAddressFromString(s string) []byte { - ha := [32]byte{} - - t := s + zerosBin[:len(zerosBin)-len(s)] - for i := 0; i < 4; i++ { - n, err := strconv.ParseUint(t[i*64:(i+1)*64], 2, 64) - if err != nil { - panic("wrong format: " + err.Error()) - } - binary.BigEndian.PutUint64(ha[i*8:(i+1)*8], n) - } - return ha[:] -} - -// BytesAddress is an interface for elements addressable by a byte slice -type BytesAddress interface { - Address() []byte -} - -// ToBytes turns the Val into bytes -func ToBytes(v Val) []byte { - if v == nil { - return nil - } - b, ok := v.([]byte) - if !ok { - ba, ok := v.(BytesAddress) - if !ok { - panic(fmt.Sprintf("unsupported value type %T", v)) - } - b = ba.Address() - } - return b -} - -// DefaultPof returns a proximity order comparison operator function -func DefaultPof(max int) func(one, other Val, pos int) (int, bool) { - return func(one, other Val, pos int) (int, bool) { - po, eq := proximityOrder(ToBytes(one), ToBytes(other), pos) - if po >= max { - eq = true - po = max - } - return po, eq - } -} - -// proximityOrder returns two parameters: -// 1. relative proximity order of the arguments one & other; -// 2. boolean indicating whether the full match occurred (one == other). -func proximityOrder(one, other []byte, pos int) (int, bool) { - for i := pos / 8; i < len(one); i++ { - if one[i] == other[i] { - continue - } - oxo := one[i] ^ other[i] - start := 0 - if i == pos/8 { - start = pos % 8 - } - for j := start; j < 8; j++ { - if (oxo>>uint8(7-j))&0x01 != 0 { - return i*8 + j, false - } - } - } - return len(one) * 8, true -} - -// Label displays the node's key in binary format -func Label(v Val) string { - if v == nil { - return "" - } - if s, ok := v.(fmt.Stringer); ok { - return s.String() - } - if b, ok := v.([]byte); ok { - return ToBin(b) - } - panic(fmt.Sprintf("unsupported value type %T", v)) -} diff --git a/swarm/pot/doc.go b/swarm/pot/doc.go deleted file mode 100644 index cb6faea57b3f..000000000000 --- a/swarm/pot/doc.go +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -/* -Package pot (proximity order tree) implements a container similar to a binary tree. -The elements are generic Val interface types. - -Each fork in the trie is itself a value. Values of the subtree contained under -a node all share the same order when compared to other elements in the tree. - -Example of proximity order is the length of the common prefix over bitvectors. -(which is equivalent to the reverse rank of order of magnitude of the MSB first X -OR distance over finite set of integers). - -Methods take a comparison operator (pof, proximity order function) to compare two -value types. The default pof assumes Val to be or project to a byte slice using -the reverse rank on the MSB first XOR logarithmic distance. - -If the address space if limited, equality is defined as the maximum proximity order. - -The container offers applicative (functional) style methods on PO trees: -* adding/removing en element -* swap (value based add/remove) -* merging two PO trees (union) - -as well as iterator accessors that respect proximity order - -When synchronicity of membership if not 100% requirement (e.g. used as a database -of network connections), applicative structures have the advantage that nodes -are immutable therefore manipulation does not need locking allowing for -concurrent retrievals. -For the use case where the entire container is supposed to allow changes by -concurrent routines, - -Pot -* retrieval, insertion and deletion by key involves log(n) pointer lookups -* for any item retrieval (defined as common prefix on the binary key) -* provide synchronous iterators respecting proximity ordering wrt any item -* provide asynchronous iterator (for parallel execution of operations) over n items -* allows cheap iteration over ranges -* asymmetric concurrent merge (union) - -Note: -* as is, union only makes sense for set representations since which of two values -with equal keys survives is random -* intersection is not implemented -* simple get accessor is not implemented (but derivable from EachNeighbour) - -Pinned value on the node implies no need to copy keys of the item type. - -Note that -* the same set of values allows for a large number of alternative -POT representations. -* values on the top are accessed faster than lower ones and the steps needed to -retrieve items has a logarithmic distribution. - -As a consequence one can organise the tree so that items that need faster access -are torwards the top. In particular for any subset where popularity has a power -distriution that is independent of proximity order (content addressed storage of -chunks), it is in principle possible to create a pot where the steps needed to -access an item is inversely proportional to its popularity. -Such organisation is not implemented as yet. - -TODO: -* overwrite-style merge -* intersection -* access frequency based optimisations - -*/ -package pot diff --git a/swarm/pot/pot.go b/swarm/pot/pot.go deleted file mode 100644 index 7e3967f3f974..000000000000 --- a/swarm/pot/pot.go +++ /dev/null @@ -1,787 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Package pot see doc.go -package pot - -import ( - "fmt" - "sync" -) - -const ( - maxkeylen = 256 -) - -// Pot is the node type (same for root, branching node and leaf) -type Pot struct { - pin Val - bins []*Pot - size int - po int -} - -// Val is the element type for Pots -type Val interface{} - -// Pof is the proximity order comparison operator function -type Pof func(Val, Val, int) (int, bool) - -// NewPot constructor. Requires a value of type Val to pin -// and po to point to a span in the Val key -// The pinned item counts towards the size -func NewPot(v Val, po int) *Pot { - var size int - if v != nil { - size++ - } - return &Pot{ - pin: v, - po: po, - size: size, - } -} - -// Pin returns the pinned element (key) of the Pot -func (t *Pot) Pin() Val { - return t.pin -} - -// Size returns the number of values in the Pot -func (t *Pot) Size() int { - if t == nil { - return 0 - } - return t.size -} - -// Add inserts a new value into the Pot and -// returns the proximity order of v and a boolean -// indicating if the item was found -// Add called on (t, v) returns a new Pot that contains all the elements of t -// plus the value v, using the applicative add -// the second return value is the proximity order of the inserted element -// the third is boolean indicating if the item was found -func Add(t *Pot, val Val, pof Pof) (*Pot, int, bool) { - return add(t, val, pof) -} - -func (t *Pot) clone() *Pot { - return &Pot{ - pin: t.pin, - size: t.size, - po: t.po, - bins: t.bins, - } -} - -func add(t *Pot, val Val, pof Pof) (*Pot, int, bool) { - var r *Pot - if t == nil || t.pin == nil { - r = t.clone() - r.pin = val - r.size++ - return r, 0, false - } - po, found := pof(t.pin, val, t.po) - if found { - r = t.clone() - r.pin = val - return r, po, true - } - - var p *Pot - var i, j int - size := t.size - for i < len(t.bins) { - n := t.bins[i] - if n.po == po { - p, _, found = add(n, val, pof) - if !found { - size++ - } - j++ - break - } - if n.po > po { - break - } - i++ - j++ - } - if p == nil { - size++ - p = &Pot{ - pin: val, - size: 1, - po: po, - } - } - - bins := append([]*Pot{}, t.bins[:i]...) - bins = append(bins, p) - bins = append(bins, t.bins[j:]...) - r = &Pot{ - pin: t.pin, - size: size, - po: t.po, - bins: bins, - } - - return r, po, found -} - -// Remove deletes element v from the Pot t and returns three parameters: -// 1. new Pot that contains all the elements of t minus the element v; -// 2. proximity order of the removed element v; -// 3. boolean indicating whether the item was found. -func Remove(t *Pot, v Val, pof Pof) (*Pot, int, bool) { - return remove(t, v, pof) -} - -func remove(t *Pot, val Val, pof Pof) (r *Pot, po int, found bool) { - size := t.size - po, found = pof(t.pin, val, t.po) - if found { - size-- - if size == 0 { - return &Pot{}, po, true - } - i := len(t.bins) - 1 - last := t.bins[i] - r = &Pot{ - pin: last.pin, - bins: append(t.bins[:i], last.bins...), - size: size, - po: t.po, - } - return r, t.po, true - } - - var p *Pot - var i, j int - for i < len(t.bins) { - n := t.bins[i] - if n.po == po { - p, po, found = remove(n, val, pof) - if found { - size-- - } - j++ - break - } - if n.po > po { - return t, po, false - } - i++ - j++ - } - bins := t.bins[:i] - if p != nil && p.pin != nil { - bins = append(bins, p) - } - bins = append(bins, t.bins[j:]...) - r = &Pot{ - pin: t.pin, - size: size, - po: t.po, - bins: bins, - } - return r, po, found -} - -// Swap called on (k, f) looks up the item at k -// and applies the function f to the value v at k or to nil if the item is not found -// if f(v) returns nil, the element is removed -// if f(v) returns v' <> v then v' is inserted into the Pot -// if (v) == v the Pot is not changed -// it panics if Pof(f(v), k) show that v' and v are not key-equal -func Swap(t *Pot, k Val, pof Pof, f func(v Val) Val) (r *Pot, po int, found bool, change bool) { - var val Val - if t.pin == nil { - val = f(nil) - if val == nil { - return nil, 0, false, false - } - return NewPot(val, t.po), 0, false, true - } - size := t.size - po, found = pof(k, t.pin, t.po) - if found { - val = f(t.pin) - // remove element - if val == nil { - size-- - if size == 0 { - r = &Pot{ - po: t.po, - } - // return empty pot - return r, po, true, true - } - // actually remove pin, by merging last bin - i := len(t.bins) - 1 - last := t.bins[i] - r = &Pot{ - pin: last.pin, - bins: append(t.bins[:i], last.bins...), - size: size, - po: t.po, - } - return r, po, true, true - } - // element found but no change - if val == t.pin { - return t, po, true, false - } - // actually modify the pinned element, but no change in structure - r = t.clone() - r.pin = val - return r, po, true, true - } - - // recursive step - var p *Pot - n, i := t.getPos(po) - if n != nil { - p, po, found, change = Swap(n, k, pof, f) - // recursive no change - if !change { - return t, po, found, false - } - // recursive change - bins := append([]*Pot{}, t.bins[:i]...) - if p.size == 0 { - size-- - } else { - size += p.size - n.size - bins = append(bins, p) - } - i++ - if i < len(t.bins) { - bins = append(bins, t.bins[i:]...) - } - r = t.clone() - r.bins = bins - r.size = size - return r, po, found, true - } - // key does not exist - val = f(nil) - if val == nil { - // and it should not be created - return t, po, false, false - } - // otherwise check val if equal to k - if _, eq := pof(val, k, po); !eq { - panic("invalid value") - } - /// - size++ - p = &Pot{ - pin: val, - size: 1, - po: po, - } - - bins := append([]*Pot{}, t.bins[:i]...) - bins = append(bins, p) - if i < len(t.bins) { - bins = append(bins, t.bins[i:]...) - } - r = t.clone() - r.bins = bins - r.size = size - return r, po, found, true -} - -// Union called on (t0, t1, pof) returns the union of t0 and t1 -// calculates the union using the applicative union -// the second return value is the number of common elements -func Union(t0, t1 *Pot, pof Pof) (*Pot, int) { - return union(t0, t1, pof) -} - -func union(t0, t1 *Pot, pof Pof) (*Pot, int) { - if t0 == nil || t0.size == 0 { - return t1, 0 - } - if t1 == nil || t1.size == 0 { - return t0, 0 - } - var pin Val - var bins []*Pot - var mis []int - wg := &sync.WaitGroup{} - wg.Add(1) - pin0 := t0.pin - pin1 := t1.pin - bins0 := t0.bins - bins1 := t1.bins - var i0, i1 int - var common int - - po, eq := pof(pin0, pin1, 0) - - for { - l0 := len(bins0) - l1 := len(bins1) - var n0, n1 *Pot - var p0, p1 int - var a0, a1 bool - - for { - - if !a0 && i0 < l0 && bins0[i0] != nil && bins0[i0].po <= po { - n0 = bins0[i0] - p0 = n0.po - a0 = p0 == po - } else { - a0 = true - } - - if !a1 && i1 < l1 && bins1[i1] != nil && bins1[i1].po <= po { - n1 = bins1[i1] - p1 = n1.po - a1 = p1 == po - } else { - a1 = true - } - if a0 && a1 { - break - } - - switch { - case (p0 < p1 || a1) && !a0: - bins = append(bins, n0) - i0++ - n0 = nil - case (p1 < p0 || a0) && !a1: - bins = append(bins, n1) - i1++ - n1 = nil - case p1 < po: - bl := len(bins) - bins = append(bins, nil) - ml := len(mis) - mis = append(mis, 0) - // wg.Add(1) - // go func(b, m int, m0, m1 *Pot) { - // defer wg.Done() - // bins[b], mis[m] = union(m0, m1, pof) - // }(bl, ml, n0, n1) - bins[bl], mis[ml] = union(n0, n1, pof) - i0++ - i1++ - n0 = nil - n1 = nil - } - } - - if eq { - common++ - pin = pin1 - break - } - - i := i0 - if len(bins0) > i && bins0[i].po == po { - i++ - } - var size0 int - for _, n := range bins0[i:] { - size0 += n.size - } - np := &Pot{ - pin: pin0, - bins: bins0[i:], - size: size0 + 1, - po: po, - } - - bins2 := []*Pot{np} - if n0 == nil { - pin0 = pin1 - po = maxkeylen + 1 - eq = true - common-- - - } else { - bins2 = append(bins2, n0.bins...) - pin0 = pin1 - pin1 = n0.pin - po, eq = pof(pin0, pin1, n0.po) - - } - bins0 = bins1 - bins1 = bins2 - i0 = i1 - i1 = 0 - - } - - wg.Done() - wg.Wait() - for _, c := range mis { - common += c - } - n := &Pot{ - pin: pin, - bins: bins, - size: t0.size + t1.size - common, - po: t0.po, - } - return n, common -} - -// Each is a synchronous iterator over the elements of pot with function f. -func (t *Pot) Each(f func(Val) bool) bool { - return t.each(f) -} - -// each is a synchronous iterator over the elements of pot with function f. -// the iteration ends if the function return false or there are no more elements. -func (t *Pot) each(f func(Val) bool) bool { - if t == nil || t.size == 0 { - return false - } - for _, n := range t.bins { - if !n.each(f) { - return false - } - } - return f(t.pin) -} - -// eachFrom is a synchronous iterator over the elements of pot with function f, -// starting from certain proximity order po, which is passed as a second parameter. -// the iteration ends if the function return false or there are no more elements. -func (t *Pot) eachFrom(f func(Val) bool, po int) bool { - if t == nil || t.size == 0 { - return false - } - _, beg := t.getPos(po) - for i := beg; i < len(t.bins); i++ { - if !t.bins[i].each(f) { - return false - } - } - return f(t.pin) -} - -// EachBin iterates over bins of the pivot node and offers iterators to the caller on each -// subtree passing the proximity order and the size -// the iteration continues until the function's return value is false -// or there are no more subtries -func (t *Pot) EachBin(val Val, pof Pof, po int, f func(int, int, func(func(val Val) bool) bool) bool) { - t.eachBin(val, pof, po, f) -} - -func (t *Pot) eachBin(val Val, pof Pof, po int, f func(int, int, func(func(val Val) bool) bool) bool) { - if t == nil || t.size == 0 { - return - } - spr, _ := pof(t.pin, val, t.po) - _, lim := t.getPos(spr) - var size int - var n *Pot - for i := 0; i < lim; i++ { - n = t.bins[i] - size += n.size - if n.po < po { - continue - } - if !f(n.po, n.size, n.each) { - return - } - } - if lim == len(t.bins) { - if spr >= po { - f(spr, 1, func(g func(Val) bool) bool { - return g(t.pin) - }) - } - return - } - - n = t.bins[lim] - - spo := spr - if n.po == spr { - spo++ - size += n.size - } - if spr >= po { - if !f(spr, t.size-size, func(g func(Val) bool) bool { - return t.eachFrom(func(v Val) bool { - return g(v) - }, spo) - }) { - return - } - } - if n.po == spr { - n.eachBin(val, pof, po, f) - } - -} - -// EachNeighbour is a synchronous iterator over neighbours of any target val -// the order of elements retrieved reflect proximity order to the target -// TODO: add maximum proxbin to start range of iteration -func (t *Pot) EachNeighbour(val Val, pof Pof, f func(Val, int) bool) bool { - return t.eachNeighbour(val, pof, f) -} - -func (t *Pot) eachNeighbour(val Val, pof Pof, f func(Val, int) bool) bool { - if t == nil || t.size == 0 { - return false - } - var next bool - l := len(t.bins) - var n *Pot - ir := l - il := l - po, eq := pof(t.pin, val, t.po) - if !eq { - n, il = t.getPos(po) - if n != nil { - next = n.eachNeighbour(val, pof, f) - if !next { - return false - } - ir = il - } else { - ir = il - 1 - } - } - - next = f(t.pin, po) - if !next { - return false - } - - for i := l - 1; i > ir; i-- { - next = t.bins[i].each(func(v Val) bool { - return f(v, po) - }) - if !next { - return false - } - } - - for i := il - 1; i >= 0; i-- { - n := t.bins[i] - next = n.each(func(v Val) bool { - return f(v, n.po) - }) - if !next { - return false - } - } - return true -} - -// EachNeighbourAsync called on (val, max, maxPos, f, wait) is an asynchronous iterator -// over elements not closer than maxPos wrt val. -// val does not need to be match an element of the Pot, but if it does, and -// maxPos is keylength than it is included in the iteration -// Calls to f are parallelised, the order of calls is undefined. -// proximity order is respected in that there is no element in the Pot that -// is not visited if a closer node is visited. -// The iteration is finished when max number of nearest nodes is visited -// or if the entire there are no nodes not closer than maxPos that is not visited -// if wait is true, the iterator returns only if all calls to f are finished -// TODO: implement minPos for proper prox range iteration -func (t *Pot) EachNeighbourAsync(val Val, pof Pof, max int, maxPos int, f func(Val, int), wait bool) { - if max > t.size { - max = t.size - } - var wg *sync.WaitGroup - if wait { - wg = &sync.WaitGroup{} - } - t.eachNeighbourAsync(val, pof, max, maxPos, f, wg) - if wait { - wg.Wait() - } -} - -func (t *Pot) eachNeighbourAsync(val Val, pof Pof, max int, maxPos int, f func(Val, int), wg *sync.WaitGroup) (extra int) { - l := len(t.bins) - - po, eq := pof(t.pin, val, t.po) - - // if po is too close, set the pivot branch (pom) to maxPos - pom := po - if pom > maxPos { - pom = maxPos - } - n, il := t.getPos(pom) - ir := il - // if pivot branch exists and po is not too close, iterate on the pivot branch - if pom == po { - if n != nil { - - m := n.size - if max < m { - m = max - } - max -= m - - extra = n.eachNeighbourAsync(val, pof, m, maxPos, f, wg) - - } else { - if !eq { - ir-- - } - } - } else { - extra++ - max-- - if n != nil { - il++ - } - // before checking max, add up the extra elements - // on the close branches that are skipped (if po is too close) - for i := l - 1; i >= il; i-- { - s := t.bins[i] - m := s.size - if max < m { - m = max - } - max -= m - extra += m - } - } - - var m int - if pom == po { - - m, max, extra = need(1, max, extra) - if m <= 0 { - return - } - - if wg != nil { - wg.Add(1) - } - go func() { - if wg != nil { - defer wg.Done() - } - f(t.pin, po) - }() - - // otherwise iterats - for i := l - 1; i > ir; i-- { - n := t.bins[i] - - m, max, extra = need(n.size, max, extra) - if m <= 0 { - return - } - - if wg != nil { - wg.Add(m) - } - go func(pn *Pot, pm int) { - pn.each(func(v Val) bool { - if wg != nil { - defer wg.Done() - } - f(v, po) - pm-- - return pm > 0 - }) - }(n, m) - - } - } - - // iterate branches that are farther tham pom with their own po - for i := il - 1; i >= 0; i-- { - n := t.bins[i] - // the first time max is less than the size of the entire branch - // wait for the pivot thread to release extra elements - m, max, extra = need(n.size, max, extra) - if m <= 0 { - return - } - - if wg != nil { - wg.Add(m) - } - go func(pn *Pot, pm int) { - pn.each(func(v Val) bool { - if wg != nil { - defer wg.Done() - } - f(v, pn.po) - pm-- - return pm > 0 - }) - }(n, m) - - } - return max + extra -} - -// getPos called on (n) returns the forking node at PO n and its index if it exists -// otherwise nil -// caller is supposed to hold the lock -func (t *Pot) getPos(po int) (n *Pot, i int) { - for i, n = range t.bins { - if po > n.po { - continue - } - if po < n.po { - return nil, i - } - return n, i - } - return nil, len(t.bins) -} - -// need called on (m, max, extra) uses max m out of extra, and then max -// if needed, returns the adjusted counts -func need(m, max, extra int) (int, int, int) { - if m <= extra { - return m, max, extra - m - } - max += extra - m - if max <= 0 { - return m + max, 0, 0 - } - return m, max, 0 -} - -func (t *Pot) String() string { - return t.sstring("") -} - -func (t *Pot) sstring(indent string) string { - if t == nil { - return "" - } - var s string - indent += " " - s += fmt.Sprintf("%v%v (%v) %v \n", indent, t.pin, t.po, t.size) - for _, n := range t.bins { - s += fmt.Sprintf("%v%v\n", indent, n.sstring(indent)) - } - return s -} diff --git a/swarm/pot/pot_test.go b/swarm/pot/pot_test.go deleted file mode 100644 index a8cdd2da0353..000000000000 --- a/swarm/pot/pot_test.go +++ /dev/null @@ -1,741 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . -package pot - -import ( - "errors" - "fmt" - "math/rand" - "runtime" - "sync" - "testing" - "time" - - "github.com/ubiq/go-ubiq/swarm/log" -) - -const ( - maxEachNeighbourTests = 420 - maxEachNeighbour = 420 - maxSwap = 420 - maxSwapTests = 420 -) - -// func init() { -// log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(false)))) -// } - -type testAddr struct { - a []byte - i int -} - -func newTestAddr(s string, i int) *testAddr { - return &testAddr{NewAddressFromString(s), i} -} - -func (a *testAddr) Address() []byte { - return a.a -} - -func (a *testAddr) String() string { - return Label(a.a) -} - -func randomTestAddr(n int, i int) *testAddr { - v := RandomAddress().Bin()[:n] - return newTestAddr(v, i) -} - -func randomtestAddr(n int, i int) *testAddr { - v := RandomAddress().Bin()[:n] - return newTestAddr(v, i) -} - -func indexes(t *Pot) (i []int) { - t.Each(func(v Val) bool { - a := v.(*testAddr) - i = append(i, a.i) - return true - }) - return i -} - -func testAdd(t *Pot, pof Pof, j int, values ...string) (_ *Pot, n int, f bool) { - for i, val := range values { - t, n, f = Add(t, newTestAddr(val, i+j), pof) - } - return t, n, f -} - -// removing non-existing element from pot -func TestPotRemoveNonExisting(t *testing.T) { - pof := DefaultPof(8) - n := NewPot(newTestAddr("00111100", 0), 0) - n, _, _ = Remove(n, newTestAddr("00000101", 0), pof) - exp := "00111100" - got := Label(n.Pin()) - if got[:8] != exp { - t.Fatalf("incorrect pinned value. Expected %v, got %v", exp, got[:8]) - } -} - -// this test creates hierarchical pot tree, and therefore any child node will have -// child_po = parent_po + 1. -// then removes a node from the middle of the tree. -func TestPotRemoveSameBin(t *testing.T) { - pof := DefaultPof(8) - n := NewPot(newTestAddr("11111111", 0), 0) - n, _, _ = testAdd(n, pof, 1, "00000000", "01000000", "01100000", "01110000", "01111000") - n, _, _ = Remove(n, newTestAddr("01110000", 0), pof) - inds := indexes(n) - goti := n.Size() - expi := 5 - if goti != expi { - t.Fatalf("incorrect number of elements in Pot. Expected %v, got %v", expi, goti) - } - inds = indexes(n) - got := fmt.Sprintf("%v", inds) - exp := "[5 3 2 1 0]" - if got != exp { - t.Fatalf("incorrect indexes in iteration over Pot. Expected %v, got %v", exp, got) - } -} - -// this test creates a flat pot tree (all the elements are leafs of one root), -// and therefore they all have the same po. -// then removes an arbitrary element from the pot. -func TestPotRemoveDifferentBins(t *testing.T) { - pof := DefaultPof(8) - n := NewPot(newTestAddr("11111111", 0), 0) - n, _, _ = testAdd(n, pof, 1, "00000000", "10000000", "11000000", "11100000", "11110000") - n, _, _ = Remove(n, newTestAddr("11100000", 0), pof) - inds := indexes(n) - goti := n.Size() - expi := 5 - if goti != expi { - t.Fatalf("incorrect number of elements in Pot. Expected %v, got %v", expi, goti) - } - inds = indexes(n) - got := fmt.Sprintf("%v", inds) - exp := "[1 2 3 5 0]" - if got != exp { - t.Fatalf("incorrect indexes in iteration over Pot. Expected %v, got %v", exp, got) - } - n, _, _ = testAdd(n, pof, 4, "11100000") - inds = indexes(n) - got = fmt.Sprintf("%v", inds) - exp = "[1 2 3 4 5 0]" - if got != exp { - t.Fatalf("incorrect indexes in iteration over Pot. Expected %v, got %v", exp, got) - } -} - -func TestPotAdd(t *testing.T) { - pof := DefaultPof(8) - n := NewPot(newTestAddr("00111100", 0), 0) - // Pin set correctly - exp := "00111100" - got := Label(n.Pin())[:8] - if got != exp { - t.Fatalf("incorrect pinned value. Expected %v, got %v", exp, got) - } - // check size - goti := n.Size() - expi := 1 - if goti != expi { - t.Fatalf("incorrect number of elements in Pot. Expected %v, got %v", expi, goti) - } - - n, _, _ = testAdd(n, pof, 1, "01111100", "00111100", "01111100", "00011100") - // check size - goti = n.Size() - expi = 3 - if goti != expi { - t.Fatalf("incorrect number of elements in Pot. Expected %v, got %v", expi, goti) - } - inds := indexes(n) - got = fmt.Sprintf("%v", inds) - exp = "[3 4 2]" - if got != exp { - t.Fatalf("incorrect indexes in iteration over Pot. Expected %v, got %v", exp, got) - } -} - -func TestPotRemove(t *testing.T) { - pof := DefaultPof(8) - n := NewPot(newTestAddr("00111100", 0), 0) - n, _, _ = Remove(n, newTestAddr("00111100", 0), pof) - exp := "" - got := Label(n.Pin()) - if got != exp { - t.Fatalf("incorrect pinned value. Expected %v, got %v", exp, got) - } - n, _, _ = testAdd(n, pof, 1, "00000000", "01111100", "00111100", "00011100") - n, _, _ = Remove(n, newTestAddr("00111100", 0), pof) - goti := n.Size() - expi := 3 - if goti != expi { - t.Fatalf("incorrect number of elements in Pot. Expected %v, got %v", expi, goti) - } - inds := indexes(n) - got = fmt.Sprintf("%v", inds) - exp = "[2 4 1]" - if got != exp { - t.Fatalf("incorrect indexes in iteration over Pot. Expected %v, got %v", exp, got) - } - n, _, _ = Remove(n, newTestAddr("00111100", 0), pof) // remove again same element - inds = indexes(n) - got = fmt.Sprintf("%v", inds) - if got != exp { - t.Fatalf("incorrect indexes in iteration over Pot. Expected %v, got %v", exp, got) - } - n, _, _ = Remove(n, newTestAddr("00000000", 0), pof) // remove the first element - inds = indexes(n) - got = fmt.Sprintf("%v", inds) - exp = "[2 4]" - if got != exp { - t.Fatalf("incorrect indexes in iteration over Pot. Expected %v, got %v", exp, got) - } -} - -func TestPotSwap(t *testing.T) { - for i := 0; i < maxSwapTests; i++ { - alen := maxkeylen - pof := DefaultPof(alen) - max := rand.Intn(maxSwap) - - n := NewPot(nil, 0) - var m []*testAddr - var found bool - for j := 0; j < 2*max; { - v := randomtestAddr(alen, j) - n, _, found = Add(n, v, pof) - if !found { - m = append(m, v) - j++ - } - } - k := make(map[string]*testAddr) - for j := 0; j < max; { - v := randomtestAddr(alen, 1) - _, found := k[Label(v)] - if !found { - k[Label(v)] = v - j++ - } - } - for _, v := range k { - m = append(m, v) - } - f := func(v Val) Val { - tv := v.(*testAddr) - if tv.i < max { - return nil - } - tv.i = 0 - return v - } - for _, val := range m { - n, _, _, _ = Swap(n, val, pof, func(v Val) Val { - if v == nil { - return val - } - return f(v) - }) - } - sum := 0 - n.Each(func(v Val) bool { - if v == nil { - return true - } - sum++ - tv := v.(*testAddr) - if tv.i > 1 { - t.Fatalf("item value incorrect, expected 0, got %v", tv.i) - } - return true - }) - if sum != 2*max { - t.Fatalf("incorrect number of elements. expected %v, got %v", 2*max, sum) - } - if sum != n.Size() { - t.Fatalf("incorrect size. expected %v, got %v", sum, n.Size()) - } - } -} - -func checkPo(val Val, pof Pof) func(Val, int) error { - return func(v Val, po int) error { - // check the po - exp, _ := pof(val, v, 0) - if po != exp { - return fmt.Errorf("incorrect prox order for item %v in neighbour iteration for %v. Expected %v, got %v", v, val, exp, po) - } - return nil - } -} - -func checkOrder(val Val) func(Val, int) error { - po := maxkeylen - return func(v Val, p int) error { - if po < p { - return fmt.Errorf("incorrect order for item %v in neighbour iteration for %v. PO %v > %v (previous max)", v, val, p, po) - } - po = p - return nil - } -} - -func checkValues(m map[string]bool, val Val) func(Val, int) error { - return func(v Val, po int) error { - duplicate, ok := m[Label(v)] - if !ok { - return fmt.Errorf("alien value %v", v) - } - if duplicate { - return fmt.Errorf("duplicate value returned: %v", v) - } - m[Label(v)] = true - return nil - } -} - -var errNoCount = errors.New("not count") - -func testPotEachNeighbour(n *Pot, pof Pof, val Val, expCount int, fs ...func(Val, int) error) error { - var err error - var count int - n.EachNeighbour(val, pof, func(v Val, po int) bool { - for _, f := range fs { - err = f(v, po) - if err != nil { - return err.Error() == errNoCount.Error() - } - } - count++ - return count != expCount - }) - if err == nil && count < expCount { - return fmt.Errorf("not enough neighbours returned, expected %v, got %v", expCount, count) - } - return err -} - -const ( - mergeTestCount = 5 - mergeTestChoose = 5 -) - -func TestPotMergeCommon(t *testing.T) { - vs := make([]*testAddr, mergeTestCount) - for i := 0; i < maxEachNeighbourTests; i++ { - alen := maxkeylen - pof := DefaultPof(alen) - - for j := 0; j < len(vs); j++ { - vs[j] = randomtestAddr(alen, j) - } - max0 := rand.Intn(mergeTestChoose) + 1 - max1 := rand.Intn(mergeTestChoose) + 1 - n0 := NewPot(nil, 0) - n1 := NewPot(nil, 0) - log.Trace(fmt.Sprintf("round %v: %v - %v", i, max0, max1)) - m := make(map[string]bool) - var found bool - for j := 0; j < max0; { - r := rand.Intn(max0) - v := vs[r] - n0, _, found = Add(n0, v, pof) - if !found { - m[Label(v)] = false - j++ - } - } - expAdded := 0 - - for j := 0; j < max1; { - r := rand.Intn(max1) - v := vs[r] - n1, _, found = Add(n1, v, pof) - if !found { - j++ - } - _, found = m[Label(v)] - if !found { - expAdded++ - m[Label(v)] = false - } - } - if i < 6 { - continue - } - expSize := len(m) - log.Trace(fmt.Sprintf("%v-0: pin: %v, size: %v", i, n0.Pin(), max0)) - log.Trace(fmt.Sprintf("%v-1: pin: %v, size: %v", i, n1.Pin(), max1)) - log.Trace(fmt.Sprintf("%v: merged tree size: %v, newly added: %v", i, expSize, expAdded)) - n, common := Union(n0, n1, pof) - added := n1.Size() - common - size := n.Size() - - if expSize != size { - t.Fatalf("%v: incorrect number of elements in merged pot, expected %v, got %v\n%v", i, expSize, size, n) - } - if expAdded != added { - t.Fatalf("%v: incorrect number of added elements in merged pot, expected %v, got %v", i, expAdded, added) - } - if !checkDuplicates(n) { - t.Fatalf("%v: merged pot contains duplicates: \n%v", i, n) - } - for k := range m { - _, _, found = Add(n, newTestAddr(k, 0), pof) - if !found { - t.Fatalf("%v: merged pot (size:%v, added: %v) missing element %v", i, size, added, k) - } - } - } -} - -func TestPotMergeScale(t *testing.T) { - for i := 0; i < maxEachNeighbourTests; i++ { - alen := maxkeylen - pof := DefaultPof(alen) - max0 := rand.Intn(maxEachNeighbour) + 1 - max1 := rand.Intn(maxEachNeighbour) + 1 - n0 := NewPot(nil, 0) - n1 := NewPot(nil, 0) - log.Trace(fmt.Sprintf("round %v: %v - %v", i, max0, max1)) - m := make(map[string]bool) - var found bool - for j := 0; j < max0; { - v := randomtestAddr(alen, j) - n0, _, found = Add(n0, v, pof) - if !found { - m[Label(v)] = false - j++ - } - } - expAdded := 0 - - for j := 0; j < max1; { - v := randomtestAddr(alen, j) - n1, _, found = Add(n1, v, pof) - if !found { - j++ - } - _, found = m[Label(v)] - if !found { - expAdded++ - m[Label(v)] = false - } - } - if i < 6 { - continue - } - expSize := len(m) - log.Trace(fmt.Sprintf("%v-0: pin: %v, size: %v", i, n0.Pin(), max0)) - log.Trace(fmt.Sprintf("%v-1: pin: %v, size: %v", i, n1.Pin(), max1)) - log.Trace(fmt.Sprintf("%v: merged tree size: %v, newly added: %v", i, expSize, expAdded)) - n, common := Union(n0, n1, pof) - added := n1.Size() - common - size := n.Size() - - if expSize != size { - t.Fatalf("%v: incorrect number of elements in merged pot, expected %v, got %v", i, expSize, size) - } - if expAdded != added { - t.Fatalf("%v: incorrect number of added elements in merged pot, expected %v, got %v", i, expAdded, added) - } - if !checkDuplicates(n) { - t.Fatalf("%v: merged pot contains duplicates", i) - } - for k := range m { - _, _, found = Add(n, newTestAddr(k, 0), pof) - if !found { - t.Fatalf("%v: merged pot (size:%v, added: %v) missing element %v", i, size, added, k) - } - } - } -} - -func checkDuplicates(t *Pot) bool { - po := -1 - for _, c := range t.bins { - if c == nil { - return false - } - if c.po <= po || !checkDuplicates(c) { - return false - } - po = c.po - } - return true -} - -func TestPotEachNeighbourSync(t *testing.T) { - for i := 0; i < maxEachNeighbourTests; i++ { - alen := maxkeylen - pof := DefaultPof(maxkeylen) - max := rand.Intn(maxEachNeighbour/2) + maxEachNeighbour/2 - pin := randomTestAddr(alen, 0) - n := NewPot(pin, 0) - m := make(map[string]bool) - m[Label(pin)] = false - for j := 1; j <= max; j++ { - v := randomTestAddr(alen, j) - n, _, _ = Add(n, v, pof) - m[Label(v)] = false - } - - size := n.Size() - if size < 2 { - continue - } - count := rand.Intn(size/2) + size/2 - val := randomTestAddr(alen, max+1) - log.Trace(fmt.Sprintf("%v: pin: %v, size: %v, val: %v, count: %v", i, n.Pin(), size, val, count)) - err := testPotEachNeighbour(n, pof, val, count, checkPo(val, pof), checkOrder(val), checkValues(m, val)) - if err != nil { - t.Fatal(err) - } - minPoFound := alen - maxPoNotFound := 0 - for k, found := range m { - po, _ := pof(val, newTestAddr(k, 0), 0) - if found { - if po < minPoFound { - minPoFound = po - } - } else { - if po > maxPoNotFound { - maxPoNotFound = po - } - } - } - if minPoFound < maxPoNotFound { - t.Fatalf("incorrect neighbours returned: found one with PO %v < there was one not found with PO %v", minPoFound, maxPoNotFound) - } - } -} - -func TestPotEachNeighbourAsync(t *testing.T) { - for i := 0; i < maxEachNeighbourTests; i++ { - max := rand.Intn(maxEachNeighbour/2) + maxEachNeighbour/2 - alen := maxkeylen - pof := DefaultPof(alen) - n := NewPot(randomTestAddr(alen, 0), 0) - size := 1 - var found bool - for j := 1; j <= max; j++ { - v := randomTestAddr(alen, j) - n, _, found = Add(n, v, pof) - if !found { - size++ - } - } - if size != n.Size() { - t.Fatal(n) - } - if size < 2 { - continue - } - count := rand.Intn(size/2) + size/2 - val := randomTestAddr(alen, max+1) - - mu := sync.Mutex{} - m := make(map[string]bool) - maxPos := rand.Intn(alen) - log.Trace(fmt.Sprintf("%v: pin: %v, size: %v, val: %v, count: %v, maxPos: %v", i, n.Pin(), size, val, count, maxPos)) - msize := 0 - remember := func(v Val, po int) error { - if po > maxPos { - return errNoCount - } - m[Label(v)] = true - msize++ - return nil - } - if i == 0 { - continue - } - testPotEachNeighbour(n, pof, val, count, remember) - d := 0 - forget := func(v Val, po int) { - mu.Lock() - defer mu.Unlock() - d++ - delete(m, Label(v)) - } - - n.EachNeighbourAsync(val, pof, count, maxPos, forget, true) - if d != msize { - t.Fatalf("incorrect number of neighbour calls in async iterator. expected %v, got %v", msize, d) - } - if len(m) != 0 { - t.Fatalf("incorrect neighbour calls in async iterator. %v items missed:\n%v", len(m), n) - } - } -} - -func benchmarkEachNeighbourSync(t *testing.B, max, count int, d time.Duration) { - t.ReportAllocs() - alen := maxkeylen - pof := DefaultPof(alen) - pin := randomTestAddr(alen, 0) - n := NewPot(pin, 0) - var found bool - for j := 1; j <= max; { - v := randomTestAddr(alen, j) - n, _, found = Add(n, v, pof) - if !found { - j++ - } - } - t.ResetTimer() - for i := 0; i < t.N; i++ { - val := randomTestAddr(alen, max+1) - m := 0 - n.EachNeighbour(val, pof, func(v Val, po int) bool { - time.Sleep(d) - m++ - return m != count - }) - } - t.StopTimer() - stats := new(runtime.MemStats) - runtime.ReadMemStats(stats) -} - -func benchmarkEachNeighbourAsync(t *testing.B, max, count int, d time.Duration) { - t.ReportAllocs() - alen := maxkeylen - pof := DefaultPof(alen) - pin := randomTestAddr(alen, 0) - n := NewPot(pin, 0) - var found bool - for j := 1; j <= max; { - v := randomTestAddr(alen, j) - n, _, found = Add(n, v, pof) - if !found { - j++ - } - } - t.ResetTimer() - for i := 0; i < t.N; i++ { - val := randomTestAddr(alen, max+1) - n.EachNeighbourAsync(val, pof, count, alen, func(v Val, po int) { - time.Sleep(d) - }, true) - } - t.StopTimer() - stats := new(runtime.MemStats) - runtime.ReadMemStats(stats) -} - -func BenchmarkEachNeighbourSync_3_1_0(t *testing.B) { - benchmarkEachNeighbourSync(t, 1000, 10, 1*time.Microsecond) -} -func BenchmarkEachNeighboursAsync_3_1_0(t *testing.B) { - benchmarkEachNeighbourAsync(t, 1000, 10, 1*time.Microsecond) -} -func BenchmarkEachNeighbourSync_3_2_0(t *testing.B) { - benchmarkEachNeighbourSync(t, 1000, 100, 1*time.Microsecond) -} -func BenchmarkEachNeighboursAsync_3_2_0(t *testing.B) { - benchmarkEachNeighbourAsync(t, 1000, 100, 1*time.Microsecond) -} -func BenchmarkEachNeighbourSync_3_3_0(t *testing.B) { - benchmarkEachNeighbourSync(t, 1000, 1000, 1*time.Microsecond) -} -func BenchmarkEachNeighboursAsync_3_3_0(t *testing.B) { - benchmarkEachNeighbourAsync(t, 1000, 1000, 1*time.Microsecond) -} - -func BenchmarkEachNeighbourSync_3_1_1(t *testing.B) { - benchmarkEachNeighbourSync(t, 1000, 10, 2*time.Microsecond) -} -func BenchmarkEachNeighboursAsync_3_1_1(t *testing.B) { - benchmarkEachNeighbourAsync(t, 1000, 10, 2*time.Microsecond) -} -func BenchmarkEachNeighbourSync_3_2_1(t *testing.B) { - benchmarkEachNeighbourSync(t, 1000, 100, 2*time.Microsecond) -} -func BenchmarkEachNeighboursAsync_3_2_1(t *testing.B) { - benchmarkEachNeighbourAsync(t, 1000, 100, 2*time.Microsecond) -} -func BenchmarkEachNeighbourSync_3_3_1(t *testing.B) { - benchmarkEachNeighbourSync(t, 1000, 1000, 2*time.Microsecond) -} -func BenchmarkEachNeighboursAsync_3_3_1(t *testing.B) { - benchmarkEachNeighbourAsync(t, 1000, 1000, 2*time.Microsecond) -} - -func BenchmarkEachNeighbourSync_3_1_2(t *testing.B) { - benchmarkEachNeighbourSync(t, 1000, 10, 4*time.Microsecond) -} -func BenchmarkEachNeighboursAsync_3_1_2(t *testing.B) { - benchmarkEachNeighbourAsync(t, 1000, 10, 4*time.Microsecond) -} -func BenchmarkEachNeighbourSync_3_2_2(t *testing.B) { - benchmarkEachNeighbourSync(t, 1000, 100, 4*time.Microsecond) -} -func BenchmarkEachNeighboursAsync_3_2_2(t *testing.B) { - benchmarkEachNeighbourAsync(t, 1000, 100, 4*time.Microsecond) -} -func BenchmarkEachNeighbourSync_3_3_2(t *testing.B) { - benchmarkEachNeighbourSync(t, 1000, 1000, 4*time.Microsecond) -} -func BenchmarkEachNeighboursAsync_3_3_2(t *testing.B) { - benchmarkEachNeighbourAsync(t, 1000, 1000, 4*time.Microsecond) -} - -func BenchmarkEachNeighbourSync_3_1_3(t *testing.B) { - benchmarkEachNeighbourSync(t, 1000, 10, 8*time.Microsecond) -} -func BenchmarkEachNeighboursAsync_3_1_3(t *testing.B) { - benchmarkEachNeighbourAsync(t, 1000, 10, 8*time.Microsecond) -} -func BenchmarkEachNeighbourSync_3_2_3(t *testing.B) { - benchmarkEachNeighbourSync(t, 1000, 100, 8*time.Microsecond) -} -func BenchmarkEachNeighboursAsync_3_2_3(t *testing.B) { - benchmarkEachNeighbourAsync(t, 1000, 100, 8*time.Microsecond) -} -func BenchmarkEachNeighbourSync_3_3_3(t *testing.B) { - benchmarkEachNeighbourSync(t, 1000, 1000, 8*time.Microsecond) -} -func BenchmarkEachNeighboursAsync_3_3_3(t *testing.B) { - benchmarkEachNeighbourAsync(t, 1000, 1000, 8*time.Microsecond) -} - -func BenchmarkEachNeighbourSync_3_1_4(t *testing.B) { - benchmarkEachNeighbourSync(t, 1000, 10, 16*time.Microsecond) -} -func BenchmarkEachNeighboursAsync_3_1_4(t *testing.B) { - benchmarkEachNeighbourAsync(t, 1000, 10, 16*time.Microsecond) -} -func BenchmarkEachNeighbourSync_3_2_4(t *testing.B) { - benchmarkEachNeighbourSync(t, 1000, 100, 16*time.Microsecond) -} -func BenchmarkEachNeighboursAsync_3_2_4(t *testing.B) { - benchmarkEachNeighbourAsync(t, 1000, 100, 16*time.Microsecond) -} -func BenchmarkEachNeighbourSync_3_3_4(t *testing.B) { - benchmarkEachNeighbourSync(t, 1000, 1000, 16*time.Microsecond) -} -func BenchmarkEachNeighboursAsync_3_3_4(t *testing.B) { - benchmarkEachNeighbourAsync(t, 1000, 1000, 16*time.Microsecond) -} diff --git a/swarm/pss/ARCHITECTURE.md b/swarm/pss/ARCHITECTURE.md deleted file mode 100644 index 279e895ab798..000000000000 --- a/swarm/pss/ARCHITECTURE.md +++ /dev/null @@ -1,144 +0,0 @@ -# Postal Service over Swarm - -Pss provides devp2p functionality for swarm nodes without the need for a direct tcp connection between them. - -Messages are encapsulated in a devp2p message structure `PssMsg`. These capsules are forwarded from node to node using ordinary tcp devp2p until they reach their destination: The node or nodes who can successfully decrypt the message. - -| Layer | Contents | -|-----------|-----------------| -| PssMsg: | Address, Expiry | -| Envelope: | Topic | -| Payload: | e(data) | - -Routing of messages is done using swarm's own kademlia routing. Optionally routing can be turned off, forcing the message to be sent to all peers, similar to the behavior of the whisper protocol. - -Pss is intended for messages of limited size, typically a couple of Kbytes at most. The messages themselves can be anything at all; complex data structures or non-descript byte sequences. - -For the current state and roadmap of pss development please see https://github.com/ethersphere/swarm/wiki/swarm-dev-progress. - -Please report issues on https://github.com/ethersphere/go-ethereum - -Feel free to ask questions in https://gitter.im/ethersphere/pss - -## STATUS OF THIS DOCUMENT - -`pss` is under active development, and the first implementation is yet to be merged to the Ethereum main branch. Expect things to change. - -## CORE INTERFACES - -The pss core provides low level control of key handling and message exchange. - -### TOPICS - -An encrypted envelope of a pss message always contains a Topic. This is pss' way of determining which message handlers to dispatch messages to. The topic of a message is only visible for the node(s) who can decrypt the message. - -This "topic" is not like the subject of an email message, but a hash-like arbitrary 4 byte value. A valid topic can be generated using the `pss_*ToTopic` API methods. - -### IDENTITY AND ENCRYPTION - -Pss aims to achieve perfect darkness. That means that the minimum requirement for two nodes to communicate using pss is a shared secret. This secret can be an arbitrary byte slice, or a ECDSA keypair. The end recipient of a message is defined as the node that can successfully decrypt that message using stored keys. - -A node's public key is derived from the private key passed to the `pss` constructor. Pss (currently) has no PKI. - -Peer keys can manually be added to the pss node through its API calls `pss_setPeerPublicKey` and `pss_setSymmetricKey`. Keys are always coupled with a topic, and the keys will only be valid for these topics. - -### CONNECTIONS - -A "connection" in pss is a purely virtual construct. There is no mechanisms in place to ensure that the remote peer actually is there. In fact, "adding" a peer involves merely the node's opinion that the peer is there. It may issue messages to that remote peer to a directly connected peer, which in turn passes it on. But if it is not present on the network - or if there is no route to it - the message will never reach its destination through mere forwarding. - -Since pss itself never requires a confirmation from a peer of whether a message is received or not, one could argue that pss shows `UDP`-like behavior. - -It is also important to note that if the wrong (partial) address is set for a particular key/topic combination, the message may never reach that peer. The further left in the address byte slice the error lies, the less likely it is that delivery will occur. - - -### EXCHANGE - -Message exchange in `pss` *requires* end-to-end encryption. - -The API methods `pss_sendSym` and `pss_sendAsym` sends an arbitrary byte slice with a specific topic to a pss peer using the respective encryption scheme. The key passed to the send method must be associated with a topic in the pss key store prior to sending, or the send method will fail. - -Return values from the send methods do *not* indicate whether the message was successfully delivered to the pss peer. It *only* indicates whether or not the message could be passed on to the network. If the message could not be forwarded to any peers, the method will fail. - -Keep in mind that symmetric encryption is less resource-intensive than asymmetric encryption. The former should be used for nodes with high message volumes. - -## EXTENSIONS - -### HANDSHAKE - -Pss offers an optional Diffie-Hellman handshake mechanism. Handshake functionality is activated per topic, and can be deactivated per topic even while the node is running. - -Handshakes are activated in the code implementation of the node by running `SetHandshakeController()` on the pss node instance BEFORE starting the node service. The methods exposed by the HandshakeController's API gives the possibility to initiate, remove and check the state of handshakes and associated keys. - -See the `HandshakeAPI` section in `godoc` for details. - -### DEVP2P PROTOCOLS - -The `Protocol` convenience structure is provided to mimic devp2p-type protocols over pss. In theory this makes it possible to reuse protocol code written for devp2p with a minimum of effort. - -#### OUTGOING CONNECTIONS - -In order to message a peer using this layer, a `Protocol` object must first be instantiated. When this is done, peers can be added using the protocol's `AddPeer()` method. The peer's key/topic combination must be in the pss key store before the peer can be aded. - -Adding a peer in effect "runs" the protocol on that peer, and adds an internal mapping between a topic and that peer, and enables sending and receiving messages using the usual io-construct of devp2p. It does not actually *transmit* anything to the peer, it merely represents the node's opinion that a connection with the peer exists. (See CONNECTION above). - -#### INCOMING CONNECTIONS - -An incoming connection is nothing more than an actual PssMsg appearing with a certain Topic. If a Handler has been registered to that Topic, the message will be passed to it. This constitutes a "new" connection if: - -- The pss node never called AddPeer with this combination of remote peer address and topic, and - -- The pss node never received a PssMsg from this remote peer with this specific Topic before. - -If it is a "new" connection, the protocol will be "run" on the remote peer, as if the peer was added via the API. - -As with the `AddPeer()` method, the key/topic of the originating peer must exist in the pss key store. - -#### TOPICS IN DEVP2P - -The `ProtocolTopic()` method should be used to determine the correct topic to use for a pss `Protocol` instance. - -## EXAMPLES - -Coming. Please refer to the tests for now. - -## PSS INTERNALS - -Pss implements the node.Service interface. It depends on a working kademlia overlay for routing. - -### DECRYPTION - -When processing an incoming message, `pss` detects whether it is encrypted symmetrically or asymmetrically. - -When decrypting symmetrically, `pss` iterates through all stored keys, and attempts to decrypt with each key in order. - -pss keeps a *cache* of these keys. The cache will only store a certain amount of keys, and the iterator will return keys in the order of most recently used key first. Abandoned keys will be garbage collected. - -### ROUTING - -(please refer to swarm kademlia routing for an explanation of the routing algorithm used for pss) - -`pss` uses *address hinting* for routing. The address hint is an arbitrary-length MSB byte slice of the peer's swarm overlay address. It can be the whole address, part of the address, or even an empty byte slice. The slice will be matched to the MSB slice of the same length of all devp2p peers in the routing stage. - -If an empty byte slice is passed, all devp2p peers will match the address hint, and the message will be forwarded to everyone. This is equivalent to `whisper` routing, and makes it difficult to perform traffic analysis based on who messages are forwarded to. - -A node will also forward to everyone if the address hint provided is in its proximity bin, both to provide saturation to increase chances of delivery, and also for recipient obfuscation to thwart traffic analysis attacks. The recipient node(s) will always forward to all its peers. - -### CACHING - -pss implements a simple caching mechanism for messages, using the swarm FileStore for storage of the messages and generation of the digest keys used in the cache table. The caching is intended to alleviate the following: - -- save messages so that they can be delivered later if the recipient was not online at the time of sending. - -- drop an identical message to the same recipient if received within a given time interval - -- prevent backwards routing of messages - -the latter may occur if only one entry is in the receiving node's kademlia, or if the proximity of the current node recipient hinted by the address is so close that the message will be forwarded to everyone. In these cases the forwarder will be provided as the "nearest node" to the final recipient. The cache keeps the address of who the message was forwarded from, and if the cache lookup matches, the message will be dropped. - -### DEVP2P PROTOCOLS - -When implementing devp2p protocols, topics are derived from protocols' name and version. The Protocol provides a generic Handler that be passed to Pss.Register. This makes it possible to use the same message handler code for pss that is used for directly connected peers in devp2p. - -Under the hood, pss implements its own MsgReadWriter, which bridges MsgReadWriter.WriteMsg with Pss.SendRaw, and deftly adds an InjectMsg method which pipes incoming messages to appear on the MsgReadWriter.ReadMsg channel. - - diff --git a/swarm/pss/README.md b/swarm/pss/README.md deleted file mode 100644 index aea871251fe0..000000000000 --- a/swarm/pss/README.md +++ /dev/null @@ -1,318 +0,0 @@ -# Postal Services over Swarm - -`pss` enables message relay over swarm. This means nodes can send messages to each other without being directly connected with each other, while taking advantage of the efficient routing algorithms that swarm uses for transporting and storing data. - -### CONTENTS - -* Status of this document -* Core concepts -* Caveat -* Examples -* API - * Retrieve node information - * Receive messages - * Send messages using public key encryption - * Send messages using symmetric encryption - * Querying peer keys - * Handshakes - -### STATUS OF THIS DOCUMENT - -`pss` is under active development, and the first implementation is yet to be merged to the Ethereum main branch. Expect things to change. - -Details on swarm routing and encryption schemes out of scope of this document. - -Please refer to [ARCHITECTURE.md](ARCHITECTURE.md) for in-depth topics concerning `pss`. - -## CORE CONCEPTS - -Three things are required to send a `pss` message: - -1. Encryption key -2. Topic -3. Message payload - -Encryption key can be a public key or a 32 byte symmetric key. It must be coupled with a peer address in the node prior to sending. - -Topic is the initial 4 bytes of a hash value. - -Message payload is an arbitrary byte slice of data. - -Upon sending the message it is encrypted and passed on from peer to peer. Any node along the route that can successfully decrypt the message is regarded as a recipient. Recipients continue to pass on the message to their peers, to make traffic analysis attacks more difficult. - -The Address that is coupled with the encryption keys are used for routing the message. This does *not* need to be a full addresses; the network will route the message to the best of its ability with the information that is available. If *no* address is given (zero-length byte slice), routing is effectively deactivated, and the message is passed to all peers by all peers. - -## CAVEAT - -`pss` connectivity resembles UDP. This means there is no delivery guarantee for a message. Furthermore there is no strict definition of what a connection between two nodes communicating via `pss` is. Reception acknowledgements and keepalive-schemes is the responsibility of the application. - -Due to the inherent properties of the `swarm` routing algorithm, a node may receive the same message more than once. Message deduplication *cannot be guaranteed* by `pss`, and must be handled in the application layer to ensure predictable results. - -## EXAMPLES - -The code tutorial [p2p programming in go-ethereum](https://github.com/nolash/go-ethereum-p2p-demo) by [@nolash](https://github.com/nolash) provides step-by-step code examples for usage of `pss` API with `go-ethereum` nodes. - -A quite unpolished example using `javascript` is available here: [https://github.com/nolash/pss-js/tree/withcrypt](https://github.com/nolash/pss-js/tree/withcrypt) - -## API - -The `pss` API is available through IPC and Websockets. There is currently no `web3.js` implementation, as this does not support message subscription. - -For `golang` clients, please use the `rpc.Client` provided by the `go-ethereum` repository. The return values may have special types in `golang`. Please refer to `godoc` for details. - -### RETRIEVE NODE INFORMATION - -#### pss_getPublicKey - -Retrieves the public key of the node, in hex format - -``` -parameters: -none - -returns: -1. publickey (hex) -``` - -#### pss_baseAddr - -Retrieves the swarm overlay address of the node, in hex format - -``` -parameters: -none - -returns: -1. swarm overlay address (hex) -``` - -#### pss_stringToTopic - -Creates a deterministic 4 byte topic value from input, returned in hex format - -``` -parameters: -1. topic string (string) - -returns: -1. pss topic (hex) -``` - -### RECEIVE MESSAGES - -#### pss_subscribe - -Creates a subscription. Received messages with matching topic will be passed to subscription client. - -``` -parameters: -1. string("receive") -2. topic (4 bytes in hex) - -returns: -1. subscription handle `base64(byte)` `rpc.ClientSubscription` -``` - -In `golang` as special method is used: - -`rpc.Client.Subscribe(context.Context, "pss", chan pss.APIMsg, "receive", pss.Topic)` - -Incoming messages are encapsulated in an object (`pss.APIMsg` in `golang`) with the following members: - -``` -1. Msg (hex) - the message payload -2. Asymmetric (bool) - true if message used public key encryption -3. Key (string) - the encryption key used -``` - -### SEND MESSAGE USING PUBLIC KEY ENCRYPTION - -#### pss_setPeerPublicKey - -Register a peer's public key. This is done once for every topic that will be used with the peer. Address can be anything from 0 to 32 bytes inclusive of the peer's swarm overlay address. - -``` -parameters: -1. public key of peer (hex) -2. topic (4 bytes in hex) -3. address of peer (hex) - -returns: -none -``` - -#### pss_sendAsym - -Encrypts the message using the provided public key, and signs it using the node's private key. It then wraps it in an envelope containing the topic, and sends it to the network. - -``` -parameters: -1. public key of peer (hex) -2. topic (4 bytes in hex) -3. message (hex) - -returns: -none -``` - -### SEND MESSAGE USING SYMMETRIC ENCRYPTION - -#### pss_setSymmetricKey - -Register a symmetric key shared with a peer. This is done once for every topic that will be used with the peer. Address can be anything from 0 to 32 bytes inclusive of the peer's swarm overlay address. - -If the fourth parameter is false, the key will *not* be added to the list of symmetric keys used for decryption attempts. - -``` -parameters: -1. symmetric key (hex) -2. topic (4 bytes in hex) -3. address of peer (hex) -4. use for decryption (bool) - -returns: -1. symmetric key id (string) -``` - -#### pss_sendSym - -Encrypts the message using the provided symmetric key, wraps it in an envelope containing the topic, and sends it to the network. - -``` -parameters: -1. symmetric key id (string) -2. topic (4 bytes in hex) -3. message (hex) - -returns: -none -``` - -### QUERY PEER KEYS - -#### pss_GetSymmetricAddressHint - -Return the swarm overlay address associated with the peer registered with the given symmetric key and topic combination. - -``` -parameters: -1. topic (4 bytes in hex) -2. symmetric key id (string) - -returns: -1. peer address (hex) -``` - -#### pss_GetAsymmetricAddressHint - -Return the swarm overlay address associated with the peer registered with the given symmetric key and topic combination. - -``` -parameters: -1. topic (4 bytes in hex) -2. public key in hex form (string) - -returns: -1. peer address (hex) -``` - -### HANDSHAKES - -Convenience implementation of Diffie-Hellman handshakes using ephemeral symmetric keys. Peers keep separate sets of keys for incoming and outgoing communications. - -*This functionality is an optional feature in `pss`. It is compiled in by default, but can be omitted by providing the `nopsshandshake` build tag.* - -#### pss_addHandshake - -Activate handshake functionality on the specified topic. - -``` -parameters: -1. topic (4 bytes in hex) - -returns: -none -``` - -#### pss_removeHandshake - -Remove handshake functionality on the specified topic. - -``` -parameters: -1. topic (4 bytes in hex) - -returns: -none -``` - -#### pss_handshake - -Instantiate handshake with peer, refreshing symmetric encryption keys. - -If parameter 3 is false, the returned array will be empty. - -``` -parameters: -1. public key of peer in hex format (string) -2. topic (4 bytes in hex) -3. block calls until keys are received (bool) -4. flush existing incoming keys (bool) - -returns: -1. list of symmetric keys (string[]) -``` - -#### pss_getHandshakeKeys - -Get valid symmetric encryption keys for a specified peer and topic. - -parameters: -1. public key of peer in hex format (string) -2. topic (4 bytes in hex) -3. include keys for incoming messages (bool) -4. include keys for outgoing messages (bool) - -returns: -1. list of symmetric keys (string[]) - -#### pss_getHandshakeKeyCapacity - -Get amount of remaining messages the specified key is valid for. - -``` -parameters: -1. symmetric key id (string) - -returns: -1. number of messages (uint16) -``` - -#### pss_getHandshakePublicKey - -Get the peer's public key associated with the specified symmetric key. - -``` -parameters: -1. symmetric key id (string) - -returns: -1. Associated public key in hex format (string) -``` - -#### pss_releaseHandshakeKey - -Invalidate the specified key. - -Normally, the key will be kept for a grace period to allow for decryption of delayed messages. If instant removal is set, this grace period is omitted, and the key removed instantaneously. - -``` -parameters: -1. public key of peer in hex format (string) -2. topic (4 bytes in hex) -3. symmetric key id to release (string) -4. remove keys instantly (bool) - -returns: -1. whether key was successfully removed (bool) -``` diff --git a/swarm/pss/api.go b/swarm/pss/api.go deleted file mode 100644 index d5ccb33815a5..000000000000 --- a/swarm/pss/api.go +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package pss - -import ( - "context" - "errors" - "fmt" - - "github.com/ubiq/go-ubiq/common/hexutil" - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/rpc" - "github.com/ubiq/go-ubiq/swarm/log" -) - -// Wrapper for receiving pss messages when using the pss API -// providing access to sender of message -type APIMsg struct { - Msg hexutil.Bytes - Asymmetric bool - Key string -} - -// Additional public methods accessible through API for pss -type API struct { - *Pss -} - -func NewAPI(ps *Pss) *API { - return &API{Pss: ps} -} - -// Creates a new subscription for the caller. Enables external handling of incoming messages. -// -// A new handler is registered in pss for the supplied topic -// -// All incoming messages to the node matching this topic will be encapsulated in the APIMsg -// struct and sent to the subscriber -func (pssapi *API) Receive(ctx context.Context, topic Topic, raw bool, prox bool) (*rpc.Subscription, error) { - notifier, supported := rpc.NotifierFromContext(ctx) - if !supported { - return nil, fmt.Errorf("Subscribe not supported") - } - - psssub := notifier.CreateSubscription() - - hndlr := NewHandler(func(msg []byte, p *p2p.Peer, asymmetric bool, keyid string) error { - apimsg := &APIMsg{ - Msg: hexutil.Bytes(msg), - Asymmetric: asymmetric, - Key: keyid, - } - if err := notifier.Notify(psssub.ID, apimsg); err != nil { - log.Warn(fmt.Sprintf("notification on pss sub topic rpc (sub %v) msg %v failed!", psssub.ID, msg)) - } - return nil - }) - if raw { - hndlr.caps.raw = true - } - if prox { - hndlr.caps.prox = true - } - - deregf := pssapi.Register(&topic, hndlr) - go func() { - defer deregf() - select { - case err := <-psssub.Err(): - log.Warn(fmt.Sprintf("caught subscription error in pss sub topic %x: %v", topic, err)) - case <-notifier.Closed(): - log.Warn(fmt.Sprintf("rpc sub notifier closed")) - } - }() - - return psssub, nil -} - -func (pssapi *API) GetAddress(topic Topic, asymmetric bool, key string) (PssAddress, error) { - var addr PssAddress - if asymmetric { - peer, ok := pssapi.Pss.pubKeyPool[key][topic] - if !ok { - return nil, fmt.Errorf("pubkey/topic pair %x/%x doesn't exist", key, topic) - } - addr = peer.address - } else { - peer, ok := pssapi.Pss.symKeyPool[key][topic] - if !ok { - return nil, fmt.Errorf("symkey/topic pair %x/%x doesn't exist", key, topic) - } - addr = peer.address - - } - return addr, nil -} - -// Retrieves the node's base address in hex form -func (pssapi *API) BaseAddr() (PssAddress, error) { - return PssAddress(pssapi.Pss.BaseAddr()), nil -} - -// Retrieves the node's public key in hex form -func (pssapi *API) GetPublicKey() (keybytes hexutil.Bytes) { - key := pssapi.Pss.PublicKey() - keybytes = crypto.FromECDSAPub(key) - return keybytes -} - -// Set Public key to associate with a particular Pss peer -func (pssapi *API) SetPeerPublicKey(pubkey hexutil.Bytes, topic Topic, addr PssAddress) error { - pk, err := crypto.UnmarshalPubkey(pubkey) - if err != nil { - return fmt.Errorf("Cannot unmarshal pubkey: %x", pubkey) - } - err = pssapi.Pss.SetPeerPublicKey(pk, topic, addr) - if err != nil { - return fmt.Errorf("Invalid key: %x", pk) - } - return nil -} - -func (pssapi *API) GetSymmetricKey(symkeyid string) (hexutil.Bytes, error) { - symkey, err := pssapi.Pss.GetSymmetricKey(symkeyid) - return hexutil.Bytes(symkey), err -} - -func (pssapi *API) GetSymmetricAddressHint(topic Topic, symkeyid string) (PssAddress, error) { - return pssapi.Pss.symKeyPool[symkeyid][topic].address, nil -} - -func (pssapi *API) GetAsymmetricAddressHint(topic Topic, pubkeyid string) (PssAddress, error) { - return pssapi.Pss.pubKeyPool[pubkeyid][topic].address, nil -} - -func (pssapi *API) StringToTopic(topicstring string) (Topic, error) { - topicbytes := BytesToTopic([]byte(topicstring)) - if topicbytes == rawTopic { - return rawTopic, errors.New("Topic string hashes to 0x00000000 and cannot be used") - } - return topicbytes, nil -} - -func (pssapi *API) SendAsym(pubkeyhex string, topic Topic, msg hexutil.Bytes) error { - if err := validateMsg(msg); err != nil { - return err - } - return pssapi.Pss.SendAsym(pubkeyhex, topic, msg[:]) -} - -func (pssapi *API) SendSym(symkeyhex string, topic Topic, msg hexutil.Bytes) error { - if err := validateMsg(msg); err != nil { - return err - } - return pssapi.Pss.SendSym(symkeyhex, topic, msg[:]) -} - -func (pssapi *API) SendRaw(addr hexutil.Bytes, topic Topic, msg hexutil.Bytes) error { - if err := validateMsg(msg); err != nil { - return err - } - return pssapi.Pss.SendRaw(PssAddress(addr), topic, msg[:]) -} - -func (pssapi *API) GetPeerTopics(pubkeyhex string) ([]Topic, error) { - topics, _, err := pssapi.Pss.GetPublickeyPeers(pubkeyhex) - return topics, err - -} - -func (pssapi *API) GetPeerAddress(pubkeyhex string, topic Topic) (PssAddress, error) { - return pssapi.Pss.getPeerAddress(pubkeyhex, topic) -} - -func validateMsg(msg []byte) error { - if len(msg) == 0 { - return errors.New("invalid message length") - } - return nil -} diff --git a/swarm/pss/client/client.go b/swarm/pss/client/client.go deleted file mode 100644 index 159af3e1dbde..000000000000 --- a/swarm/pss/client/client.go +++ /dev/null @@ -1,352 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build !noclient,!noprotocol - -package client - -import ( - "context" - "errors" - "fmt" - "sync" - "time" - - "github.com/ubiq/go-ubiq/common/hexutil" - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/protocols" - "github.com/ubiq/go-ubiq/rlp" - "github.com/ubiq/go-ubiq/rpc" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/pss" -) - -const ( - handshakeRetryTimeout = 1000 - handshakeRetryCount = 3 -) - -// The pss client provides devp2p emulation over pss RPC API, -// giving access to pss methods from a different process -type Client struct { - BaseAddrHex string - - // peers - peerPool map[pss.Topic]map[string]*pssRPCRW - protos map[pss.Topic]*p2p.Protocol - - // rpc connections - rpc *rpc.Client - subs []*rpc.ClientSubscription - - // channels - topicsC chan []byte - quitC chan struct{} - - poolMu sync.Mutex -} - -// implements p2p.MsgReadWriter -type pssRPCRW struct { - *Client - topic string - msgC chan []byte - addr pss.PssAddress - pubKeyId string - lastSeen time.Time - closed bool -} - -func (c *Client) newpssRPCRW(pubkeyid string, addr pss.PssAddress, topicobj pss.Topic) (*pssRPCRW, error) { - topic := topicobj.String() - err := c.rpc.Call(nil, "pss_setPeerPublicKey", pubkeyid, topic, hexutil.Encode(addr[:])) - if err != nil { - return nil, fmt.Errorf("setpeer %s %s: %v", topic, pubkeyid, err) - } - return &pssRPCRW{ - Client: c, - topic: topic, - msgC: make(chan []byte), - addr: addr, - pubKeyId: pubkeyid, - }, nil -} - -func (rw *pssRPCRW) ReadMsg() (p2p.Msg, error) { - msg := <-rw.msgC - log.Trace("pssrpcrw read", "msg", msg) - pmsg, err := pss.ToP2pMsg(msg) - if err != nil { - return p2p.Msg{}, err - } - - return pmsg, nil -} - -// If only one message slot left -// then new is requested through handshake -// if buffer is empty, handshake request blocks until return -// after which pointer is changed to first new key in buffer -// will fail if: -// - any api calls fail -// - handshake retries are exhausted without reply, -// - send fails -func (rw *pssRPCRW) WriteMsg(msg p2p.Msg) error { - log.Trace("got writemsg pssclient", "msg", msg) - if rw.closed { - return fmt.Errorf("connection closed") - } - rlpdata := make([]byte, msg.Size) - msg.Payload.Read(rlpdata) - pmsg, err := rlp.EncodeToBytes(pss.ProtocolMsg{ - Code: msg.Code, - Size: msg.Size, - Payload: rlpdata, - }) - if err != nil { - return err - } - - // Get the keys we have - var symkeyids []string - err = rw.Client.rpc.Call(&symkeyids, "pss_getHandshakeKeys", rw.pubKeyId, rw.topic, false, true) - if err != nil { - return err - } - - // Check the capacity of the first key - var symkeycap uint16 - if len(symkeyids) > 0 { - err = rw.Client.rpc.Call(&symkeycap, "pss_getHandshakeKeyCapacity", symkeyids[0]) - if err != nil { - return err - } - } - - err = rw.Client.rpc.Call(nil, "pss_sendSym", symkeyids[0], rw.topic, hexutil.Encode(pmsg)) - if err != nil { - return err - } - - // If this is the last message it is valid for, initiate new handshake - if symkeycap == 1 { - var retries int - var sync bool - // if it's the only remaining key, make sure we don't continue until we have new ones for further writes - if len(symkeyids) == 1 { - sync = true - } - // initiate handshake - _, err := rw.handshake(retries, sync, false) - if err != nil { - log.Warn("failing", "err", err) - return err - } - } - return nil -} - -// retry and synchronicity wrapper for handshake api call -// returns first new symkeyid upon successful execution -func (rw *pssRPCRW) handshake(retries int, sync bool, flush bool) (string, error) { - - var symkeyids []string - var i int - // request new keys - // if the key buffer was depleted, make this as a blocking call and try several times before giving up - for i = 0; i < 1+retries; i++ { - log.Debug("handshake attempt pssrpcrw", "pubkeyid", rw.pubKeyId, "topic", rw.topic, "sync", sync) - err := rw.Client.rpc.Call(&symkeyids, "pss_handshake", rw.pubKeyId, rw.topic, sync, flush) - if err == nil { - var keyid string - if sync { - keyid = symkeyids[0] - } - return keyid, nil - } - if i-1+retries > 1 { - time.Sleep(time.Millisecond * handshakeRetryTimeout) - } - } - - return "", fmt.Errorf("handshake failed after %d attempts", i) -} - -// Custom constructor -// -// Provides direct access to the rpc object -func NewClient(rpcurl string) (*Client, error) { - rpcclient, err := rpc.Dial(rpcurl) - if err != nil { - return nil, err - } - - client, err := NewClientWithRPC(rpcclient) - if err != nil { - return nil, err - } - return client, nil -} - -// Main constructor -// -// The 'rpcclient' parameter allows passing a in-memory rpc client to act as the remote websocket RPC. -func NewClientWithRPC(rpcclient *rpc.Client) (*Client, error) { - client := newClient() - client.rpc = rpcclient - err := client.rpc.Call(&client.BaseAddrHex, "pss_baseAddr") - if err != nil { - return nil, fmt.Errorf("cannot get pss node baseaddress: %v", err) - } - return client, nil -} - -func newClient() (client *Client) { - client = &Client{ - quitC: make(chan struct{}), - peerPool: make(map[pss.Topic]map[string]*pssRPCRW), - protos: make(map[pss.Topic]*p2p.Protocol), - } - return -} - -// Mounts a new devp2p protcool on the pss connection -// -// the protocol is aliased as a "pss topic" -// uses normal devp2p send and incoming message handler routines from the p2p/protocols package -// -// when an incoming message is received from a peer that is not yet known to the client, -// this peer object is instantiated, and the protocol is run on it. -func (c *Client) RunProtocol(ctx context.Context, proto *p2p.Protocol) error { - topicobj := pss.BytesToTopic([]byte(fmt.Sprintf("%s:%d", proto.Name, proto.Version))) - topichex := topicobj.String() - msgC := make(chan pss.APIMsg) - c.peerPool[topicobj] = make(map[string]*pssRPCRW) - sub, err := c.rpc.Subscribe(ctx, "pss", msgC, "receive", topichex, false, false) - if err != nil { - return fmt.Errorf("pss event subscription failed: %v", err) - } - c.subs = append(c.subs, sub) - err = c.rpc.Call(nil, "pss_addHandshake", topichex) - if err != nil { - return fmt.Errorf("pss handshake activation failed: %v", err) - } - - // dispatch incoming messages - go func() { - for { - select { - case msg := <-msgC: - // we only allow sym msgs here - if msg.Asymmetric { - continue - } - // we get passed the symkeyid - // need the symkey itself to resolve to peer's pubkey - var pubkeyid string - err = c.rpc.Call(&pubkeyid, "pss_getHandshakePublicKey", msg.Key) - if err != nil || pubkeyid == "" { - log.Trace("proto err or no pubkey", "err", err, "symkeyid", msg.Key) - continue - } - // if we don't have the peer on this protocol already, create it - // this is more or less the same as AddPssPeer, less the handshake initiation - if c.peerPool[topicobj][pubkeyid] == nil { - var addrhex string - err := c.rpc.Call(&addrhex, "pss_getAddress", topichex, false, msg.Key) - if err != nil { - log.Trace(err.Error()) - continue - } - addrbytes, err := hexutil.Decode(addrhex) - if err != nil { - log.Trace(err.Error()) - break - } - addr := pss.PssAddress(addrbytes) - rw, err := c.newpssRPCRW(pubkeyid, addr, topicobj) - if err != nil { - break - } - c.peerPool[topicobj][pubkeyid] = rw - p := p2p.NewPeer(enode.ID{}, fmt.Sprintf("%v", addr), []p2p.Cap{}) - go proto.Run(p, c.peerPool[topicobj][pubkeyid]) - } - go func() { - c.peerPool[topicobj][pubkeyid].msgC <- msg.Msg - }() - case <-c.quitC: - return - } - } - }() - - c.protos[topicobj] = proto - return nil -} - -// Always call this to ensure that we exit cleanly -func (c *Client) Close() error { - for _, s := range c.subs { - s.Unsubscribe() - } - return nil -} - -// Add a pss peer (public key) and run the protocol on it -// -// client.RunProtocol with matching topic must have been -// run prior to adding the peer, or this method will -// return an error. -// -// The key must exist in the key store of the pss node -// before the peer is added. The method will return an error -// if it is not. -func (c *Client) AddPssPeer(pubkeyid string, addr []byte, spec *protocols.Spec) error { - topic := pss.ProtocolTopic(spec) - if c.peerPool[topic] == nil { - return errors.New("addpeer on unset topic") - } - if c.peerPool[topic][pubkeyid] == nil { - rw, err := c.newpssRPCRW(pubkeyid, addr, topic) - if err != nil { - return err - } - _, err = rw.handshake(handshakeRetryCount, true, true) - if err != nil { - return err - } - c.poolMu.Lock() - c.peerPool[topic][pubkeyid] = rw - c.poolMu.Unlock() - p := p2p.NewPeer(enode.ID{}, fmt.Sprintf("%v", addr), []p2p.Cap{}) - go c.protos[topic].Run(p, c.peerPool[topic][pubkeyid]) - } - return nil -} - -// Remove a pss peer -// -// TODO: underlying cleanup -func (c *Client) RemovePssPeer(pubkeyid string, spec *protocols.Spec) { - log.Debug("closing pss client peer", "pubkey", pubkeyid, "protoname", spec.Name, "protoversion", spec.Version) - c.poolMu.Lock() - defer c.poolMu.Unlock() - topic := pss.ProtocolTopic(spec) - c.peerPool[topic][pubkeyid].closed = true - delete(c.peerPool[topic], pubkeyid) -} diff --git a/swarm/pss/client/client_test.go b/swarm/pss/client/client_test.go deleted file mode 100644 index 66fbf2fe955b..000000000000 --- a/swarm/pss/client/client_test.go +++ /dev/null @@ -1,303 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package client - -import ( - "bytes" - "context" - "flag" - "fmt" - "math/rand" - "os" - "sync" - "testing" - "time" - - "github.com/ubiq/go-ubiq/common/hexutil" - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/simulations" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" - "github.com/ubiq/go-ubiq/rpc" - "github.com/ubiq/go-ubiq/swarm/network" - "github.com/ubiq/go-ubiq/swarm/pss" - "github.com/ubiq/go-ubiq/swarm/state" - whisper "github.com/ubiq/go-ubiq/whisper/whisperv6" -) - -type protoCtrl struct { - C chan bool - protocol *pss.Protocol - run func(*p2p.Peer, p2p.MsgReadWriter) error -} - -var ( - debugdebugflag = flag.Bool("vv", false, "veryverbose") - debugflag = flag.Bool("v", false, "verbose") - w *whisper.Whisper - wapi *whisper.PublicWhisperAPI - // custom logging - psslogmain log.Logger - pssprotocols map[string]*protoCtrl - sendLimit = uint16(256) -) - -var services = newServices() - -func init() { - flag.Parse() - rand.Seed(time.Now().Unix()) - - adapters.RegisterServices(services) - - loglevel := log.LvlInfo - if *debugflag { - loglevel = log.LvlDebug - } else if *debugdebugflag { - loglevel = log.LvlTrace - } - - psslogmain = log.New("psslog", "*") - hs := log.StreamHandler(os.Stderr, log.TerminalFormat(true)) - hf := log.LvlFilterHandler(loglevel, hs) - h := log.CallerFileHandler(hf) - log.Root().SetHandler(h) - - w = whisper.New(&whisper.DefaultConfig) - wapi = whisper.NewPublicWhisperAPI(w) - - pssprotocols = make(map[string]*protoCtrl) -} - -// ping pong exchange across one expired symkey -func TestClientHandshake(t *testing.T) { - sendLimit = 3 - - clients, err := setupNetwork(2) - if err != nil { - t.Fatal(err) - } - - lpsc, err := NewClientWithRPC(clients[0]) - if err != nil { - t.Fatal(err) - } - rpsc, err := NewClientWithRPC(clients[1]) - if err != nil { - t.Fatal(err) - } - lpssping := &pss.Ping{ - OutC: make(chan bool), - InC: make(chan bool), - Pong: false, - } - rpssping := &pss.Ping{ - OutC: make(chan bool), - InC: make(chan bool), - Pong: false, - } - lproto := pss.NewPingProtocol(lpssping) - rproto := pss.NewPingProtocol(rpssping) - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - defer cancel() - err = lpsc.RunProtocol(ctx, lproto) - if err != nil { - t.Fatal(err) - } - err = rpsc.RunProtocol(ctx, rproto) - if err != nil { - t.Fatal(err) - } - topic := pss.PingTopic.String() - - var loaddr string - err = clients[0].Call(&loaddr, "pss_baseAddr") - if err != nil { - t.Fatalf("rpc get node 1 baseaddr fail: %v", err) - } - var roaddr string - err = clients[1].Call(&roaddr, "pss_baseAddr") - if err != nil { - t.Fatalf("rpc get node 2 baseaddr fail: %v", err) - } - - var lpubkey string - err = clients[0].Call(&lpubkey, "pss_getPublicKey") - if err != nil { - t.Fatalf("rpc get node 1 pubkey fail: %v", err) - } - var rpubkey string - err = clients[1].Call(&rpubkey, "pss_getPublicKey") - if err != nil { - t.Fatalf("rpc get node 2 pubkey fail: %v", err) - } - - err = clients[0].Call(nil, "pss_setPeerPublicKey", rpubkey, topic, roaddr) - if err != nil { - t.Fatal(err) - } - err = clients[1].Call(nil, "pss_setPeerPublicKey", lpubkey, topic, loaddr) - if err != nil { - t.Fatal(err) - } - - time.Sleep(time.Second) - - roaddrbytes, err := hexutil.Decode(roaddr) - if err != nil { - t.Fatal(err) - } - err = lpsc.AddPssPeer(rpubkey, roaddrbytes, pss.PingProtocol) - if err != nil { - t.Fatal(err) - } - - time.Sleep(time.Second) - - for i := uint16(0); i <= sendLimit; i++ { - lpssping.OutC <- false - got := <-rpssping.InC - log.Warn("ok", "idx", i, "got", got) - time.Sleep(time.Second) - } - - rw := lpsc.peerPool[pss.PingTopic][rpubkey] - lpsc.RemovePssPeer(rpubkey, pss.PingProtocol) - if err := rw.WriteMsg(p2p.Msg{ - Size: 3, - Payload: bytes.NewReader([]byte("foo")), - }); err == nil { - t.Fatalf("expected error on write") - } -} - -func setupNetwork(numnodes int) (clients []*rpc.Client, err error) { - nodes := make([]*simulations.Node, numnodes) - clients = make([]*rpc.Client, numnodes) - if numnodes < 2 { - return nil, fmt.Errorf("Minimum two nodes in network") - } - adapter := adapters.NewSimAdapter(services) - net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{ - ID: "0", - DefaultService: "bzz", - }) - for i := 0; i < numnodes; i++ { - nodeconf := adapters.RandomNodeConfig() - nodeconf.Services = []string{"bzz", "pss"} - nodes[i], err = net.NewNodeWithConfig(nodeconf) - if err != nil { - return nil, fmt.Errorf("error creating node 1: %v", err) - } - err = net.Start(nodes[i].ID()) - if err != nil { - return nil, fmt.Errorf("error starting node 1: %v", err) - } - if i > 0 { - err = net.Connect(nodes[i].ID(), nodes[i-1].ID()) - if err != nil { - return nil, fmt.Errorf("error connecting nodes: %v", err) - } - } - clients[i], err = nodes[i].Client() - if err != nil { - return nil, fmt.Errorf("create node 1 rpc client fail: %v", err) - } - } - if numnodes > 2 { - err = net.Connect(nodes[0].ID(), nodes[len(nodes)-1].ID()) - if err != nil { - return nil, fmt.Errorf("error connecting first and last nodes") - } - } - return clients, nil -} - -func newServices() adapters.Services { - stateStore := state.NewInmemoryStore() - kademlias := make(map[enode.ID]*network.Kademlia) - kademlia := func(id enode.ID) *network.Kademlia { - if k, ok := kademlias[id]; ok { - return k - } - params := network.NewKadParams() - params.NeighbourhoodSize = 2 - params.MaxBinSize = 3 - params.MinBinSize = 1 - params.MaxRetries = 1000 - params.RetryExponent = 2 - params.RetryInterval = 1000000 - kademlias[id] = network.NewKademlia(id[:], params) - return kademlias[id] - } - return adapters.Services{ - "pss": func(ctx *adapters.ServiceContext) (node.Service, error) { - ctxlocal, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - keys, err := wapi.NewKeyPair(ctxlocal) - if err != nil { - return nil, err - } - privkey, err := w.GetPrivateKey(keys) - if err != nil { - return nil, err - } - psparams := pss.NewPssParams().WithPrivateKey(privkey) - pskad := kademlia(ctx.Config.ID) - ps, err := pss.NewPss(pskad, psparams) - if err != nil { - return nil, err - } - pshparams := pss.NewHandshakeParams() - pshparams.SymKeySendLimit = sendLimit - err = pss.SetHandshakeController(ps, pshparams) - if err != nil { - return nil, fmt.Errorf("handshake controller fail: %v", err) - } - return ps, nil - }, - "bzz": func(ctx *adapters.ServiceContext) (node.Service, error) { - addr := network.NewAddr(ctx.Config.Node()) - hp := network.NewHiveParams() - hp.Discovery = false - config := &network.BzzConfig{ - OverlayAddr: addr.Over(), - UnderlayAddr: addr.Under(), - HiveParams: hp, - } - return network.NewBzz(config, kademlia(ctx.Config.ID), stateStore, nil, nil), nil - }, - } -} - -// copied from swarm/network/protocol_test_go -type testStore struct { - sync.Mutex - - values map[string][]byte -} - -func (t *testStore) Load(key string) ([]byte, error) { - return nil, nil -} - -func (t *testStore) Save(key string, v []byte) error { - return nil -} diff --git a/swarm/pss/client/doc.go b/swarm/pss/client/doc.go deleted file mode 100644 index 5316ebb689bc..000000000000 --- a/swarm/pss/client/doc.go +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// simple abstraction for implementing pss functionality -// -// the pss client library aims to simplify usage of the p2p.protocols package over pss -// -// IO is performed using the ordinary p2p.MsgReadWriter interface, which transparently communicates with a pss node via RPC using websockets as transport layer, using methods in the PssAPI class in the swarm/pss package -// -// -// Minimal-ish usage example (requires a running pss node with websocket RPC): -// -// -// import ( -// "context" -// "fmt" -// "os" -// pss "github.com/ubiq/go-ubiq/swarm/pss/client" -// "github.com/ubiq/go-ubiq/p2p/protocols" -// "github.com/ubiq/go-ubiq/p2p" -// "github.com/ubiq/go-ubiq/swarm/pot" -// "github.com/ubiq/go-ubiq/swarm/log" -// ) -// -// type FooMsg struct { -// Bar int -// } -// -// -// func fooHandler (msg interface{}) error { -// foomsg, ok := msg.(*FooMsg) -// if ok { -// log.Debug("Yay, just got a message", "msg", foomsg) -// } -// return errors.New(fmt.Sprintf("Unknown message")) -// } -// -// spec := &protocols.Spec{ -// Name: "foo", -// Version: 1, -// MaxMsgSize: 1024, -// Messages: []interface{}{ -// FooMsg{}, -// }, -// } -// -// proto := &p2p.Protocol{ -// Name: spec.Name, -// Version: spec.Version, -// Length: uint64(len(spec.Messages)), -// Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { -// pp := protocols.NewPeer(p, rw, spec) -// return pp.Run(fooHandler) -// }, -// } -// -// func implementation() { -// cfg := pss.NewClientConfig() -// psc := pss.NewClient(context.Background(), nil, cfg) -// err := psc.Start() -// if err != nil { -// log.Crit("can't start pss client") -// os.Exit(1) -// } -// -// log.Debug("connected to pss node", "bzz addr", psc.BaseAddr) -// -// err = psc.RunProtocol(proto) -// if err != nil { -// log.Crit("can't start protocol on pss websocket") -// os.Exit(1) -// } -// -// addr := pot.RandomAddress() // should be a real address, of course -// psc.AddPssPeer(addr, spec) -// -// // use the protocol for something -// -// psc.Stop() -// } -// -// BUG(test): TestIncoming test times out due to deadlock issues in the swarm hive -package client diff --git a/swarm/pss/doc.go b/swarm/pss/doc.go deleted file mode 100644 index 462c82aaad3d..000000000000 --- a/swarm/pss/doc.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Pss provides devp2p functionality for swarm nodes without the need for a direct tcp connection between them. -// -// Messages are encapsulated in a devp2p message structure `PssMsg`. These capsules are forwarded from node to node using ordinary tcp devp2p until it reaches its destination: The node or nodes who can successfully decrypt the message. -// -// Routing of messages is done using swarm's own kademlia routing. Optionally routing can be turned off, forcing the message to be sent to all peers, similar to the behavior of the whisper protocol. -// -// Pss is intended for messages of limited size, typically a couple of Kbytes at most. The messages themselves can be anything at all; complex data structures or non-descript byte sequences. -// -// Documentation can be found in the README file. -// -// For the current state and roadmap of pss development please see https://github.com/ethersphere/swarm/wiki/swarm-dev-progress. -// -// Please report issues on https://github.com/ethersphere/go-ethereum -// -// Feel free to ask questions in https://gitter.im/ethersphere/pss -// -// TOPICS -// -// An encrypted envelope of a pss messages always contains a Topic. This is pss' way of determining what action to take on the message. The topic is only visible for the node(s) who can decrypt the message. -// -// This "topic" is not like the subject of an email message, but a hash-like arbitrary 4 byte value. A valid topic can be generated using the `pss_*ToTopic` API methods. -// -// IDENTITY IN PSS -// -// Pss aims to achieve perfect darkness. That means that the minimum requirement for two nodes to communicate using pss is a shared secret. This secret can be an arbitrary byte slice, or a ECDSA keypair. -// -// Peer keys can manually be added to the pss node through its API calls `pss_setPeerPublicKey` and `pss_setSymmetricKey`. Keys are always coupled with a topic, and the keys will only be valid for these topics. -// -// CONNECTIONS -// -// A "connection" in pss is a purely virtual construct. There is no mechanisms in place to ensure that the remote peer actually is there. In fact, "adding" a peer involves merely the node's opinion that the peer is there. It may issue messages to that remote peer to a directly connected peer, which in turn passes it on. But if it is not present on the network - or if there is no route to it - the message will never reach its destination through mere forwarding. -// -// When implementing the devp2p protocol stack, the "adding" of a remote peer is a prerequisite for the side actually initiating the protocol communication. Adding a peer in effect "runs" the protocol on that peer, and adds an internal mapping between a topic and that peer. It also enables sending and receiving messages using the main io-construct in devp2p - the p2p.MsgReadWriter. -// -// Under the hood, pss implements its own MsgReadWriter, which bridges MsgReadWriter.WriteMsg with Pss.SendRaw, and deftly adds an InjectMsg method which pipes incoming messages to appear on the MsgReadWriter.ReadMsg channel. -// -// An incoming connection is nothing more than an actual PssMsg appearing with a certain Topic. If a Handler har been registered to that Topic, the message will be passed to it. This constitutes a "new" connection if: -// -// - The pss node never called AddPeer with this combination of remote peer address and topic, and -// -// - The pss node never received a PssMsg from this remote peer with this specific Topic before. -// -// If it is a "new" connection, the protocol will be "run" on the remote peer, in the same manner as if it was pre-emptively added. -// -package pss diff --git a/swarm/pss/forwarding_test.go b/swarm/pss/forwarding_test.go deleted file mode 100644 index 7e8fc1853730..000000000000 --- a/swarm/pss/forwarding_test.go +++ /dev/null @@ -1,357 +0,0 @@ -package pss - -import ( - "fmt" - "math/rand" - "testing" - "time" - - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/protocols" - "github.com/ubiq/go-ubiq/swarm/network" - "github.com/ubiq/go-ubiq/swarm/pot" - whisper "github.com/ubiq/go-ubiq/whisper/whisperv6" -) - -type testCase struct { - name string - recipient []byte - peers []pot.Address - expected []int - exclusive bool - nFails int - success bool - errors string -} - -var testCases []testCase - -// the purpose of this test is to see that pss.forward() function correctly -// selects the peers for message forwarding, depending on the message address -// and kademlia constellation. -func TestForwardBasic(t *testing.T) { - baseAddrBytes := make([]byte, 32) - for i := 0; i < len(baseAddrBytes); i++ { - baseAddrBytes[i] = 0xFF - } - var c testCase - base := pot.NewAddressFromBytes(baseAddrBytes) - var peerAddresses []pot.Address - const depth = 10 - for i := 0; i <= depth; i++ { - // add two peers for each proximity order - a := pot.RandomAddressAt(base, i) - peerAddresses = append(peerAddresses, a) - a = pot.RandomAddressAt(base, i) - peerAddresses = append(peerAddresses, a) - } - - // skip one level, add one peer at one level deeper. - // as a result, we will have an edge case of three peers in nearest neighbours' bin. - peerAddresses = append(peerAddresses, pot.RandomAddressAt(base, depth+2)) - - kad := network.NewKademlia(base[:], network.NewKadParams()) - ps := createPss(t, kad) - defer ps.Stop() - addPeers(kad, peerAddresses) - - const firstNearest = depth * 2 // shallowest peer in the nearest neighbours' bin - nearestNeighbours := []int{firstNearest, firstNearest + 1, firstNearest + 2} - var all []int // indices of all the peers - for i := 0; i < len(peerAddresses); i++ { - all = append(all, i) - } - - for i := 0; i < len(peerAddresses); i++ { - // send msg directly to the known peers (recipient address == peer address) - c = testCase{ - name: fmt.Sprintf("Send direct to known, id: [%d]", i), - recipient: peerAddresses[i][:], - peers: peerAddresses, - expected: []int{i}, - exclusive: false, - } - testCases = append(testCases, c) - } - - for i := 0; i < firstNearest; i++ { - // send random messages with proximity orders, corresponding to PO of each bin, - // with one peer being closer to the recipient address - a := pot.RandomAddressAt(peerAddresses[i], 64) - c = testCase{ - name: fmt.Sprintf("Send random to each PO, id: [%d]", i), - recipient: a[:], - peers: peerAddresses, - expected: []int{i}, - exclusive: false, - } - testCases = append(testCases, c) - } - - for i := 0; i < firstNearest; i++ { - // send random messages with proximity orders, corresponding to PO of each bin, - // with random proximity relative to the recipient address - po := i / 2 - a := pot.RandomAddressAt(base, po) - c = testCase{ - name: fmt.Sprintf("Send direct to known, id: [%d]", i), - recipient: a[:], - peers: peerAddresses, - expected: []int{po * 2, po*2 + 1}, - exclusive: true, - } - testCases = append(testCases, c) - } - - for i := firstNearest; i < len(peerAddresses); i++ { - // recipient address falls into the nearest neighbours' bin - a := pot.RandomAddressAt(base, i) - c = testCase{ - name: fmt.Sprintf("recipient address falls into the nearest neighbours' bin, id: [%d]", i), - recipient: a[:], - peers: peerAddresses, - expected: nearestNeighbours, - exclusive: false, - } - testCases = append(testCases, c) - } - - // send msg with proximity order much deeper than the deepest nearest neighbour - a2 := pot.RandomAddressAt(base, 77) - c = testCase{ - name: "proximity order much deeper than the deepest nearest neighbour", - recipient: a2[:], - peers: peerAddresses, - expected: nearestNeighbours, - exclusive: false, - } - testCases = append(testCases, c) - - // test with partial addresses - const part = 12 - - for i := 0; i < firstNearest; i++ { - // send messages with partial address falling into different proximity orders - po := i / 2 - if i%8 != 0 { - c = testCase{ - name: fmt.Sprintf("partial address falling into different proximity orders, id: [%d]", i), - recipient: peerAddresses[i][:i], - peers: peerAddresses, - expected: []int{po * 2, po*2 + 1}, - exclusive: true, - } - testCases = append(testCases, c) - } - c = testCase{ - name: fmt.Sprintf("extended partial address falling into different proximity orders, id: [%d]", i), - recipient: peerAddresses[i][:part], - peers: peerAddresses, - expected: []int{po * 2, po*2 + 1}, - exclusive: true, - } - testCases = append(testCases, c) - } - - for i := firstNearest; i < len(peerAddresses); i++ { - // partial address falls into the nearest neighbours' bin - c = testCase{ - name: fmt.Sprintf("partial address falls into the nearest neighbours' bin, id: [%d]", i), - recipient: peerAddresses[i][:part], - peers: peerAddresses, - expected: nearestNeighbours, - exclusive: false, - } - testCases = append(testCases, c) - } - - // partial address with proximity order deeper than any of the nearest neighbour - a3 := pot.RandomAddressAt(base, part) - c = testCase{ - name: "partial address with proximity order deeper than any of the nearest neighbour", - recipient: a3[:part], - peers: peerAddresses, - expected: nearestNeighbours, - exclusive: false, - } - testCases = append(testCases, c) - - // special cases where partial address matches a large group of peers - - // zero bytes of address is given, msg should be delivered to all the peers - c = testCase{ - name: "zero bytes of address is given", - recipient: []byte{}, - peers: peerAddresses, - expected: all, - exclusive: false, - } - testCases = append(testCases, c) - - // luminous radius of 8 bits, proximity order 8 - indexAtPo8 := 16 - c = testCase{ - name: "luminous radius of 8 bits", - recipient: []byte{0xFF}, - peers: peerAddresses, - expected: all[indexAtPo8:], - exclusive: false, - } - testCases = append(testCases, c) - - // luminous radius of 256 bits, proximity order 8 - a4 := pot.Address{} - a4[0] = 0xFF - c = testCase{ - name: "luminous radius of 256 bits", - recipient: a4[:], - peers: peerAddresses, - expected: []int{indexAtPo8, indexAtPo8 + 1}, - exclusive: true, - } - testCases = append(testCases, c) - - // check correct behaviour in case send fails - for i := 2; i < firstNearest-3; i += 2 { - po := i / 2 - // send random messages with proximity orders, corresponding to PO of each bin, - // with different numbers of failed attempts. - // msg should be received by only one of the deeper peers. - a := pot.RandomAddressAt(base, po) - c = testCase{ - name: fmt.Sprintf("Send direct to known, id: [%d]", i), - recipient: a[:], - peers: peerAddresses, - expected: all[i+1:], - exclusive: true, - nFails: rand.Int()%3 + 2, - } - testCases = append(testCases, c) - } - - for _, c := range testCases { - testForwardMsg(t, ps, &c) - } -} - -// this function tests the forwarding of a single message. the recipient address is passed as param, -// along with addresses of all peers, and indices of those peers which are expected to receive the message. -func testForwardMsg(t *testing.T, ps *Pss, c *testCase) { - recipientAddr := c.recipient - peers := c.peers - expected := c.expected - exclusive := c.exclusive - nFails := c.nFails - tries := 0 // number of previous failed tries - - resultMap := make(map[pot.Address]int) - - defer func() { sendFunc = sendMsg }() - sendFunc = func(_ *Pss, sp *network.Peer, _ *PssMsg) bool { - if tries < nFails { - tries++ - return false - } - a := pot.NewAddressFromBytes(sp.Address()) - resultMap[a]++ - return true - } - - msg := newTestMsg(recipientAddr) - ps.forward(msg) - - // check test results - var fail bool - precision := len(recipientAddr) - if precision > 4 { - precision = 4 - } - s := fmt.Sprintf("test [%s]\nmsg address: %x..., radius: %d", c.name, recipientAddr[:precision], 8*len(recipientAddr)) - - // false negatives (expected message didn't reach peer) - if exclusive { - var cnt int - for _, i := range expected { - a := peers[i] - cnt += resultMap[a] - resultMap[a] = 0 - } - if cnt != 1 { - s += fmt.Sprintf("\n%d messages received by %d peers with indices: [%v]", cnt, len(expected), expected) - fail = true - } - } else { - for _, i := range expected { - a := peers[i] - received := resultMap[a] - if received != 1 { - s += fmt.Sprintf("\npeer number %d [%x...] received %d messages", i, a[:4], received) - fail = true - } - resultMap[a] = 0 - } - } - - // false positives (unexpected message reached peer) - for k, v := range resultMap { - if v != 0 { - // find the index of the false positive peer - var j int - for j = 0; j < len(peers); j++ { - if peers[j] == k { - break - } - } - s += fmt.Sprintf("\npeer number %d [%x...] received %d messages", j, k[:4], v) - fail = true - } - } - - if fail { - t.Fatal(s) - } -} - -func addPeers(kad *network.Kademlia, addresses []pot.Address) { - for _, a := range addresses { - p := newTestDiscoveryPeer(a, kad) - kad.On(p) - } -} - -func createPss(t *testing.T, kad *network.Kademlia) *Pss { - privKey, err := crypto.GenerateKey() - pssp := NewPssParams().WithPrivateKey(privKey) - ps, err := NewPss(kad, pssp) - if err != nil { - t.Fatal(err.Error()) - } - return ps -} - -func newTestDiscoveryPeer(addr pot.Address, kad *network.Kademlia) *network.Peer { - rw := &p2p.MsgPipeRW{} - p := p2p.NewPeer(enode.ID{}, "test", []p2p.Cap{}) - pp := protocols.NewPeer(p, rw, &protocols.Spec{}) - bp := &network.BzzPeer{ - Peer: pp, - BzzAddr: &network.BzzAddr{ - OAddr: addr.Bytes(), - UAddr: []byte(fmt.Sprintf("%x", addr[:])), - }, - } - return network.NewPeer(bp, kad) -} - -func newTestMsg(addr []byte) *PssMsg { - msg := newPssMsg(&msgParams{}) - msg.To = addr[:] - msg.Expire = uint32(time.Now().Add(time.Second * 60).Unix()) - msg.Payload = &whisper.Envelope{ - Topic: [4]byte{}, - Data: []byte("i have nothing to hide"), - } - return msg -} diff --git a/swarm/pss/handshake.go b/swarm/pss/handshake.go deleted file mode 100644 index 10c68426755f..000000000000 --- a/swarm/pss/handshake.go +++ /dev/null @@ -1,566 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build !nopsshandshake - -package pss - -import ( - "context" - "errors" - "fmt" - "sync" - "time" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/common/hexutil" - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/rlp" - "github.com/ubiq/go-ubiq/rpc" - "github.com/ubiq/go-ubiq/swarm/log" -) - -const ( - IsActiveHandshake = true -) - -var ( - ctrlSingleton *HandshakeController -) - -const ( - defaultSymKeyRequestTimeout = 1000 * 8 // max wait ms to receive a response to a handshake symkey request - defaultSymKeyExpiryTimeout = 1000 * 10 // ms to wait before allowing garbage collection of an expired symkey - defaultSymKeySendLimit = 256 // amount of messages a symkey is valid for - defaultSymKeyCapacity = 4 // max number of symkeys to store/send simultaneously -) - -// symmetric key exchange message payload -type handshakeMsg struct { - From []byte - Limit uint16 - Keys [][]byte - Request uint8 - Topic Topic -} - -// internal representation of an individual symmetric key -type handshakeKey struct { - symKeyID *string - pubKeyID *string - limit uint16 - count uint16 - expiredAt time.Time -} - -// container for all in- and outgoing keys -// for one particular peer (public key) and topic -type handshake struct { - outKeys []handshakeKey - inKeys []handshakeKey -} - -// Initialization parameters for the HandshakeController -// -// SymKeyRequestExpiry: Timeout for waiting for a handshake reply -// (default 8000 ms) -// -// SymKeySendLimit: Amount of messages symmetric keys issues by -// this node is valid for (default 256) -// -// SymKeyCapacity: Ideal (and maximum) amount of symmetric keys -// held per direction per peer (default 4) -type HandshakeParams struct { - SymKeyRequestTimeout time.Duration - SymKeyExpiryTimeout time.Duration - SymKeySendLimit uint16 - SymKeyCapacity uint8 -} - -// Sane defaults for HandshakeController initialization -func NewHandshakeParams() *HandshakeParams { - return &HandshakeParams{ - SymKeyRequestTimeout: defaultSymKeyRequestTimeout * time.Millisecond, - SymKeyExpiryTimeout: defaultSymKeyExpiryTimeout * time.Millisecond, - SymKeySendLimit: defaultSymKeySendLimit, - SymKeyCapacity: defaultSymKeyCapacity, - } -} - -// Singleton object enabling semi-automatic Diffie-Hellman -// exchange of ephemeral symmetric keys -type HandshakeController struct { - pss *Pss - keyC map[string]chan []string // adds a channel to report when a handshake succeeds - lock sync.Mutex - symKeyRequestTimeout time.Duration - symKeyExpiryTimeout time.Duration - symKeySendLimit uint16 - symKeyCapacity uint8 - symKeyIndex map[string]*handshakeKey - handshakes map[string]map[Topic]*handshake - deregisterFuncs map[Topic]func() -} - -// Attach HandshakeController to pss node -// -// Must be called before starting the pss node service -func SetHandshakeController(pss *Pss, params *HandshakeParams) error { - ctrl := &HandshakeController{ - pss: pss, - keyC: make(map[string]chan []string), - symKeyRequestTimeout: params.SymKeyRequestTimeout, - symKeyExpiryTimeout: params.SymKeyExpiryTimeout, - symKeySendLimit: params.SymKeySendLimit, - symKeyCapacity: params.SymKeyCapacity, - symKeyIndex: make(map[string]*handshakeKey), - handshakes: make(map[string]map[Topic]*handshake), - deregisterFuncs: make(map[Topic]func()), - } - api := &HandshakeAPI{ - namespace: "pss", - ctrl: ctrl, - } - pss.addAPI(rpc.API{ - Namespace: api.namespace, - Version: "0.2", - Service: api, - Public: true, - }) - ctrlSingleton = ctrl - return nil -} - -// Return all unexpired symmetric keys from store by -// peer (public key), topic and specified direction -func (ctl *HandshakeController) validKeys(pubkeyid string, topic *Topic, in bool) (validkeys []*string) { - ctl.lock.Lock() - defer ctl.lock.Unlock() - now := time.Now() - if _, ok := ctl.handshakes[pubkeyid]; !ok { - return []*string{} - } else if _, ok := ctl.handshakes[pubkeyid][*topic]; !ok { - return []*string{} - } - var keystore *[]handshakeKey - if in { - keystore = &(ctl.handshakes[pubkeyid][*topic].inKeys) - } else { - keystore = &(ctl.handshakes[pubkeyid][*topic].outKeys) - } - - for _, key := range *keystore { - if key.limit <= key.count { - ctl.releaseKey(*key.symKeyID, topic) - } else if !key.expiredAt.IsZero() && key.expiredAt.Before(now) { - ctl.releaseKey(*key.symKeyID, topic) - } else { - validkeys = append(validkeys, key.symKeyID) - } - } - return -} - -// Add all given symmetric keys with validity limits to store by -// peer (public key), topic and specified direction -func (ctl *HandshakeController) updateKeys(pubkeyid string, topic *Topic, in bool, symkeyids []string, limit uint16) { - ctl.lock.Lock() - defer ctl.lock.Unlock() - if _, ok := ctl.handshakes[pubkeyid]; !ok { - ctl.handshakes[pubkeyid] = make(map[Topic]*handshake) - - } - if ctl.handshakes[pubkeyid][*topic] == nil { - ctl.handshakes[pubkeyid][*topic] = &handshake{} - } - var keystore *[]handshakeKey - expire := time.Now() - if in { - keystore = &(ctl.handshakes[pubkeyid][*topic].inKeys) - } else { - keystore = &(ctl.handshakes[pubkeyid][*topic].outKeys) - expire = expire.Add(time.Millisecond * ctl.symKeyExpiryTimeout) - } - for _, storekey := range *keystore { - storekey.expiredAt = expire - } - for i := 0; i < len(symkeyids); i++ { - storekey := handshakeKey{ - symKeyID: &symkeyids[i], - pubKeyID: &pubkeyid, - limit: limit, - } - *keystore = append(*keystore, storekey) - ctl.pss.symKeyPool[*storekey.symKeyID][*topic].protected = true - } - for i := 0; i < len(*keystore); i++ { - ctl.symKeyIndex[*(*keystore)[i].symKeyID] = &((*keystore)[i]) - } -} - -// Expire a symmetric key, making it elegible for garbage collection -func (ctl *HandshakeController) releaseKey(symkeyid string, topic *Topic) bool { - if ctl.symKeyIndex[symkeyid] == nil { - log.Debug("no symkey", "symkeyid", symkeyid) - return false - } - ctl.symKeyIndex[symkeyid].expiredAt = time.Now() - log.Debug("handshake release", "symkeyid", symkeyid) - return true -} - -// Checks all symmetric keys in given direction(s) by -// specified peer (public key) and topic for expiry. -// Expired means: -// - expiry timestamp is set, and grace period is exceeded -// - message validity limit is reached -func (ctl *HandshakeController) cleanHandshake(pubkeyid string, topic *Topic, in bool, out bool) int { - ctl.lock.Lock() - defer ctl.lock.Unlock() - var deletecount int - var deletes []string - now := time.Now() - handshake := ctl.handshakes[pubkeyid][*topic] - log.Debug("handshake clean", "pubkey", pubkeyid, "topic", topic) - if in { - for i, key := range handshake.inKeys { - if key.expiredAt.Before(now) || (key.expiredAt.IsZero() && key.limit <= key.count) { - log.Trace("handshake in clean remove", "symkeyid", *key.symKeyID) - deletes = append(deletes, *key.symKeyID) - handshake.inKeys[deletecount] = handshake.inKeys[i] - deletecount++ - } - } - handshake.inKeys = handshake.inKeys[:len(handshake.inKeys)-deletecount] - } - if out { - deletecount = 0 - for i, key := range handshake.outKeys { - if key.expiredAt.Before(now) && (key.expiredAt.IsZero() && key.limit <= key.count) { - log.Trace("handshake out clean remove", "symkeyid", *key.symKeyID) - deletes = append(deletes, *key.symKeyID) - handshake.outKeys[deletecount] = handshake.outKeys[i] - deletecount++ - } - } - handshake.outKeys = handshake.outKeys[:len(handshake.outKeys)-deletecount] - } - for _, keyid := range deletes { - delete(ctl.symKeyIndex, keyid) - ctl.pss.symKeyPool[keyid][*topic].protected = false - } - return len(deletes) -} - -// Runs cleanHandshake() on all peers and topics -func (ctl *HandshakeController) clean() { - peerpubkeys := ctl.handshakes - for pubkeyid, peertopics := range peerpubkeys { - for topic := range peertopics { - ctl.cleanHandshake(pubkeyid, &topic, true, true) - } - } -} - -// Passed as a PssMsg handler for the topic handshake is activated on -// Handles incoming key exchange messages and -// ccunts message usage by symmetric key (expiry limit control) -// Only returns error if key handler fails -func (ctl *HandshakeController) handler(msg []byte, p *p2p.Peer, asymmetric bool, symkeyid string) error { - if !asymmetric { - if ctl.symKeyIndex[symkeyid] != nil { - if ctl.symKeyIndex[symkeyid].count >= ctl.symKeyIndex[symkeyid].limit { - return fmt.Errorf("discarding message using expired key: %s", symkeyid) - } - ctl.symKeyIndex[symkeyid].count++ - log.Trace("increment symkey recv use", "symsymkeyid", symkeyid, "count", ctl.symKeyIndex[symkeyid].count, "limit", ctl.symKeyIndex[symkeyid].limit, "receiver", common.ToHex(crypto.FromECDSAPub(ctl.pss.PublicKey()))) - } - return nil - } - keymsg := &handshakeMsg{} - err := rlp.DecodeBytes(msg, keymsg) - if err == nil { - err := ctl.handleKeys(symkeyid, keymsg) - if err != nil { - log.Error("handlekeys fail", "error", err) - } - return err - } - return nil -} - -// Handle incoming key exchange message -// Add keys received from peer to store -// and enerate and send the amount of keys requested by peer -// -// TODO: -// - flood guard -// - keylength check -// - update address hint if: -// 1) leftmost bytes in new address do not match stored -// 2) else, if new address is longer -func (ctl *HandshakeController) handleKeys(pubkeyid string, keymsg *handshakeMsg) error { - // new keys from peer - if len(keymsg.Keys) > 0 { - log.Debug("received handshake keys", "pubkeyid", pubkeyid, "from", keymsg.From, "count", len(keymsg.Keys)) - var sendsymkeyids []string - for _, key := range keymsg.Keys { - sendsymkey := make([]byte, len(key)) - copy(sendsymkey, key) - sendsymkeyid, err := ctl.pss.setSymmetricKey(sendsymkey, keymsg.Topic, PssAddress(keymsg.From), false, false) - if err != nil { - return err - } - sendsymkeyids = append(sendsymkeyids, sendsymkeyid) - } - if len(sendsymkeyids) > 0 { - ctl.updateKeys(pubkeyid, &keymsg.Topic, false, sendsymkeyids, keymsg.Limit) - - ctl.alertHandshake(pubkeyid, sendsymkeyids) - } - } - - // peer request for keys - if keymsg.Request > 0 { - _, err := ctl.sendKey(pubkeyid, &keymsg.Topic, keymsg.Request) - if err != nil { - return err - } - } - - return nil -} - -// Send key exchange to peer (public key) valid for `topic` -// Will send number of keys specified by `keycount` with -// validity limits specified in `msglimit` -// If number of valid outgoing keys is less than the ideal/max -// amount, a request is sent for the amount of keys to make up -// the difference -func (ctl *HandshakeController) sendKey(pubkeyid string, topic *Topic, keycount uint8) ([]string, error) { - - var requestcount uint8 - to := PssAddress{} - if _, ok := ctl.pss.pubKeyPool[pubkeyid]; !ok { - return []string{}, errors.New("Invalid public key") - } else if psp, ok := ctl.pss.pubKeyPool[pubkeyid][*topic]; ok { - to = psp.address - } - - recvkeys := make([][]byte, keycount) - recvkeyids := make([]string, keycount) - ctl.lock.Lock() - if _, ok := ctl.handshakes[pubkeyid]; !ok { - ctl.handshakes[pubkeyid] = make(map[Topic]*handshake) - } - ctl.lock.Unlock() - - // check if buffer is not full - outkeys := ctl.validKeys(pubkeyid, topic, false) - if len(outkeys) < int(ctl.symKeyCapacity) { - //requestcount = uint8(self.symKeyCapacity - uint8(len(outkeys))) - requestcount = ctl.symKeyCapacity - } - // return if there's nothing to be accomplished - if requestcount == 0 && keycount == 0 { - return []string{}, nil - } - - // generate new keys to send - for i := 0; i < len(recvkeyids); i++ { - var err error - recvkeyids[i], err = ctl.pss.GenerateSymmetricKey(*topic, to, true) - if err != nil { - return []string{}, fmt.Errorf("set receive symkey fail (pubkey %x topic %x): %v", pubkeyid, topic, err) - } - recvkeys[i], err = ctl.pss.GetSymmetricKey(recvkeyids[i]) - if err != nil { - return []string{}, fmt.Errorf("GET Generated outgoing symkey fail (pubkey %x topic %x): %v", pubkeyid, topic, err) - } - } - ctl.updateKeys(pubkeyid, topic, true, recvkeyids, ctl.symKeySendLimit) - - // encode and send the message - recvkeymsg := &handshakeMsg{ - From: ctl.pss.BaseAddr(), - Keys: recvkeys, - Request: requestcount, - Limit: ctl.symKeySendLimit, - Topic: *topic, - } - log.Debug("sending our symkeys", "pubkey", pubkeyid, "symkeys", recvkeyids, "limit", ctl.symKeySendLimit, "requestcount", requestcount, "keycount", len(recvkeys)) - recvkeybytes, err := rlp.EncodeToBytes(recvkeymsg) - if err != nil { - return []string{}, fmt.Errorf("rlp keymsg encode fail: %v", err) - } - // if the send fails it means this public key is not registered for this particular address AND topic - err = ctl.pss.SendAsym(pubkeyid, *topic, recvkeybytes) - if err != nil { - return []string{}, fmt.Errorf("Send symkey failed: %v", err) - } - return recvkeyids, nil -} - -// Enables callback for keys received from a key exchange request -func (ctl *HandshakeController) alertHandshake(pubkeyid string, symkeys []string) chan []string { - if len(symkeys) > 0 { - if _, ok := ctl.keyC[pubkeyid]; ok { - ctl.keyC[pubkeyid] <- symkeys - close(ctl.keyC[pubkeyid]) - delete(ctl.keyC, pubkeyid) - } - return nil - } - if _, ok := ctl.keyC[pubkeyid]; !ok { - ctl.keyC[pubkeyid] = make(chan []string) - } - return ctl.keyC[pubkeyid] -} - -type HandshakeAPI struct { - namespace string - ctrl *HandshakeController -} - -// Initiate a handshake session for a peer (public key) and topic -// combination. -// -// If `sync` is set, the call will block until keys are received from peer, -// or if the handshake request times out -// -// If `flush` is set, the max amount of keys will be sent to the peer -// regardless of how many valid keys that currently exist in the store. -// -// Returns list of symmetric key ids that can be passed to pss.GetSymmetricKey() -// for retrieval of the symmetric key bytes themselves. -// -// Fails if the incoming symmetric key store is already full (and `flush` is false), -// or if the underlying key dispatcher fails -func (api *HandshakeAPI) Handshake(pubkeyid string, topic Topic, sync bool, flush bool) (keys []string, err error) { - var hsc chan []string - var keycount uint8 - if flush { - keycount = api.ctrl.symKeyCapacity - } else { - validkeys := api.ctrl.validKeys(pubkeyid, &topic, false) - keycount = api.ctrl.symKeyCapacity - uint8(len(validkeys)) - } - if keycount == 0 { - return keys, errors.New("Incoming symmetric key store is already full") - } - if sync { - hsc = api.ctrl.alertHandshake(pubkeyid, []string{}) - } - _, err = api.ctrl.sendKey(pubkeyid, &topic, keycount) - if err != nil { - return keys, err - } - if sync { - ctx, cancel := context.WithTimeout(context.Background(), api.ctrl.symKeyRequestTimeout) - defer cancel() - select { - case keys = <-hsc: - log.Trace("sync handshake response receive", "key", keys) - case <-ctx.Done(): - return []string{}, errors.New("timeout") - } - } - return keys, nil -} - -// Activate handshake functionality on a topic -func (api *HandshakeAPI) AddHandshake(topic Topic) error { - api.ctrl.deregisterFuncs[topic] = api.ctrl.pss.Register(&topic, NewHandler(api.ctrl.handler)) - return nil -} - -// Deactivate handshake functionality on a topic -func (api *HandshakeAPI) RemoveHandshake(topic *Topic) error { - if _, ok := api.ctrl.deregisterFuncs[*topic]; ok { - api.ctrl.deregisterFuncs[*topic]() - } - return nil -} - -// Returns all valid symmetric keys in store per peer (public key) -// and topic. -// -// The `in` and `out` parameters indicate for which direction(s) -// symmetric keys will be returned. -// If both are false, no keys (and no error) will be returned. -func (api *HandshakeAPI) GetHandshakeKeys(pubkeyid string, topic Topic, in bool, out bool) (keys []string, err error) { - if in { - for _, inkey := range api.ctrl.validKeys(pubkeyid, &topic, true) { - keys = append(keys, *inkey) - } - } - if out { - for _, outkey := range api.ctrl.validKeys(pubkeyid, &topic, false) { - keys = append(keys, *outkey) - } - } - return keys, nil -} - -// Returns the amount of messages the specified symmetric key -// is still valid for under the handshake scheme -func (api *HandshakeAPI) GetHandshakeKeyCapacity(symkeyid string) (uint16, error) { - storekey := api.ctrl.symKeyIndex[symkeyid] - if storekey == nil { - return 0, fmt.Errorf("invalid symkey id %s", symkeyid) - } - return storekey.limit - storekey.count, nil -} - -// Returns the byte representation of the public key in ascii hex -// associated with the given symmetric key -func (api *HandshakeAPI) GetHandshakePublicKey(symkeyid string) (string, error) { - storekey := api.ctrl.symKeyIndex[symkeyid] - if storekey == nil { - return "", fmt.Errorf("invalid symkey id %s", symkeyid) - } - return *storekey.pubKeyID, nil -} - -// Manually expire the given symkey -// -// If `flush` is set, garbage collection will be performed before returning. -// -// Returns true on successful removal, false otherwise -func (api *HandshakeAPI) ReleaseHandshakeKey(pubkeyid string, topic Topic, symkeyid string, flush bool) (removed bool, err error) { - removed = api.ctrl.releaseKey(symkeyid, &topic) - if removed && flush { - api.ctrl.cleanHandshake(pubkeyid, &topic, true, true) - } - return -} - -// Send symmetric message under the handshake scheme -// -// Overloads the pss.SendSym() API call, adding symmetric key usage count -// for message expiry control -func (api *HandshakeAPI) SendSym(symkeyid string, topic Topic, msg hexutil.Bytes) (err error) { - err = api.ctrl.pss.SendSym(symkeyid, topic, msg[:]) - if api.ctrl.symKeyIndex[symkeyid] != nil { - if api.ctrl.symKeyIndex[symkeyid].count >= api.ctrl.symKeyIndex[symkeyid].limit { - return errors.New("attempted send with expired key") - } - api.ctrl.symKeyIndex[symkeyid].count++ - log.Trace("increment symkey send use", "symkeyid", symkeyid, "count", api.ctrl.symKeyIndex[symkeyid].count, "limit", api.ctrl.symKeyIndex[symkeyid].limit, "receiver", common.ToHex(crypto.FromECDSAPub(api.ctrl.pss.PublicKey()))) - } - return err -} diff --git a/swarm/pss/handshake_none.go b/swarm/pss/handshake_none.go deleted file mode 100644 index a09674be3971..000000000000 --- a/swarm/pss/handshake_none.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build nopsshandshake - -package pss - -const ( - IsActiveHandshake = false -) - -func NewHandshakeParams() interface{} { - return nil -} diff --git a/swarm/pss/handshake_test.go b/swarm/pss/handshake_test.go deleted file mode 100644 index d0701c49360b..000000000000 --- a/swarm/pss/handshake_test.go +++ /dev/null @@ -1,267 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build foo - -package pss - -import ( - "strconv" - "strings" - "testing" - "time" - - "github.com/ubiq/go-ubiq/swarm/log" -) - -// asymmetrical key exchange between two directly connected peers -// full address, partial address (8 bytes) and empty address -func TestHandshake(t *testing.T) { - t.Skip("handshakes are not adapted to current pss core code") - t.Run("32", testHandshake) - t.Run("8", testHandshake) - t.Run("0", testHandshake) -} - -func testHandshake(t *testing.T) { - - // how much of the address we will use - useHandshake = true - var addrsize int64 - var err error - addrsizestring := strings.Split(t.Name(), "/") - addrsize, _ = strconv.ParseInt(addrsizestring[1], 10, 0) - - // set up two nodes directly connected - // (we are not testing pss routing here) - clients, err := setupNetwork(2) - if err != nil { - t.Fatal(err) - } - - var topic string - err = clients[0].Call(&topic, "pss_stringToTopic", "foo:42") - if err != nil { - t.Fatal(err) - } - - var loaddr string - err = clients[0].Call(&loaddr, "pss_baseAddr") - if err != nil { - t.Fatalf("rpc get node 1 baseaddr fail: %v", err) - } - // "0x" = 2 bytes + addrsize address bytes which in hex is 2x length - loaddr = loaddr[:2+(addrsize*2)] - var roaddr string - err = clients[1].Call(&roaddr, "pss_baseAddr") - if err != nil { - t.Fatalf("rpc get node 2 baseaddr fail: %v", err) - } - roaddr = roaddr[:2+(addrsize*2)] - log.Debug("addresses", "left", loaddr, "right", roaddr) - - // retrieve public key from pss instance - // set this public key reciprocally - var lpubkey string - err = clients[0].Call(&lpubkey, "pss_getPublicKey") - if err != nil { - t.Fatalf("rpc get node 1 pubkey fail: %v", err) - } - var rpubkey string - err = clients[1].Call(&rpubkey, "pss_getPublicKey") - if err != nil { - t.Fatalf("rpc get node 2 pubkey fail: %v", err) - } - - time.Sleep(time.Millisecond * 1000) // replace with hive healthy code - - // give each node its peer's public key - err = clients[0].Call(nil, "pss_setPeerPublicKey", rpubkey, topic, roaddr) - if err != nil { - t.Fatal(err) - } - err = clients[1].Call(nil, "pss_setPeerPublicKey", lpubkey, topic, loaddr) - if err != nil { - t.Fatal(err) - } - - // perform the handshake - // after this each side will have defaultSymKeyBufferCapacity symkeys each for in- and outgoing messages: - // L -> request 4 keys -> R - // L <- send 4 keys, request 4 keys <- R - // L -> send 4 keys -> R - // the call will fill the array with symkeys L needs for sending to R - err = clients[0].Call(nil, "pss_addHandshake", topic) - if err != nil { - t.Fatal(err) - } - err = clients[1].Call(nil, "pss_addHandshake", topic) - if err != nil { - t.Fatal(err) - } - - var lhsendsymkeyids []string - err = clients[0].Call(&lhsendsymkeyids, "pss_handshake", rpubkey, topic, true, true) - if err != nil { - t.Fatal(err) - } - - // make sure the r-node gets its keys - time.Sleep(time.Second) - - // check if we have 6 outgoing keys stored, and they match what was received from R - var lsendsymkeyids []string - err = clients[0].Call(&lsendsymkeyids, "pss_getHandshakeKeys", rpubkey, topic, false, true) - if err != nil { - t.Fatal(err) - } - m := 0 - for _, hid := range lhsendsymkeyids { - for _, lid := range lsendsymkeyids { - if lid == hid { - m++ - } - } - } - if m != defaultSymKeyCapacity { - t.Fatalf("buffer size mismatch, expected %d, have %d: %v", defaultSymKeyCapacity, m, lsendsymkeyids) - } - - // check if in- and outgoing keys on l-node and r-node match up and are in opposite categories (l recv = r send, l send = r recv) - var rsendsymkeyids []string - err = clients[1].Call(&rsendsymkeyids, "pss_getHandshakeKeys", lpubkey, topic, false, true) - if err != nil { - t.Fatal(err) - } - var lrecvsymkeyids []string - err = clients[0].Call(&lrecvsymkeyids, "pss_getHandshakeKeys", rpubkey, topic, true, false) - if err != nil { - t.Fatal(err) - } - var rrecvsymkeyids []string - err = clients[1].Call(&rrecvsymkeyids, "pss_getHandshakeKeys", lpubkey, topic, true, false) - if err != nil { - t.Fatal(err) - } - - // get outgoing symkeys in byte form from both sides - var lsendsymkeys []string - for _, id := range lsendsymkeyids { - var key string - err = clients[0].Call(&key, "pss_getSymmetricKey", id) - if err != nil { - t.Fatal(err) - } - lsendsymkeys = append(lsendsymkeys, key) - } - var rsendsymkeys []string - for _, id := range rsendsymkeyids { - var key string - err = clients[1].Call(&key, "pss_getSymmetricKey", id) - if err != nil { - t.Fatal(err) - } - rsendsymkeys = append(rsendsymkeys, key) - } - - // get incoming symkeys in byte form from both sides and compare - var lrecvsymkeys []string - for _, id := range lrecvsymkeyids { - var key string - err = clients[0].Call(&key, "pss_getSymmetricKey", id) - if err != nil { - t.Fatal(err) - } - match := false - for _, otherkey := range rsendsymkeys { - if otherkey == key { - match = true - } - } - if !match { - t.Fatalf("no match right send for left recv key %s", id) - } - lrecvsymkeys = append(lrecvsymkeys, key) - } - var rrecvsymkeys []string - for _, id := range rrecvsymkeyids { - var key string - err = clients[1].Call(&key, "pss_getSymmetricKey", id) - if err != nil { - t.Fatal(err) - } - match := false - for _, otherkey := range lsendsymkeys { - if otherkey == key { - match = true - } - } - if !match { - t.Fatalf("no match left send for right recv key %s", id) - } - rrecvsymkeys = append(rrecvsymkeys, key) - } - - // send new handshake request, should send no keys - err = clients[0].Call(nil, "pss_handshake", rpubkey, topic, false) - if err == nil { - t.Fatal("expected full symkey buffer error") - } - - // expire one key, send new handshake request - err = clients[0].Call(nil, "pss_releaseHandshakeKey", rpubkey, topic, lsendsymkeyids[0], true) - if err != nil { - t.Fatalf("release left send key %s fail: %v", lsendsymkeyids[0], err) - } - - var newlhsendkeyids []string - - // send new handshake request, should now receive one key - // check that it is not in previous right recv key array - err = clients[0].Call(&newlhsendkeyids, "pss_handshake", rpubkey, topic, true, false) - if err != nil { - t.Fatalf("handshake send fail: %v", err) - } else if len(newlhsendkeyids) != defaultSymKeyCapacity { - t.Fatalf("wrong receive count, expected 1, got %d", len(newlhsendkeyids)) - } - - var newlrecvsymkey string - err = clients[0].Call(&newlrecvsymkey, "pss_getSymmetricKey", newlhsendkeyids[0]) - if err != nil { - t.Fatal(err) - } - var rmatchsymkeyid *string - for i, id := range rrecvsymkeyids { - var key string - err = clients[1].Call(&key, "pss_getSymmetricKey", id) - if err != nil { - t.Fatal(err) - } - if newlrecvsymkey == key { - rmatchsymkeyid = &rrecvsymkeyids[i] - } - } - if rmatchsymkeyid != nil { - t.Fatalf("right sent old key id %s in second handshake", *rmatchsymkeyid) - } - - // clean the pss core keystore. Should clean the key released earlier - var cleancount int - clients[0].Call(&cleancount, "psstest_clean") - if cleancount > 1 { - t.Fatalf("pss clean count mismatch; expected 1, got %d", cleancount) - } -} diff --git a/swarm/pss/keystore.go b/swarm/pss/keystore.go deleted file mode 100644 index f143662b6b1e..000000000000 --- a/swarm/pss/keystore.go +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package pss - -import ( - "crypto/ecdsa" - "errors" - "fmt" - "sync" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/swarm/log" - whisper "github.com/ubiq/go-ubiq/whisper/whisperv6" -) - -type KeyStore struct { - w *whisper.Whisper // key and encryption backend - - mx sync.RWMutex - pubKeyPool map[string]map[Topic]*pssPeer // mapping of hex public keys to peer address by topic. - symKeyPool map[string]map[Topic]*pssPeer // mapping of symkeyids to peer address by topic. - symKeyDecryptCache []*string // fast lookup of symkeys recently used for decryption; last used is on top of stack - symKeyDecryptCacheCursor int // modular cursor pointing to last used, wraps on symKeyDecryptCache array -} - -func loadKeyStore() *KeyStore { - return &KeyStore{ - w: whisper.New(&whisper.DefaultConfig), - - pubKeyPool: make(map[string]map[Topic]*pssPeer), - symKeyPool: make(map[string]map[Topic]*pssPeer), - symKeyDecryptCache: make([]*string, defaultSymKeyCacheCapacity), - } -} - -func (ks *KeyStore) isSymKeyStored(key string) bool { - ks.mx.RLock() - defer ks.mx.RUnlock() - var ok bool - _, ok = ks.symKeyPool[key] - return ok -} - -func (ks *KeyStore) isPubKeyStored(key string) bool { - ks.mx.RLock() - defer ks.mx.RUnlock() - var ok bool - _, ok = ks.pubKeyPool[key] - return ok -} - -func (ks *KeyStore) getPeerSym(symkeyid string, topic Topic) (*pssPeer, bool) { - ks.mx.RLock() - defer ks.mx.RUnlock() - psp, ok := ks.symKeyPool[symkeyid][topic] - return psp, ok -} - -func (ks *KeyStore) getPeerPub(pubkeyid string, topic Topic) (*pssPeer, bool) { - ks.mx.RLock() - defer ks.mx.RUnlock() - psp, ok := ks.pubKeyPool[pubkeyid][topic] - return psp, ok -} - -// Links a peer ECDSA public key to a topic. -// This is required for asymmetric message exchange on the given topic. -// The value in `address` will be used as a routing hint for the public key / topic association. -func (ks *KeyStore) SetPeerPublicKey(pubkey *ecdsa.PublicKey, topic Topic, address PssAddress) error { - if err := validateAddress(address); err != nil { - return err - } - pubkeybytes := crypto.FromECDSAPub(pubkey) - if len(pubkeybytes) == 0 { - return fmt.Errorf("invalid public key: %v", pubkey) - } - pubkeyid := common.ToHex(pubkeybytes) - psp := &pssPeer{ - address: address, - } - ks.mx.Lock() - if _, ok := ks.pubKeyPool[pubkeyid]; !ok { - ks.pubKeyPool[pubkeyid] = make(map[Topic]*pssPeer) - } - ks.pubKeyPool[pubkeyid][topic] = psp - ks.mx.Unlock() - log.Trace("added pubkey", "pubkeyid", pubkeyid, "topic", topic, "address", address) - return nil -} - -// adds a symmetric key to the pss key pool, and optionally adds the key to the -// collection of keys used to attempt symmetric decryption of incoming messages -func (ks *KeyStore) addSymmetricKeyToPool(keyid string, topic Topic, address PssAddress, addtocache bool, protected bool) { - psp := &pssPeer{ - address: address, - protected: protected, - } - ks.mx.Lock() - if _, ok := ks.symKeyPool[keyid]; !ok { - ks.symKeyPool[keyid] = make(map[Topic]*pssPeer) - } - ks.symKeyPool[keyid][topic] = psp - ks.mx.Unlock() - if addtocache { - ks.symKeyDecryptCacheCursor++ - ks.symKeyDecryptCache[ks.symKeyDecryptCacheCursor%cap(ks.symKeyDecryptCache)] = &keyid - } -} - -// Returns all recorded topic and address combination for a specific public key -func (ks *KeyStore) GetPublickeyPeers(keyid string) (topic []Topic, address []PssAddress, err error) { - ks.mx.RLock() - defer ks.mx.RUnlock() - for t, peer := range ks.pubKeyPool[keyid] { - topic = append(topic, t) - address = append(address, peer.address) - } - return topic, address, nil -} - -func (ks *KeyStore) getPeerAddress(keyid string, topic Topic) (PssAddress, error) { - ks.mx.RLock() - defer ks.mx.RUnlock() - if peers, ok := ks.pubKeyPool[keyid]; ok { - if t, ok := peers[topic]; ok { - return t.address, nil - } - } - return nil, fmt.Errorf("peer with pubkey %s, topic %x not found", keyid, topic) -} - -// Attempt to decrypt, validate and unpack a symmetrically encrypted message. -// If successful, returns the unpacked whisper ReceivedMessage struct -// encapsulating the decrypted message, and the whisper backend id -// of the symmetric key used to decrypt the message. -// It fails if decryption of the message fails or if the message is corrupted. -func (ks *KeyStore) processSym(envelope *whisper.Envelope) (*whisper.ReceivedMessage, string, PssAddress, error) { - metrics.GetOrRegisterCounter("pss.process.sym", nil).Inc(1) - - for i := ks.symKeyDecryptCacheCursor; i > ks.symKeyDecryptCacheCursor-cap(ks.symKeyDecryptCache) && i > 0; i-- { - symkeyid := ks.symKeyDecryptCache[i%cap(ks.symKeyDecryptCache)] - symkey, err := ks.w.GetSymKey(*symkeyid) - if err != nil { - continue - } - recvmsg, err := envelope.OpenSymmetric(symkey) - if err != nil { - continue - } - if !recvmsg.ValidateAndParse() { - return nil, "", nil, errors.New("symmetrically encrypted message has invalid signature or is corrupt") - } - var from PssAddress - ks.mx.RLock() - if ks.symKeyPool[*symkeyid][Topic(envelope.Topic)] != nil { - from = ks.symKeyPool[*symkeyid][Topic(envelope.Topic)].address - } - ks.mx.RUnlock() - ks.symKeyDecryptCacheCursor++ - ks.symKeyDecryptCache[ks.symKeyDecryptCacheCursor%cap(ks.symKeyDecryptCache)] = symkeyid - return recvmsg, *symkeyid, from, nil - } - return nil, "", nil, errors.New("could not decrypt message") -} - -// Attempt to decrypt, validate and unpack an asymmetrically encrypted message. -// If successful, returns the unpacked whisper ReceivedMessage struct -// encapsulating the decrypted message, and the byte representation of -// the public key used to decrypt the message. -// It fails if decryption of message fails, or if the message is corrupted. -func (ks *Pss) processAsym(envelope *whisper.Envelope) (*whisper.ReceivedMessage, string, PssAddress, error) { - metrics.GetOrRegisterCounter("pss.process.asym", nil).Inc(1) - - recvmsg, err := envelope.OpenAsymmetric(ks.privateKey) - if err != nil { - return nil, "", nil, fmt.Errorf("could not decrypt message: %s", err) - } - // check signature (if signed), strip padding - if !recvmsg.ValidateAndParse() { - return nil, "", nil, errors.New("invalid message") - } - pubkeyid := common.ToHex(crypto.FromECDSAPub(recvmsg.Src)) - var from PssAddress - ks.mx.RLock() - if ks.pubKeyPool[pubkeyid][Topic(envelope.Topic)] != nil { - from = ks.pubKeyPool[pubkeyid][Topic(envelope.Topic)].address - } - ks.mx.RUnlock() - return recvmsg, pubkeyid, from, nil -} - -// Symkey garbage collection -// a key is removed if: -// - it is not marked as protected -// - it is not in the incoming decryption cache -func (ks *Pss) cleanKeys() (count int) { - for keyid, peertopics := range ks.symKeyPool { - var expiredtopics []Topic - for topic, psp := range peertopics { - if psp.protected { - continue - } - - var match bool - for i := ks.symKeyDecryptCacheCursor; i > ks.symKeyDecryptCacheCursor-cap(ks.symKeyDecryptCache) && i > 0; i-- { - cacheid := ks.symKeyDecryptCache[i%cap(ks.symKeyDecryptCache)] - if *cacheid == keyid { - match = true - } - } - if !match { - expiredtopics = append(expiredtopics, topic) - } - } - for _, topic := range expiredtopics { - ks.mx.Lock() - delete(ks.symKeyPool[keyid], topic) - log.Trace("symkey cleanup deletion", "symkeyid", keyid, "topic", topic, "val", ks.symKeyPool[keyid]) - ks.mx.Unlock() - count++ - } - } - return count -} - -// Automatically generate a new symkey for a topic and address hint -func (ks *KeyStore) GenerateSymmetricKey(topic Topic, address PssAddress, addToCache bool) (string, error) { - keyid, err := ks.w.GenerateSymKey() - if err == nil { - ks.addSymmetricKeyToPool(keyid, topic, address, addToCache, false) - } - return keyid, err -} - -// Returns a symmetric key byte sequence stored in the whisper backend by its unique id. -// Passes on the error value from the whisper backend. -func (ks *KeyStore) GetSymmetricKey(symkeyid string) ([]byte, error) { - return ks.w.GetSymKey(symkeyid) -} - -// Links a peer symmetric key (arbitrary byte sequence) to a topic. -// -// This is required for symmetrically encrypted message exchange on the given topic. -// -// The key is stored in the whisper backend. -// -// If addtocache is set to true, the key will be added to the cache of keys -// used to attempt symmetric decryption of incoming messages. -// -// Returns a string id that can be used to retrieve the key bytes -// from the whisper backend (see pss.GetSymmetricKey()) -func (ks *KeyStore) SetSymmetricKey(key []byte, topic Topic, address PssAddress, addtocache bool) (string, error) { - if err := validateAddress(address); err != nil { - return "", err - } - return ks.setSymmetricKey(key, topic, address, addtocache, true) -} - -func (ks *KeyStore) setSymmetricKey(key []byte, topic Topic, address PssAddress, addtocache bool, protected bool) (string, error) { - keyid, err := ks.w.AddSymKeyDirect(key) - if err == nil { - ks.addSymmetricKeyToPool(keyid, topic, address, addtocache, protected) - } - return keyid, err -} diff --git a/swarm/pss/notify/notify.go b/swarm/pss/notify/notify.go deleted file mode 100644 index 661c36bdcc8e..000000000000 --- a/swarm/pss/notify/notify.go +++ /dev/null @@ -1,394 +0,0 @@ -package notify - -import ( - "crypto/ecdsa" - "fmt" - "sync" - - "github.com/ubiq/go-ubiq/common/hexutil" - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/rlp" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/pss" -) - -const ( - // sent from requester to updater to request start of notifications - MsgCodeStart = iota - - // sent from updater to requester, contains a notification plus a new symkey to replace the old - MsgCodeNotifyWithKey - - // sent from updater to requester, contains a notification - MsgCodeNotify - - // sent from requester to updater to request stop of notifications (currently unused) - MsgCodeStop - MsgCodeMax -) - -const ( - DefaultAddressLength = 1 - symKeyLength = 32 // this should be gotten from source -) - -var ( - // control topic is used before symmetric key issuance completes - controlTopic = pss.Topic{0x00, 0x00, 0x00, 0x01} -) - -// when code is MsgCodeStart, Payload is address -// when code is MsgCodeNotifyWithKey, Payload is notification | symkey -// when code is MsgCodeNotify, Payload is notification -// when code is MsgCodeStop, Payload is address -type Msg struct { - Code byte - Name []byte - Payload []byte - namestring string -} - -// NewMsg creates a new notification message object -func NewMsg(code byte, name string, payload []byte) *Msg { - return &Msg{ - Code: code, - Name: []byte(name), - Payload: payload, - namestring: name, - } -} - -// NewMsgFromPayload decodes a serialized message payload into a new notification message object -func NewMsgFromPayload(payload []byte) (*Msg, error) { - msg := &Msg{} - err := rlp.DecodeBytes(payload, msg) - if err != nil { - return nil, err - } - msg.namestring = string(msg.Name) - return msg, nil -} - -// a notifier has one sendBin entry for each address space it sends messages to -type sendBin struct { - address pss.PssAddress - symKeyId string - count int -} - -// represents a single notification service -// only subscription address bins that match the address of a notification client have entries. -type notifier struct { - bins map[string]*sendBin - topic pss.Topic // identifies the resource for pss receiver - threshold int // amount of address bytes used in bins - updateC <-chan []byte - quitC chan struct{} -} - -func (n *notifier) removeSubscription() { - n.quitC <- struct{}{} -} - -// represents an individual subscription made by a public key at a specific address/neighborhood -type subscription struct { - pubkeyId string - address pss.PssAddress - handler func(string, []byte) error -} - -// Controller is the interface to control, add and remove notification services and subscriptions -type Controller struct { - pss *pss.Pss - notifiers map[string]*notifier - subscriptions map[string]*subscription - mu sync.Mutex -} - -// NewController creates a new Controller object -func NewController(ps *pss.Pss) *Controller { - ctrl := &Controller{ - pss: ps, - notifiers: make(map[string]*notifier), - subscriptions: make(map[string]*subscription), - } - ctrl.pss.Register(&controlTopic, pss.NewHandler(ctrl.Handler)) - return ctrl -} - -// IsActive is used to check if a notification service exists for a specified id string -// Returns true if exists, false if not -func (c *Controller) IsActive(name string) bool { - c.mu.Lock() - defer c.mu.Unlock() - return c.isActive(name) -} - -func (c *Controller) isActive(name string) bool { - _, ok := c.notifiers[name] - return ok -} - -// Subscribe is used by a client to request notifications from a notification service provider -// It will create a MsgCodeStart message and send asymmetrically to the provider using its public key and routing address -// The handler function is a callback that will be called when notifications are received -// Fails if the request pss cannot be sent or if the update message could not be serialized -func (c *Controller) Subscribe(name string, pubkey *ecdsa.PublicKey, address pss.PssAddress, handler func(string, []byte) error) error { - c.mu.Lock() - defer c.mu.Unlock() - msg := NewMsg(MsgCodeStart, name, c.pss.BaseAddr()) - c.pss.SetPeerPublicKey(pubkey, controlTopic, address) - pubkeyId := hexutil.Encode(crypto.FromECDSAPub(pubkey)) - smsg, err := rlp.EncodeToBytes(msg) - if err != nil { - return err - } - err = c.pss.SendAsym(pubkeyId, controlTopic, smsg) - if err != nil { - return err - } - c.subscriptions[name] = &subscription{ - pubkeyId: pubkeyId, - address: address, - handler: handler, - } - return nil -} - -// Unsubscribe, perhaps unsurprisingly, undoes the effects of Subscribe -// Fails if the subscription does not exist, if the request pss cannot be sent or if the update message could not be serialized -func (c *Controller) Unsubscribe(name string) error { - c.mu.Lock() - defer c.mu.Unlock() - sub, ok := c.subscriptions[name] - if !ok { - return fmt.Errorf("Unknown subscription '%s'", name) - } - msg := NewMsg(MsgCodeStop, name, sub.address) - smsg, err := rlp.EncodeToBytes(msg) - if err != nil { - return err - } - err = c.pss.SendAsym(sub.pubkeyId, controlTopic, smsg) - if err != nil { - return err - } - delete(c.subscriptions, name) - return nil -} - -// NewNotifier is used by a notification service provider to create a new notification service -// It takes a name as identifier for the resource, a threshold indicating the granularity of the subscription address bin -// It then starts an event loop which listens to the supplied update channel and executes notifications on channel receives -// Fails if a notifier already is registered on the name -//func (c *Controller) NewNotifier(name string, threshold int, contentFunc func(string) ([]byte, error)) error { -func (c *Controller) NewNotifier(name string, threshold int, updateC <-chan []byte) (func(), error) { - c.mu.Lock() - if c.isActive(name) { - c.mu.Unlock() - return nil, fmt.Errorf("Notification service %s already exists in controller", name) - } - quitC := make(chan struct{}) - c.notifiers[name] = ¬ifier{ - bins: make(map[string]*sendBin), - topic: pss.BytesToTopic([]byte(name)), - threshold: threshold, - updateC: updateC, - quitC: quitC, - //contentFunc: contentFunc, - } - c.mu.Unlock() - go func() { - for { - select { - case <-quitC: - return - case data := <-updateC: - c.notify(name, data) - } - } - }() - - return c.notifiers[name].removeSubscription, nil -} - -// RemoveNotifier is used to stop a notification service. -// It cancels the event loop listening to the notification provider's update channel -func (c *Controller) RemoveNotifier(name string) error { - c.mu.Lock() - defer c.mu.Unlock() - currentNotifier, ok := c.notifiers[name] - if !ok { - return fmt.Errorf("Unknown notification service %s", name) - } - currentNotifier.removeSubscription() - delete(c.notifiers, name) - return nil -} - -// Notify is called by a notification service provider to issue a new notification -// It takes the name of the notification service and the data to be sent. -// It fails if a notifier with this name does not exist or if data could not be serialized -// Note that it does NOT fail on failure to send a message -func (c *Controller) notify(name string, data []byte) error { - c.mu.Lock() - defer c.mu.Unlock() - if !c.isActive(name) { - return fmt.Errorf("Notification service %s doesn't exist", name) - } - msg := NewMsg(MsgCodeNotify, name, data) - smsg, err := rlp.EncodeToBytes(msg) - if err != nil { - return err - } - for _, m := range c.notifiers[name].bins { - log.Debug("sending pss notify", "name", name, "addr", fmt.Sprintf("%x", m.address), "topic", fmt.Sprintf("%x", c.notifiers[name].topic), "data", data) - go func(m *sendBin) { - err = c.pss.SendSym(m.symKeyId, c.notifiers[name].topic, smsg) - if err != nil { - log.Warn("Failed to send notify to addr %x: %v", m.address, err) - } - }(m) - } - return nil -} - -// check if we already have the bin -// if we do, retrieve the symkey from it and increment the count -// if we dont make a new symkey and a new bin entry -func (c *Controller) addToBin(ntfr *notifier, address []byte) (symKeyId string, pssAddress pss.PssAddress, err error) { - - // parse the address from the message and truncate if longer than our bins threshold - if len(address) > ntfr.threshold { - address = address[:ntfr.threshold] - } - - pssAddress = pss.PssAddress(address) - hexAddress := fmt.Sprintf("%x", address) - currentBin, ok := ntfr.bins[hexAddress] - if ok { - currentBin.count++ - symKeyId = currentBin.symKeyId - } else { - symKeyId, err = c.pss.GenerateSymmetricKey(ntfr.topic, pssAddress, false) - if err != nil { - return "", nil, err - } - ntfr.bins[hexAddress] = &sendBin{ - address: address, - symKeyId: symKeyId, - count: 1, - } - } - return symKeyId, pssAddress, nil -} - -func (c *Controller) handleStartMsg(msg *Msg, keyid string) (err error) { - - keyidbytes, err := hexutil.Decode(keyid) - if err != nil { - return err - } - pubkey, err := crypto.UnmarshalPubkey(keyidbytes) - if err != nil { - return err - } - - // if name is not registered for notifications we will not react - currentNotifier, ok := c.notifiers[msg.namestring] - if !ok { - return fmt.Errorf("Subscribe attempted on unknown resource '%s'", msg.namestring) - } - - // add to or open new bin - symKeyId, pssAddress, err := c.addToBin(currentNotifier, msg.Payload) - if err != nil { - return err - } - - // add to address book for send initial notify - symkey, err := c.pss.GetSymmetricKey(symKeyId) - if err != nil { - return err - } - err = c.pss.SetPeerPublicKey(pubkey, controlTopic, pssAddress) - if err != nil { - return err - } - - // TODO this is set to zero-length byte pending decision on protocol for initial message, whether it should include message or not, and how to trigger the initial message so that current state of Swarm feed is sent upon subscription - notify := []byte{} - replyMsg := NewMsg(MsgCodeNotifyWithKey, msg.namestring, make([]byte, len(notify)+symKeyLength)) - copy(replyMsg.Payload, notify) - copy(replyMsg.Payload[len(notify):], symkey) - sReplyMsg, err := rlp.EncodeToBytes(replyMsg) - if err != nil { - return err - } - return c.pss.SendAsym(keyid, controlTopic, sReplyMsg) -} - -func (c *Controller) handleNotifyWithKeyMsg(msg *Msg) error { - symkey := msg.Payload[len(msg.Payload)-symKeyLength:] - topic := pss.BytesToTopic(msg.Name) - - // \TODO keep track of and add actual address - updaterAddr := pss.PssAddress([]byte{}) - c.pss.SetSymmetricKey(symkey, topic, updaterAddr, true) - c.pss.Register(&topic, pss.NewHandler(c.Handler)) - return c.subscriptions[msg.namestring].handler(msg.namestring, msg.Payload[:len(msg.Payload)-symKeyLength]) -} - -func (c *Controller) handleStopMsg(msg *Msg) error { - // if name is not registered for notifications we will not react - currentNotifier, ok := c.notifiers[msg.namestring] - if !ok { - return fmt.Errorf("Unsubscribe attempted on unknown resource '%s'", msg.namestring) - } - - // parse the address from the message and truncate if longer than our bins' address length threshold - address := msg.Payload - if len(msg.Payload) > currentNotifier.threshold { - address = address[:currentNotifier.threshold] - } - - // remove the entry from the bin if it exists, and remove the bin if it's the last remaining one - hexAddress := fmt.Sprintf("%x", address) - currentBin, ok := currentNotifier.bins[hexAddress] - if !ok { - return fmt.Errorf("found no active bin for address %s", hexAddress) - } - currentBin.count-- - if currentBin.count == 0 { // if no more clients in this bin, remove it - delete(currentNotifier.bins, hexAddress) - } - return nil -} - -// Handler is the pss topic handler to be used to process notification service messages -// It should be registered in the pss of both to any notification service provides and clients using the service -func (c *Controller) Handler(smsg []byte, p *p2p.Peer, asymmetric bool, keyid string) error { - c.mu.Lock() - defer c.mu.Unlock() - log.Debug("notify controller handler", "keyid", keyid) - - // see if the message is valid - msg, err := NewMsgFromPayload(smsg) - if err != nil { - return err - } - - switch msg.Code { - case MsgCodeStart: - return c.handleStartMsg(msg, keyid) - case MsgCodeNotifyWithKey: - return c.handleNotifyWithKeyMsg(msg) - case MsgCodeNotify: - return c.subscriptions[msg.namestring].handler(msg.namestring, msg.Payload) - case MsgCodeStop: - return c.handleStopMsg(msg) - } - - return fmt.Errorf("Invalid message code: %d", msg.Code) -} diff --git a/swarm/pss/notify/notify_test.go b/swarm/pss/notify/notify_test.go deleted file mode 100644 index b15b41e5a13c..000000000000 --- a/swarm/pss/notify/notify_test.go +++ /dev/null @@ -1,257 +0,0 @@ -package notify - -import ( - "bytes" - "context" - "flag" - "fmt" - "os" - "testing" - "time" - - "github.com/ubiq/go-ubiq/common/hexutil" - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/simulations" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" - "github.com/ubiq/go-ubiq/swarm/network" - "github.com/ubiq/go-ubiq/swarm/pss" - "github.com/ubiq/go-ubiq/swarm/state" - whisper "github.com/ubiq/go-ubiq/whisper/whisperv6" -) - -var ( - loglevel = flag.Int("l", 3, "loglevel") - psses map[string]*pss.Pss - w *whisper.Whisper - wapi *whisper.PublicWhisperAPI -) - -func init() { - flag.Parse() - hs := log.StreamHandler(os.Stderr, log.TerminalFormat(true)) - hf := log.LvlFilterHandler(log.Lvl(*loglevel), hs) - h := log.CallerFileHandler(hf) - log.Root().SetHandler(h) - - w = whisper.New(&whisper.DefaultConfig) - wapi = whisper.NewPublicWhisperAPI(w) - psses = make(map[string]*pss.Pss) -} - -// Creates a client node and notifier node -// Client sends pss notifications requests -// notifier sends initial notification with symmetric key, and -// second notification symmetrically encrypted -func TestStart(t *testing.T) { - adapter := adapters.NewSimAdapter(newServices(false)) - net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{ - ID: "0", - DefaultService: "bzz", - }) - leftNodeConf := adapters.RandomNodeConfig() - leftNodeConf.Services = []string{"bzz", "pss"} - leftNode, err := net.NewNodeWithConfig(leftNodeConf) - if err != nil { - t.Fatal(err) - } - err = net.Start(leftNode.ID()) - if err != nil { - t.Fatal(err) - } - - rightNodeConf := adapters.RandomNodeConfig() - rightNodeConf.Services = []string{"bzz", "pss"} - rightNode, err := net.NewNodeWithConfig(rightNodeConf) - if err != nil { - t.Fatal(err) - } - err = net.Start(rightNode.ID()) - if err != nil { - t.Fatal(err) - } - - err = net.Connect(rightNode.ID(), leftNode.ID()) - if err != nil { - t.Fatal(err) - } - - leftRpc, err := leftNode.Client() - if err != nil { - t.Fatal(err) - } - - rightRpc, err := rightNode.Client() - if err != nil { - t.Fatal(err) - } - - var leftAddr string - err = leftRpc.Call(&leftAddr, "pss_baseAddr") - if err != nil { - t.Fatal(err) - } - - var rightAddr string - err = rightRpc.Call(&rightAddr, "pss_baseAddr") - if err != nil { - t.Fatal(err) - } - - var leftPub string - err = leftRpc.Call(&leftPub, "pss_getPublicKey") - if err != nil { - t.Fatal(err) - } - - var rightPub string - err = rightRpc.Call(&rightPub, "pss_getPublicKey") - if err != nil { - t.Fatal(err) - } - - rsrcName := "foo.eth" - rsrcTopic := pss.BytesToTopic([]byte(rsrcName)) - - // wait for kademlia table to populate - time.Sleep(time.Second) - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*2) - defer cancel() - rmsgC := make(chan *pss.APIMsg) - rightSub, err := rightRpc.Subscribe(ctx, "pss", rmsgC, "receive", controlTopic, false, false) - if err != nil { - t.Fatal(err) - } - defer rightSub.Unsubscribe() - - updateC := make(chan []byte) - updateMsg := []byte{} - ctrlClient := NewController(psses[rightPub]) - ctrlNotifier := NewController(psses[leftPub]) - ctrlNotifier.NewNotifier("foo.eth", 2, updateC) - - pubkeybytes, err := hexutil.Decode(leftPub) - if err != nil { - t.Fatal(err) - } - pubkey, err := crypto.UnmarshalPubkey(pubkeybytes) - if err != nil { - t.Fatal(err) - } - addrbytes, err := hexutil.Decode(leftAddr) - if err != nil { - t.Fatal(err) - } - ctrlClient.Subscribe(rsrcName, pubkey, addrbytes, func(s string, b []byte) error { - if s != "foo.eth" || !bytes.Equal(updateMsg, b) { - t.Fatalf("unexpected result in client handler: '%s':'%x'", s, b) - } - log.Info("client handler receive", "s", s, "b", b) - return nil - }) - - var inMsg *pss.APIMsg - select { - case inMsg = <-rmsgC: - case <-ctx.Done(): - t.Fatal(ctx.Err()) - } - - dMsg, err := NewMsgFromPayload(inMsg.Msg) - if err != nil { - t.Fatal(err) - } - if dMsg.namestring != rsrcName { - t.Fatalf("expected name '%s', got '%s'", rsrcName, dMsg.namestring) - } - if !bytes.Equal(dMsg.Payload[:len(updateMsg)], updateMsg) { - t.Fatalf("expected payload first %d bytes '%x', got '%x'", len(updateMsg), updateMsg, dMsg.Payload[:len(updateMsg)]) - } - if len(updateMsg)+symKeyLength != len(dMsg.Payload) { - t.Fatalf("expected payload length %d, have %d", len(updateMsg)+symKeyLength, len(dMsg.Payload)) - } - - rightSubUpdate, err := rightRpc.Subscribe(ctx, "pss", rmsgC, "receive", rsrcTopic, false, false) - if err != nil { - t.Fatal(err) - } - defer rightSubUpdate.Unsubscribe() - - updateMsg = []byte("plugh") - updateC <- updateMsg - select { - case inMsg = <-rmsgC: - case <-ctx.Done(): - log.Error("timed out waiting for msg", "topic", fmt.Sprintf("%x", rsrcTopic)) - t.Fatal(ctx.Err()) - } - dMsg, err = NewMsgFromPayload(inMsg.Msg) - if err != nil { - t.Fatal(err) - } - if dMsg.namestring != rsrcName { - t.Fatalf("expected name %s, got %s", rsrcName, dMsg.namestring) - } - if !bytes.Equal(dMsg.Payload, updateMsg) { - t.Fatalf("expected payload '%x', got '%x'", updateMsg, dMsg.Payload) - } - -} - -func newServices(allowRaw bool) adapters.Services { - stateStore := state.NewInmemoryStore() - kademlias := make(map[enode.ID]*network.Kademlia) - kademlia := func(id enode.ID) *network.Kademlia { - if k, ok := kademlias[id]; ok { - return k - } - params := network.NewKadParams() - params.NeighbourhoodSize = 2 - params.MaxBinSize = 3 - params.MinBinSize = 1 - params.MaxRetries = 1000 - params.RetryExponent = 2 - params.RetryInterval = 1000000 - kademlias[id] = network.NewKademlia(id[:], params) - return kademlias[id] - } - return adapters.Services{ - "pss": func(ctx *adapters.ServiceContext) (node.Service, error) { - ctxlocal, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - keys, err := wapi.NewKeyPair(ctxlocal) - if err != nil { - return nil, err - } - privkey, err := w.GetPrivateKey(keys) - if err != nil { - return nil, err - } - pssp := pss.NewPssParams().WithPrivateKey(privkey) - pssp.MsgTTL = time.Second * 30 - pssp.AllowRaw = allowRaw - pskad := kademlia(ctx.Config.ID) - ps, err := pss.NewPss(pskad, pssp) - if err != nil { - return nil, err - } - //psses[common.ToHex(crypto.FromECDSAPub(&privkey.PublicKey))] = ps - psses[hexutil.Encode(crypto.FromECDSAPub(&privkey.PublicKey))] = ps - return ps, nil - }, - "bzz": func(ctx *adapters.ServiceContext) (node.Service, error) { - addr := network.NewAddr(ctx.Config.Node()) - hp := network.NewHiveParams() - hp.Discovery = false - config := &network.BzzConfig{ - OverlayAddr: addr.Over(), - UnderlayAddr: addr.Under(), - HiveParams: hp, - } - return network.NewBzz(config, kademlia(ctx.Config.ID), stateStore, nil, nil), nil - }, - } -} diff --git a/swarm/pss/ping.go b/swarm/pss/ping.go deleted file mode 100644 index a802a80c4cd7..000000000000 --- a/swarm/pss/ping.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build !nopssprotocol,!nopssping - -package pss - -import ( - "context" - "errors" - "time" - - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/p2p/protocols" - "github.com/ubiq/go-ubiq/swarm/log" -) - -// Generic ping protocol implementation for -// pss devp2p protocol emulation -type PingMsg struct { - Created time.Time - Pong bool // set if message is pong reply -} - -type Ping struct { - Pong bool // toggle pong reply upon ping receive - OutC chan bool // trigger ping - InC chan bool // optional, report back to calling code -} - -func (p *Ping) pingHandler(ctx context.Context, msg interface{}) error { - var pingmsg *PingMsg - var ok bool - if pingmsg, ok = msg.(*PingMsg); !ok { - return errors.New("invalid msg") - } - log.Debug("ping handler", "msg", pingmsg, "outc", p.OutC) - if p.InC != nil { - p.InC <- pingmsg.Pong - } - if p.Pong && !pingmsg.Pong { - p.OutC <- true - } - return nil -} - -var PingProtocol = &protocols.Spec{ - Name: "psstest", - Version: 1, - MaxMsgSize: 1024, - Messages: []interface{}{ - PingMsg{}, - }, -} - -var PingTopic = ProtocolTopic(PingProtocol) - -func NewPingProtocol(ping *Ping) *p2p.Protocol { - return &p2p.Protocol{ - Name: PingProtocol.Name, - Version: PingProtocol.Version, - Length: uint64(PingProtocol.MaxMsgSize), - Run: func(p *p2p.Peer, rw p2p.MsgReadWriter) error { - quitC := make(chan struct{}) - pp := protocols.NewPeer(p, rw, PingProtocol) - log.Trace("running pss vprotocol", "peer", p, "outc", ping.OutC) - go func() { - for { - select { - case ispong := <-ping.OutC: - pp.Send(context.TODO(), &PingMsg{ - Created: time.Now(), - Pong: ispong, - }) - case <-quitC: - } - } - }() - err := pp.Run(ping.pingHandler) - quitC <- struct{}{} - return err - }, - } -} diff --git a/swarm/pss/protocol.go b/swarm/pss/protocol.go deleted file mode 100644 index 588f4376c3f6..000000000000 --- a/swarm/pss/protocol.go +++ /dev/null @@ -1,283 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build !nopssprotocol - -package pss - -import ( - "bytes" - "fmt" - "sync" - "time" - - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/p2p/protocols" - "github.com/ubiq/go-ubiq/rlp" - "github.com/ubiq/go-ubiq/swarm/log" -) - -const ( - IsActiveProtocol = true -) - -// Convenience wrapper for devp2p protocol messages for transport over pss -type ProtocolMsg struct { - Code uint64 - Size uint32 - Payload []byte - ReceivedAt time.Time -} - -// Creates a ProtocolMsg -func NewProtocolMsg(code uint64, msg interface{}) ([]byte, error) { - - rlpdata, err := rlp.EncodeToBytes(msg) - if err != nil { - return nil, err - } - - // TODO verify that nested structs cannot be used in rlp - smsg := &ProtocolMsg{ - Code: code, - Size: uint32(len(rlpdata)), - Payload: rlpdata, - } - - return rlp.EncodeToBytes(smsg) -} - -// Protocol options to be passed to a new Protocol instance -// -// The parameters specify which encryption schemes to allow -type ProtocolParams struct { - Asymmetric bool - Symmetric bool -} - -// PssReadWriter bridges pss send/receive with devp2p protocol send/receive -// -// Implements p2p.MsgReadWriter -type PssReadWriter struct { - *Pss - LastActive time.Time - rw chan p2p.Msg - spec *protocols.Spec - topic *Topic - sendFunc func(string, Topic, []byte) error - key string - closed bool -} - -// Implements p2p.MsgReader -func (prw *PssReadWriter) ReadMsg() (p2p.Msg, error) { - msg := <-prw.rw - log.Trace(fmt.Sprintf("pssrw readmsg: %v", msg)) - return msg, nil -} - -// Implements p2p.MsgWriter -func (prw *PssReadWriter) WriteMsg(msg p2p.Msg) error { - log.Trace("pssrw writemsg", "msg", msg) - if prw.closed { - return fmt.Errorf("connection closed") - } - rlpdata := make([]byte, msg.Size) - msg.Payload.Read(rlpdata) - pmsg, err := rlp.EncodeToBytes(ProtocolMsg{ - Code: msg.Code, - Size: msg.Size, - Payload: rlpdata, - }) - if err != nil { - return err - } - return prw.sendFunc(prw.key, *prw.topic, pmsg) -} - -// Injects a p2p.Msg into the MsgReadWriter, so that it appears on the associated p2p.MsgReader -func (prw *PssReadWriter) injectMsg(msg p2p.Msg) error { - log.Trace(fmt.Sprintf("pssrw injectmsg: %v", msg)) - prw.rw <- msg - return nil -} - -// Convenience object for emulation devp2p over pss -type Protocol struct { - *Pss - proto *p2p.Protocol - topic *Topic - spec *protocols.Spec - pubKeyRWPool map[string]p2p.MsgReadWriter - symKeyRWPool map[string]p2p.MsgReadWriter - Asymmetric bool - Symmetric bool - RWPoolMu sync.Mutex -} - -// Activates devp2p emulation over a specific pss topic -// -// One or both encryption schemes must be specified. If -// only one is specified, the protocol will not be valid -// for the other, and will make the message handler -// return errors -func RegisterProtocol(ps *Pss, topic *Topic, spec *protocols.Spec, targetprotocol *p2p.Protocol, options *ProtocolParams) (*Protocol, error) { - if !options.Asymmetric && !options.Symmetric { - return nil, fmt.Errorf("specify at least one of asymmetric or symmetric messaging mode") - } - pp := &Protocol{ - Pss: ps, - proto: targetprotocol, - topic: topic, - spec: spec, - pubKeyRWPool: make(map[string]p2p.MsgReadWriter), - symKeyRWPool: make(map[string]p2p.MsgReadWriter), - Asymmetric: options.Asymmetric, - Symmetric: options.Symmetric, - } - return pp, nil -} - -// Generic handler for incoming messages over devp2p emulation -// -// To be passed to pss.Register() -// -// Will run the protocol on a new incoming peer, provided that -// the encryption key of the message has a match in the internal -// pss keypool -// -// Fails if protocol is not valid for the message encryption scheme, -// if adding a new peer fails, or if the message is not a serialized -// p2p.Msg (which it always will be if it is sent from this object). -func (p *Protocol) Handle(msg []byte, peer *p2p.Peer, asymmetric bool, keyid string) error { - var vrw *PssReadWriter - if p.Asymmetric != asymmetric && p.Symmetric == !asymmetric { - return fmt.Errorf("invalid protocol encryption") - } else if (!p.isActiveSymKey(keyid, *p.topic) && !asymmetric) || - (!p.isActiveAsymKey(keyid, *p.topic) && asymmetric) { - - rw, err := p.AddPeer(peer, *p.topic, asymmetric, keyid) - if err != nil { - return err - } else if rw == nil { - return fmt.Errorf("handle called on nil MsgReadWriter for new key " + keyid) - } - vrw = rw.(*PssReadWriter) - } - - pmsg, err := ToP2pMsg(msg) - if err != nil { - return fmt.Errorf("could not decode pssmsg") - } - if asymmetric { - if p.pubKeyRWPool[keyid] == nil { - return fmt.Errorf("handle called on nil MsgReadWriter for key " + keyid) - } - vrw = p.pubKeyRWPool[keyid].(*PssReadWriter) - } else { - if p.symKeyRWPool[keyid] == nil { - return fmt.Errorf("handle called on nil MsgReadWriter for key " + keyid) - } - vrw = p.symKeyRWPool[keyid].(*PssReadWriter) - } - vrw.injectMsg(pmsg) - return nil -} - -// check if (peer) symmetric key is currently registered with this topic -func (p *Protocol) isActiveSymKey(key string, topic Topic) bool { - return p.symKeyRWPool[key] != nil -} - -// check if (peer) asymmetric key is currently registered with this topic -func (p *Protocol) isActiveAsymKey(key string, topic Topic) bool { - return p.pubKeyRWPool[key] != nil -} - -// Creates a serialized (non-buffered) version of a p2p.Msg, used in the specialized internal p2p.MsgReadwriter implementations -func ToP2pMsg(msg []byte) (p2p.Msg, error) { - payload := &ProtocolMsg{} - if err := rlp.DecodeBytes(msg, payload); err != nil { - return p2p.Msg{}, fmt.Errorf("pss protocol handler unable to decode payload as p2p message: %v", err) - } - - return p2p.Msg{ - Code: payload.Code, - Size: uint32(len(payload.Payload)), - ReceivedAt: time.Now(), - Payload: bytes.NewBuffer(payload.Payload), - }, nil -} - -// Runs an emulated pss Protocol on the specified peer, -// linked to a specific topic -// `key` and `asymmetric` specifies what encryption key -// to link the peer to. -// The key must exist in the pss store prior to adding the peer. -func (p *Protocol) AddPeer(peer *p2p.Peer, topic Topic, asymmetric bool, key string) (p2p.MsgReadWriter, error) { - rw := &PssReadWriter{ - Pss: p.Pss, - rw: make(chan p2p.Msg), - spec: p.spec, - topic: p.topic, - key: key, - } - if asymmetric { - rw.sendFunc = p.Pss.SendAsym - } else { - rw.sendFunc = p.Pss.SendSym - } - if asymmetric { - if !p.Pss.isPubKeyStored(key) { - return nil, fmt.Errorf("asym key does not exist: %s", key) - } - p.RWPoolMu.Lock() - p.pubKeyRWPool[key] = rw - p.RWPoolMu.Unlock() - } else { - if !p.Pss.isSymKeyStored(key) { - return nil, fmt.Errorf("symkey does not exist: %s", key) - } - p.RWPoolMu.Lock() - p.symKeyRWPool[key] = rw - p.RWPoolMu.Unlock() - } - go func() { - err := p.proto.Run(peer, rw) - log.Warn(fmt.Sprintf("pss vprotocol quit on %v topic %v: %v", peer, topic, err)) - }() - return rw, nil -} - -func (p *Protocol) RemovePeer(asymmetric bool, key string) { - log.Debug("closing pss peer", "asym", asymmetric, "key", key) - p.RWPoolMu.Lock() - defer p.RWPoolMu.Unlock() - if asymmetric { - rw := p.pubKeyRWPool[key].(*PssReadWriter) - rw.closed = true - delete(p.pubKeyRWPool, key) - } else { - rw := p.symKeyRWPool[key].(*PssReadWriter) - rw.closed = true - delete(p.symKeyRWPool, key) - } -} - -// Uniform translation of protocol specifiers to topic -func ProtocolTopic(spec *protocols.Spec) Topic { - return BytesToTopic([]byte(fmt.Sprintf("%s:%d", spec.Name, spec.Version))) -} diff --git a/swarm/pss/protocol_none.go b/swarm/pss/protocol_none.go deleted file mode 100644 index c92be3f902e7..000000000000 --- a/swarm/pss/protocol_none.go +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// +build nopssprotocol - -package pss - -const ( - IsActiveProtocol = false -) diff --git a/swarm/pss/protocol_test.go b/swarm/pss/protocol_test.go deleted file mode 100644 index bf2ba587018a..000000000000 --- a/swarm/pss/protocol_test.go +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package pss - -import ( - "bytes" - "context" - "fmt" - "strconv" - "strings" - "testing" - "time" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/swarm/log" -) - -type protoCtrl struct { - C chan bool - protocol *Protocol - run func(*p2p.Peer, p2p.MsgReadWriter) error -} - -// simple ping pong protocol test for the pss devp2p emulation -func TestProtocol(t *testing.T) { - t.Run("32", testProtocol) - t.Run("8", testProtocol) - t.Run("0", testProtocol) -} - -func testProtocol(t *testing.T) { - - // address hint size - var addrsize int64 - paramstring := strings.Split(t.Name(), "/") - addrsize, _ = strconv.ParseInt(paramstring[1], 10, 0) - log.Info("protocol test", "addrsize", addrsize) - - topic := PingTopic.String() - - clients, err := setupNetwork(2, false) - if err != nil { - t.Fatal(err) - } - var loaddrhex string - err = clients[0].Call(&loaddrhex, "pss_baseAddr") - if err != nil { - t.Fatalf("rpc get node 1 baseaddr fail: %v", err) - } - loaddrhex = loaddrhex[:2+(addrsize*2)] - var roaddrhex string - err = clients[1].Call(&roaddrhex, "pss_baseAddr") - if err != nil { - t.Fatalf("rpc get node 2 baseaddr fail: %v", err) - } - roaddrhex = roaddrhex[:2+(addrsize*2)] - lnodeinfo := &p2p.NodeInfo{} - err = clients[0].Call(&lnodeinfo, "admin_nodeInfo") - if err != nil { - t.Fatalf("rpc nodeinfo node 11 fail: %v", err) - } - - var lpubkey string - err = clients[0].Call(&lpubkey, "pss_getPublicKey") - if err != nil { - t.Fatalf("rpc get node 1 pubkey fail: %v", err) - } - var rpubkey string - err = clients[1].Call(&rpubkey, "pss_getPublicKey") - if err != nil { - t.Fatalf("rpc get node 2 pubkey fail: %v", err) - } - - time.Sleep(time.Millisecond * 1000) // replace with hive healthy code - - lmsgC := make(chan APIMsg) - lctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - defer cancel() - lsub, err := clients[0].Subscribe(lctx, "pss", lmsgC, "receive", topic, false, false) - if err != nil { - t.Fatal(err) - } - defer lsub.Unsubscribe() - rmsgC := make(chan APIMsg) - rctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - defer cancel() - rsub, err := clients[1].Subscribe(rctx, "pss", rmsgC, "receive", topic, false, false) - if err != nil { - t.Fatal(err) - } - defer rsub.Unsubscribe() - - // set reciprocal public keys - err = clients[0].Call(nil, "pss_setPeerPublicKey", rpubkey, topic, roaddrhex) - if err != nil { - t.Fatal(err) - } - err = clients[1].Call(nil, "pss_setPeerPublicKey", lpubkey, topic, loaddrhex) - if err != nil { - t.Fatal(err) - } - - // add right peer's public key as protocol peer on left - p := p2p.NewPeer(enode.ID{}, fmt.Sprintf("%x", common.FromHex(loaddrhex)), []p2p.Cap{}) - _, err = pssprotocols[lnodeinfo.ID].protocol.AddPeer(p, PingTopic, true, rpubkey) - if err != nil { - t.Fatal(err) - } - - // sends ping asym, checks delivery - pssprotocols[lnodeinfo.ID].C <- false - select { - case <-lmsgC: - log.Debug("lnode ok") - case cerr := <-lctx.Done(): - t.Fatalf("test message timed out: %v", cerr) - return - } - select { - case <-rmsgC: - log.Debug("rnode ok") - case cerr := <-lctx.Done(): - t.Fatalf("test message timed out: %v", cerr) - } - - // sends ping asym, checks delivery - pssprotocols[lnodeinfo.ID].C <- false - select { - case <-lmsgC: - log.Debug("lnode ok") - case cerr := <-lctx.Done(): - t.Fatalf("test message timed out: %v", cerr) - } - select { - case <-rmsgC: - log.Debug("rnode ok") - case cerr := <-lctx.Done(): - t.Fatalf("test message timed out: %v", cerr) - } - rw := pssprotocols[lnodeinfo.ID].protocol.pubKeyRWPool[rpubkey] - pssprotocols[lnodeinfo.ID].protocol.RemovePeer(true, rpubkey) - if err := rw.WriteMsg(p2p.Msg{ - Size: 3, - Payload: bytes.NewReader([]byte("foo")), - }); err == nil { - t.Fatalf("expected error on write") - } -} diff --git a/swarm/pss/pss.go b/swarm/pss/pss.go deleted file mode 100644 index 6bccc0af4255..000000000000 --- a/swarm/pss/pss.go +++ /dev/null @@ -1,840 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package pss - -import ( - "bytes" - "context" - "crypto/ecdsa" - "crypto/rand" - "errors" - "fmt" - "hash" - "sync" - "time" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/protocols" - "github.com/ubiq/go-ubiq/rpc" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/network" - "github.com/ubiq/go-ubiq/swarm/pot" - "github.com/ubiq/go-ubiq/swarm/storage" - whisper "github.com/ubiq/go-ubiq/whisper/whisperv6" - "golang.org/x/crypto/sha3" -) - -const ( - defaultPaddingByteSize = 16 - DefaultMsgTTL = time.Second * 120 - defaultDigestCacheTTL = time.Second * 10 - defaultSymKeyCacheCapacity = 512 - digestLength = 32 // byte length of digest used for pss cache (currently same as swarm chunk hash) - defaultWhisperWorkTime = 3 - defaultWhisperPoW = 0.0000000001 - defaultMaxMsgSize = 1024 * 1024 - defaultCleanInterval = time.Second * 60 * 10 - defaultOutboxCapacity = 100000 - pssProtocolName = "pss" - pssVersion = 2 - hasherCount = 8 -) - -var ( - addressLength = len(pot.Address{}) -) - -// cache is used for preventing backwards routing -// will also be instrumental in flood guard mechanism -// and mailbox implementation -type pssCacheEntry struct { - expiresAt time.Time -} - -// abstraction to enable access to p2p.protocols.Peer.Send -type senderPeer interface { - Info() *p2p.PeerInfo - ID() enode.ID - Address() []byte - Send(context.Context, interface{}) error -} - -// per-key peer related information -// member `protected` prevents garbage collection of the instance -type pssPeer struct { - lastSeen time.Time - address PssAddress - protected bool -} - -// Pss configuration parameters -type PssParams struct { - MsgTTL time.Duration - CacheTTL time.Duration - privateKey *ecdsa.PrivateKey - SymKeyCacheCapacity int - AllowRaw bool // If true, enables sending and receiving messages without builtin pss encryption -} - -// Sane defaults for Pss -func NewPssParams() *PssParams { - return &PssParams{ - MsgTTL: DefaultMsgTTL, - CacheTTL: defaultDigestCacheTTL, - SymKeyCacheCapacity: defaultSymKeyCacheCapacity, - } -} - -func (params *PssParams) WithPrivateKey(privatekey *ecdsa.PrivateKey) *PssParams { - params.privateKey = privatekey - return params -} - -// Toplevel pss object, takes care of message sending, receiving, decryption and encryption, message handler dispatchers and message forwarding. -// -// Implements node.Service -type Pss struct { - *network.Kademlia // we can get the Kademlia address from this - *KeyStore - - privateKey *ecdsa.PrivateKey // pss can have it's own independent key - auxAPIs []rpc.API // builtins (handshake, test) can add APIs - - // sending and forwarding - fwdPool map[string]*protocols.Peer // keep track of all peers sitting on the pssmsg routing layer - fwdPoolMu sync.RWMutex - fwdCache map[pssDigest]pssCacheEntry // checksum of unique fields from pssmsg mapped to expiry, cache to determine whether to drop msg - fwdCacheMu sync.RWMutex - cacheTTL time.Duration // how long to keep messages in fwdCache (not implemented) - msgTTL time.Duration - paddingByteSize int - capstring string - outbox chan *PssMsg - - // message handling - handlers map[Topic]map[*handler]bool // topic and version based pss payload handlers. See pss.Handle() - handlersMu sync.RWMutex - hashPool sync.Pool - topicHandlerCaps map[Topic]*handlerCaps // caches capabilities of each topic's handlers (see handlerCap* consts in types.go) - - // process - quitC chan struct{} -} - -func (p *Pss) String() string { - return fmt.Sprintf("pss: addr %x, pubkey %v", p.BaseAddr(), common.ToHex(crypto.FromECDSAPub(&p.privateKey.PublicKey))) -} - -// Creates a new Pss instance. -// -// In addition to params, it takes a swarm network Kademlia -// and a FileStore storage for message cache storage. -func NewPss(k *network.Kademlia, params *PssParams) (*Pss, error) { - if params.privateKey == nil { - return nil, errors.New("missing private key for pss") - } - cap := p2p.Cap{ - Name: pssProtocolName, - Version: pssVersion, - } - ps := &Pss{ - Kademlia: k, - KeyStore: loadKeyStore(), - - privateKey: params.privateKey, - quitC: make(chan struct{}), - - fwdPool: make(map[string]*protocols.Peer), - fwdCache: make(map[pssDigest]pssCacheEntry), - cacheTTL: params.CacheTTL, - msgTTL: params.MsgTTL, - paddingByteSize: defaultPaddingByteSize, - capstring: cap.String(), - outbox: make(chan *PssMsg, defaultOutboxCapacity), - - handlers: make(map[Topic]map[*handler]bool), - topicHandlerCaps: make(map[Topic]*handlerCaps), - - hashPool: sync.Pool{ - New: func() interface{} { - return sha3.NewLegacyKeccak256() - }, - }, - } - - for i := 0; i < hasherCount; i++ { - hashfunc := storage.MakeHashFunc(storage.DefaultHash)() - ps.hashPool.Put(hashfunc) - } - - return ps, nil -} - -///////////////////////////////////////////////////////////////////// -// SECTION: node.Service interface -///////////////////////////////////////////////////////////////////// - -func (p *Pss) Start(srv *p2p.Server) error { - go func() { - ticker := time.NewTicker(defaultCleanInterval) - cacheTicker := time.NewTicker(p.cacheTTL) - defer ticker.Stop() - defer cacheTicker.Stop() - for { - select { - case <-cacheTicker.C: - p.cleanFwdCache() - case <-ticker.C: - p.cleanKeys() - case <-p.quitC: - return - } - } - }() - go func() { - for { - select { - case msg := <-p.outbox: - err := p.forward(msg) - if err != nil { - log.Error(err.Error()) - metrics.GetOrRegisterCounter("pss.forward.err", nil).Inc(1) - } - case <-p.quitC: - return - } - } - }() - log.Info("Started Pss") - log.Info("Loaded EC keys", "pubkey", common.ToHex(crypto.FromECDSAPub(p.PublicKey())), "secp256", common.ToHex(crypto.CompressPubkey(p.PublicKey()))) - return nil -} - -func (p *Pss) Stop() error { - log.Info("Pss shutting down") - close(p.quitC) - return nil -} - -var pssSpec = &protocols.Spec{ - Name: pssProtocolName, - Version: pssVersion, - MaxMsgSize: defaultMaxMsgSize, - Messages: []interface{}{ - PssMsg{}, - }, -} - -func (p *Pss) Protocols() []p2p.Protocol { - return []p2p.Protocol{ - { - Name: pssSpec.Name, - Version: pssSpec.Version, - Length: pssSpec.Length(), - Run: p.Run, - }, - } -} - -func (p *Pss) Run(peer *p2p.Peer, rw p2p.MsgReadWriter) error { - pp := protocols.NewPeer(peer, rw, pssSpec) - p.fwdPoolMu.Lock() - p.fwdPool[peer.Info().ID] = pp - p.fwdPoolMu.Unlock() - return pp.Run(p.handlePssMsg) -} - -func (p *Pss) APIs() []rpc.API { - apis := []rpc.API{ - { - Namespace: "pss", - Version: "1.0", - Service: NewAPI(p), - Public: true, - }, - } - apis = append(apis, p.auxAPIs...) - return apis -} - -// add API methods to the pss API -// must be run before node is started -func (p *Pss) addAPI(api rpc.API) { - p.auxAPIs = append(p.auxAPIs, api) -} - -// Returns the swarm Kademlia address of the pss node -func (p *Pss) BaseAddr() []byte { - return p.Kademlia.BaseAddr() -} - -// Returns the pss node's public key -func (p *Pss) PublicKey() *ecdsa.PublicKey { - return &p.privateKey.PublicKey -} - -///////////////////////////////////////////////////////////////////// -// SECTION: Message handling -///////////////////////////////////////////////////////////////////// - -// Links a handler function to a Topic -// -// All incoming messages with an envelope Topic matching the -// topic specified will be passed to the given Handler function. -// -// There may be an arbitrary number of handler functions per topic. -// -// Returns a deregister function which needs to be called to -// deregister the handler, -func (p *Pss) Register(topic *Topic, hndlr *handler) func() { - p.handlersMu.Lock() - defer p.handlersMu.Unlock() - handlers := p.handlers[*topic] - if handlers == nil { - handlers = make(map[*handler]bool) - p.handlers[*topic] = handlers - log.Debug("registered handler", "caps", hndlr.caps) - } - if hndlr.caps == nil { - hndlr.caps = &handlerCaps{} - } - handlers[hndlr] = true - if _, ok := p.topicHandlerCaps[*topic]; !ok { - p.topicHandlerCaps[*topic] = &handlerCaps{} - } - if hndlr.caps.raw { - p.topicHandlerCaps[*topic].raw = true - } - if hndlr.caps.prox { - p.topicHandlerCaps[*topic].prox = true - } - return func() { p.deregister(topic, hndlr) } -} -func (p *Pss) deregister(topic *Topic, hndlr *handler) { - p.handlersMu.Lock() - defer p.handlersMu.Unlock() - handlers := p.handlers[*topic] - if len(handlers) > 1 { - delete(p.handlers, *topic) - // topic caps might have changed now that a handler is gone - caps := &handlerCaps{} - for h := range handlers { - if h.caps.raw { - caps.raw = true - } - if h.caps.prox { - caps.prox = true - } - } - p.topicHandlerCaps[*topic] = caps - return - } - delete(handlers, hndlr) -} - -// get all registered handlers for respective topics -func (p *Pss) getHandlers(topic Topic) map[*handler]bool { - p.handlersMu.RLock() - defer p.handlersMu.RUnlock() - return p.handlers[topic] -} - -// Filters incoming messages for processing or forwarding. -// Check if address partially matches -// If yes, it CAN be for us, and we process it -// Only passes error to pss protocol handler if payload is not valid pssmsg -func (p *Pss) handlePssMsg(ctx context.Context, msg interface{}) error { - metrics.GetOrRegisterCounter("pss.handlepssmsg", nil).Inc(1) - pssmsg, ok := msg.(*PssMsg) - if !ok { - return fmt.Errorf("invalid message type. Expected *PssMsg, got %T ", msg) - } - log.Trace("handler", "self", label(p.Kademlia.BaseAddr()), "topic", label(pssmsg.Payload.Topic[:])) - if int64(pssmsg.Expire) < time.Now().Unix() { - metrics.GetOrRegisterCounter("pss.expire", nil).Inc(1) - log.Warn("pss filtered expired message", "from", common.ToHex(p.Kademlia.BaseAddr()), "to", common.ToHex(pssmsg.To)) - return nil - } - if p.checkFwdCache(pssmsg) { - log.Trace("pss relay block-cache match (process)", "from", common.ToHex(p.Kademlia.BaseAddr()), "to", (common.ToHex(pssmsg.To))) - return nil - } - p.addFwdCache(pssmsg) - - psstopic := Topic(pssmsg.Payload.Topic) - - // raw is simplest handler contingency to check, so check that first - var isRaw bool - if pssmsg.isRaw() { - if _, ok := p.topicHandlerCaps[psstopic]; ok { - if !p.topicHandlerCaps[psstopic].raw { - log.Debug("No handler for raw message", "topic", psstopic) - return nil - } - } - isRaw = true - } - - // check if we can be recipient: - // - no prox handler on message and partial address matches - // - prox handler on message and we are in prox regardless of partial address match - // store this result so we don't calculate again on every handler - var isProx bool - if _, ok := p.topicHandlerCaps[psstopic]; ok { - isProx = p.topicHandlerCaps[psstopic].prox - } - isRecipient := p.isSelfPossibleRecipient(pssmsg, isProx) - if !isRecipient { - log.Trace("pss was for someone else :'( ... forwarding", "pss", common.ToHex(p.BaseAddr()), "prox", isProx) - return p.enqueue(pssmsg) - } - - log.Trace("pss for us, yay! ... let's process!", "pss", common.ToHex(p.BaseAddr()), "prox", isProx, "raw", isRaw, "topic", label(pssmsg.Payload.Topic[:])) - if err := p.process(pssmsg, isRaw, isProx); err != nil { - qerr := p.enqueue(pssmsg) - if qerr != nil { - return fmt.Errorf("process fail: processerr %v, queueerr: %v", err, qerr) - } - } - return nil - -} - -// Entry point to processing a message for which the current node can be the intended recipient. -// Attempts symmetric and asymmetric decryption with stored keys. -// Dispatches message to all handlers matching the message topic -func (p *Pss) process(pssmsg *PssMsg, raw bool, prox bool) error { - metrics.GetOrRegisterCounter("pss.process", nil).Inc(1) - - var err error - var recvmsg *whisper.ReceivedMessage - var payload []byte - var from PssAddress - var asymmetric bool - var keyid string - var keyFunc func(envelope *whisper.Envelope) (*whisper.ReceivedMessage, string, PssAddress, error) - - envelope := pssmsg.Payload - psstopic := Topic(envelope.Topic) - - if raw { - payload = pssmsg.Payload.Data - } else { - if pssmsg.isSym() { - keyFunc = p.processSym - } else { - asymmetric = true - keyFunc = p.processAsym - } - - recvmsg, keyid, from, err = keyFunc(envelope) - if err != nil { - return errors.New("Decryption failed") - } - payload = recvmsg.Payload - } - - if len(pssmsg.To) < addressLength { - if err := p.enqueue(pssmsg); err != nil { - return err - } - } - p.executeHandlers(psstopic, payload, from, raw, prox, asymmetric, keyid) - - return nil - -} - -func (p *Pss) executeHandlers(topic Topic, payload []byte, from PssAddress, raw bool, prox bool, asymmetric bool, keyid string) { - handlers := p.getHandlers(topic) - peer := p2p.NewPeer(enode.ID{}, fmt.Sprintf("%x", from), []p2p.Cap{}) - for h := range handlers { - if !h.caps.raw && raw { - log.Warn("norawhandler") - continue - } - if !h.caps.prox && prox { - log.Warn("noproxhandler") - continue - } - err := (h.f)(payload, peer, asymmetric, keyid) - if err != nil { - log.Warn("Pss handler failed", "err", err) - } - } -} - -// will return false if using partial address -func (p *Pss) isSelfRecipient(msg *PssMsg) bool { - return bytes.Equal(msg.To, p.Kademlia.BaseAddr()) -} - -// test match of leftmost bytes in given message to node's Kademlia address -func (p *Pss) isSelfPossibleRecipient(msg *PssMsg, prox bool) bool { - local := p.Kademlia.BaseAddr() - - // if a partial address matches we are possible recipient regardless of prox - // if not and prox is not set, we are surely not - if bytes.Equal(msg.To, local[:len(msg.To)]) { - - return true - } else if !prox { - return false - } - - depth := p.Kademlia.NeighbourhoodDepth() - po, _ := network.Pof(p.Kademlia.BaseAddr(), msg.To, 0) - log.Trace("selfpossible", "po", po, "depth", depth) - - return depth <= po -} - -///////////////////////////////////////////////////////////////////// -// SECTION: Message sending -///////////////////////////////////////////////////////////////////// - -func (p *Pss) enqueue(msg *PssMsg) error { - select { - case p.outbox <- msg: - return nil - default: - } - - metrics.GetOrRegisterCounter("pss.enqueue.outbox.full", nil).Inc(1) - return errors.New("outbox full") -} - -// Send a raw message (any encryption is responsibility of calling client) -// -// Will fail if raw messages are disallowed -func (p *Pss) SendRaw(address PssAddress, topic Topic, msg []byte) error { - if err := validateAddress(address); err != nil { - return err - } - pssMsgParams := &msgParams{ - raw: true, - } - payload := &whisper.Envelope{ - Data: msg, - Topic: whisper.TopicType(topic), - } - pssMsg := newPssMsg(pssMsgParams) - pssMsg.To = address - pssMsg.Expire = uint32(time.Now().Add(p.msgTTL).Unix()) - pssMsg.Payload = payload - p.addFwdCache(pssMsg) - err := p.enqueue(pssMsg) - if err != nil { - return err - } - - // if we have a proxhandler on this topic - // also deliver message to ourselves - if _, ok := p.topicHandlerCaps[topic]; ok { - if p.isSelfPossibleRecipient(pssMsg, true) && p.topicHandlerCaps[topic].prox { - return p.process(pssMsg, true, true) - } - } - return nil -} - -// Send a message using symmetric encryption -// -// Fails if the key id does not match any of the stored symmetric keys -func (p *Pss) SendSym(symkeyid string, topic Topic, msg []byte) error { - symkey, err := p.GetSymmetricKey(symkeyid) - if err != nil { - return fmt.Errorf("missing valid send symkey %s: %v", symkeyid, err) - } - psp, ok := p.getPeerSym(symkeyid, topic) - if !ok { - return fmt.Errorf("invalid topic '%s' for symkey '%s'", topic.String(), symkeyid) - } - return p.send(psp.address, topic, msg, false, symkey) -} - -// Send a message using asymmetric encryption -// -// Fails if the key id does not match any in of the stored public keys -func (p *Pss) SendAsym(pubkeyid string, topic Topic, msg []byte) error { - if _, err := crypto.UnmarshalPubkey(common.FromHex(pubkeyid)); err != nil { - return fmt.Errorf("Cannot unmarshal pubkey: %x", pubkeyid) - } - psp, ok := p.getPeerPub(pubkeyid, topic) - if !ok { - return fmt.Errorf("invalid topic '%s' for pubkey '%s'", topic.String(), pubkeyid) - } - return p.send(psp.address, topic, msg, true, common.FromHex(pubkeyid)) -} - -// Send is payload agnostic, and will accept any byte slice as payload -// It generates an whisper envelope for the specified recipient and topic, -// and wraps the message payload in it. -// TODO: Implement proper message padding -func (p *Pss) send(to []byte, topic Topic, msg []byte, asymmetric bool, key []byte) error { - metrics.GetOrRegisterCounter("pss.send", nil).Inc(1) - - if key == nil || bytes.Equal(key, []byte{}) { - return fmt.Errorf("Zero length key passed to pss send") - } - padding := make([]byte, p.paddingByteSize) - c, err := rand.Read(padding) - if err != nil { - return err - } else if c < p.paddingByteSize { - return fmt.Errorf("invalid padding length: %d", c) - } - wparams := &whisper.MessageParams{ - TTL: defaultWhisperTTL, - Src: p.privateKey, - Topic: whisper.TopicType(topic), - WorkTime: defaultWhisperWorkTime, - PoW: defaultWhisperPoW, - Payload: msg, - Padding: padding, - } - if asymmetric { - pk, err := crypto.UnmarshalPubkey(key) - if err != nil { - return fmt.Errorf("Cannot unmarshal pubkey: %x", key) - } - wparams.Dst = pk - } else { - wparams.KeySym = key - } - // set up outgoing message container, which does encryption and envelope wrapping - woutmsg, err := whisper.NewSentMessage(wparams) - if err != nil { - return fmt.Errorf("failed to generate whisper message encapsulation: %v", err) - } - // performs encryption. - // Does NOT perform / performs negligible PoW due to very low difficulty setting - // after this the message is ready for sending - envelope, err := woutmsg.Wrap(wparams) - if err != nil { - return fmt.Errorf("failed to perform whisper encryption: %v", err) - } - log.Trace("pssmsg whisper done", "env", envelope, "wparams payload", common.ToHex(wparams.Payload), "to", common.ToHex(to), "asym", asymmetric, "key", common.ToHex(key)) - - // prepare for devp2p transport - pssMsgParams := &msgParams{ - sym: !asymmetric, - } - pssMsg := newPssMsg(pssMsgParams) - pssMsg.To = to - pssMsg.Expire = uint32(time.Now().Add(p.msgTTL).Unix()) - pssMsg.Payload = envelope - err = p.enqueue(pssMsg) - if err != nil { - return err - } - if _, ok := p.topicHandlerCaps[topic]; ok { - if p.isSelfPossibleRecipient(pssMsg, true) && p.topicHandlerCaps[topic].prox { - return p.process(pssMsg, true, true) - } - } - return nil -} - -// sendFunc is a helper function that tries to send a message and returns true on success. -// It is set here for usage in production, and optionally overridden in tests. -var sendFunc func(p *Pss, sp *network.Peer, msg *PssMsg) bool = sendMsg - -// tries to send a message, returns true if successful -func sendMsg(p *Pss, sp *network.Peer, msg *PssMsg) bool { - var isPssEnabled bool - info := sp.Info() - for _, capability := range info.Caps { - if capability == p.capstring { - isPssEnabled = true - break - } - } - if !isPssEnabled { - log.Error("peer doesn't have matching pss capabilities, skipping", "peer", info.Name, "caps", info.Caps) - return false - } - - // get the protocol peer from the forwarding peer cache - p.fwdPoolMu.RLock() - pp := p.fwdPool[sp.Info().ID] - p.fwdPoolMu.RUnlock() - - err := pp.Send(context.TODO(), msg) - if err != nil { - metrics.GetOrRegisterCounter("pss.pp.send.error", nil).Inc(1) - log.Error(err.Error()) - } - - return err == nil -} - -// Forwards a pss message to the peer(s) based on recipient address according to the algorithm -// described below. The recipient address can be of any length, and the byte slice will be matched -// to the MSB slice of the peer address of the equivalent length. -// -// If the recipient address (or partial address) is within the neighbourhood depth of the forwarding -// node, then it will be forwarded to all the nearest neighbours of the forwarding node. In case of -// partial address, it should be forwarded to all the peers matching the partial address, if there -// are any; otherwise only to one peer, closest to the recipient address. In any case, if the message -// forwarding fails, the node should try to forward it to the next best peer, until the message is -// successfully forwarded to at least one peer. -func (p *Pss) forward(msg *PssMsg) error { - metrics.GetOrRegisterCounter("pss.forward", nil).Inc(1) - sent := 0 // number of successful sends - to := make([]byte, addressLength) - copy(to[:len(msg.To)], msg.To) - neighbourhoodDepth := p.Kademlia.NeighbourhoodDepth() - - // luminosity is the opposite of darkness. the more bytes are removed from the address, the higher is darkness, - // but the luminosity is less. here luminosity equals the number of bits given in the destination address. - luminosityRadius := len(msg.To) * 8 - - // proximity order function matching up to neighbourhoodDepth bits (po <= neighbourhoodDepth) - pof := pot.DefaultPof(neighbourhoodDepth) - - // soft threshold for msg broadcast - broadcastThreshold, _ := pof(to, p.BaseAddr(), 0) - if broadcastThreshold > luminosityRadius { - broadcastThreshold = luminosityRadius - } - - var onlySendOnce bool // indicates if the message should only be sent to one peer with closest address - - // if measured from the recipient address as opposed to the base address (see Kademlia.EachConn - // call below), then peers that fall in the same proximity bin as recipient address will appear - // [at least] one bit closer, but only if these additional bits are given in the recipient address. - if broadcastThreshold < luminosityRadius && broadcastThreshold < neighbourhoodDepth { - broadcastThreshold++ - onlySendOnce = true - } - - p.Kademlia.EachConn(to, addressLength*8, func(sp *network.Peer, po int) bool { - if po < broadcastThreshold && sent > 0 { - return false // stop iterating - } - if sendFunc(p, sp, msg) { - sent++ - if onlySendOnce { - return false - } - if po == addressLength*8 { - // stop iterating if successfully sent to the exact recipient (perfect match of full address) - return false - } - } - return true - }) - - // if we failed to send to anyone, re-insert message in the send-queue - if sent == 0 { - log.Debug("unable to forward to any peers") - if err := p.enqueue(msg); err != nil { - metrics.GetOrRegisterCounter("pss.forward.enqueue.error", nil).Inc(1) - log.Error(err.Error()) - return err - } - } - - // cache the message - p.addFwdCache(msg) - return nil -} - -///////////////////////////////////////////////////////////////////// -// SECTION: Caching -///////////////////////////////////////////////////////////////////// - -// cleanFwdCache is used to periodically remove expired entries from the forward cache -func (p *Pss) cleanFwdCache() { - metrics.GetOrRegisterCounter("pss.cleanfwdcache", nil).Inc(1) - p.fwdCacheMu.Lock() - defer p.fwdCacheMu.Unlock() - for k, v := range p.fwdCache { - if v.expiresAt.Before(time.Now()) { - delete(p.fwdCache, k) - } - } -} - -func label(b []byte) string { - return fmt.Sprintf("%04x", b[:2]) -} - -// add a message to the cache -func (p *Pss) addFwdCache(msg *PssMsg) error { - metrics.GetOrRegisterCounter("pss.addfwdcache", nil).Inc(1) - - var entry pssCacheEntry - var ok bool - - p.fwdCacheMu.Lock() - defer p.fwdCacheMu.Unlock() - - digest := p.digest(msg) - if entry, ok = p.fwdCache[digest]; !ok { - entry = pssCacheEntry{} - } - entry.expiresAt = time.Now().Add(p.cacheTTL) - p.fwdCache[digest] = entry - return nil -} - -// check if message is in the cache -func (p *Pss) checkFwdCache(msg *PssMsg) bool { - p.fwdCacheMu.Lock() - defer p.fwdCacheMu.Unlock() - - digest := p.digest(msg) - entry, ok := p.fwdCache[digest] - if ok { - if entry.expiresAt.After(time.Now()) { - log.Trace("unexpired cache", "digest", fmt.Sprintf("%x", digest)) - metrics.GetOrRegisterCounter("pss.checkfwdcache.unexpired", nil).Inc(1) - return true - } - metrics.GetOrRegisterCounter("pss.checkfwdcache.expired", nil).Inc(1) - } - return false -} - -// Digest of message -func (p *Pss) digest(msg *PssMsg) pssDigest { - return p.digestBytes(msg.serialize()) -} - -func (p *Pss) digestBytes(msg []byte) pssDigest { - hasher := p.hashPool.Get().(hash.Hash) - defer p.hashPool.Put(hasher) - hasher.Reset() - hasher.Write(msg) - digest := pssDigest{} - key := hasher.Sum(nil) - copy(digest[:], key[:digestLength]) - return digest -} - -func validateAddress(addr PssAddress) error { - if len(addr) > addressLength { - return errors.New("address too long") - } - return nil -} diff --git a/swarm/pss/pss_test.go b/swarm/pss/pss_test.go deleted file mode 100644 index b65486426eb3..000000000000 --- a/swarm/pss/pss_test.go +++ /dev/null @@ -1,2108 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package pss - -import ( - "bytes" - "context" - "crypto/ecdsa" - "encoding/binary" - "encoding/hex" - "encoding/json" - "flag" - "fmt" - "io/ioutil" - "math/rand" - "os" - "strconv" - "strings" - "sync" - "testing" - "time" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/common/hexutil" - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/metrics/influxdb" - "github.com/ubiq/go-ubiq/node" - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/protocols" - "github.com/ubiq/go-ubiq/p2p/simulations" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" - "github.com/ubiq/go-ubiq/rpc" - "github.com/ubiq/go-ubiq/swarm/network" - "github.com/ubiq/go-ubiq/swarm/pot" - "github.com/ubiq/go-ubiq/swarm/state" - whisper "github.com/ubiq/go-ubiq/whisper/whisperv6" -) - -var ( - initOnce = sync.Once{} - loglevel = flag.Int("loglevel", 2, "logging verbosity") - longrunning = flag.Bool("longrunning", false, "do run long-running tests") - w *whisper.Whisper - wapi *whisper.PublicWhisperAPI - psslogmain log.Logger - pssprotocols map[string]*protoCtrl - useHandshake bool - noopHandlerFunc = func(msg []byte, p *p2p.Peer, asymmetric bool, keyid string) error { - return nil - } -) - -func init() { - flag.Parse() - rand.Seed(time.Now().Unix()) - - adapters.RegisterServices(newServices(false)) - initTest() -} - -func initTest() { - initOnce.Do( - func() { - psslogmain = log.New("psslog", "*") - hs := log.StreamHandler(os.Stderr, log.TerminalFormat(true)) - hf := log.LvlFilterHandler(log.Lvl(*loglevel), hs) - h := log.CallerFileHandler(hf) - log.Root().SetHandler(h) - - w = whisper.New(&whisper.DefaultConfig) - wapi = whisper.NewPublicWhisperAPI(w) - - pssprotocols = make(map[string]*protoCtrl) - }, - ) -} - -// test that topic conversion functions give predictable results -func TestTopic(t *testing.T) { - - api := &API{} - - topicstr := strings.Join([]string{PingProtocol.Name, strconv.Itoa(int(PingProtocol.Version))}, ":") - - // bytestotopic is the authoritative topic conversion source - topicobj := BytesToTopic([]byte(topicstr)) - - // string to topic and bytes to topic must match - topicapiobj, _ := api.StringToTopic(topicstr) - if topicobj != topicapiobj { - t.Fatalf("bytes and string topic conversion mismatch; %s != %s", topicobj, topicapiobj) - } - - // string representation of topichex - topichex := topicobj.String() - - // protocoltopic wrapper on pingtopic should be same as topicstring - // check that it matches - pingtopichex := PingTopic.String() - if topichex != pingtopichex { - t.Fatalf("protocol topic conversion mismatch; %s != %s", topichex, pingtopichex) - } - - // json marshal of topic - topicjsonout, err := topicobj.MarshalJSON() - if err != nil { - t.Fatal(err) - } - if string(topicjsonout)[1:len(topicjsonout)-1] != topichex { - t.Fatalf("topic json marshal mismatch; %s != \"%s\"", topicjsonout, topichex) - } - - // json unmarshal of topic - var topicjsonin Topic - topicjsonin.UnmarshalJSON(topicjsonout) - if topicjsonin != topicobj { - t.Fatalf("topic json unmarshal mismatch: %x != %x", topicjsonin, topicobj) - } -} - -// test bit packing of message control flags -func TestMsgParams(t *testing.T) { - var ctrl byte - ctrl |= pssControlRaw - p := newMsgParamsFromBytes([]byte{ctrl}) - m := newPssMsg(p) - if !m.isRaw() || m.isSym() { - t.Fatal("expected raw=true and sym=false") - } - ctrl |= pssControlSym - p = newMsgParamsFromBytes([]byte{ctrl}) - m = newPssMsg(p) - if !m.isRaw() || !m.isSym() { - t.Fatal("expected raw=true and sym=true") - } - ctrl &= 0xff &^ pssControlRaw - p = newMsgParamsFromBytes([]byte{ctrl}) - m = newPssMsg(p) - if m.isRaw() || !m.isSym() { - t.Fatal("expected raw=false and sym=true") - } -} - -// test if we can insert into cache, match items with cache and cache expiry -func TestCache(t *testing.T) { - var err error - to, _ := hex.DecodeString("08090a0b0c0d0e0f1011121314150001020304050607161718191a1b1c1d1e1f") - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - keys, err := wapi.NewKeyPair(ctx) - privkey, err := w.GetPrivateKey(keys) - if err != nil { - t.Fatal(err) - } - ps := newTestPss(privkey, nil, nil) - defer ps.Stop() - pp := NewPssParams().WithPrivateKey(privkey) - data := []byte("foo") - datatwo := []byte("bar") - datathree := []byte("baz") - wparams := &whisper.MessageParams{ - TTL: defaultWhisperTTL, - Src: privkey, - Dst: &privkey.PublicKey, - Topic: whisper.TopicType(PingTopic), - WorkTime: defaultWhisperWorkTime, - PoW: defaultWhisperPoW, - Payload: data, - } - woutmsg, err := whisper.NewSentMessage(wparams) - env, err := woutmsg.Wrap(wparams) - msg := &PssMsg{ - Payload: env, - To: to, - } - wparams.Payload = datatwo - woutmsg, err = whisper.NewSentMessage(wparams) - envtwo, err := woutmsg.Wrap(wparams) - msgtwo := &PssMsg{ - Payload: envtwo, - To: to, - } - wparams.Payload = datathree - woutmsg, err = whisper.NewSentMessage(wparams) - envthree, err := woutmsg.Wrap(wparams) - msgthree := &PssMsg{ - Payload: envthree, - To: to, - } - - digest := ps.digest(msg) - if err != nil { - t.Fatalf("could not store cache msgone: %v", err) - } - digesttwo := ps.digest(msgtwo) - if err != nil { - t.Fatalf("could not store cache msgtwo: %v", err) - } - digestthree := ps.digest(msgthree) - if err != nil { - t.Fatalf("could not store cache msgthree: %v", err) - } - - if digest == digesttwo { - t.Fatalf("different msgs return same hash: %d", digesttwo) - } - - // check the cache - err = ps.addFwdCache(msg) - if err != nil { - t.Fatalf("write to pss expire cache failed: %v", err) - } - - if !ps.checkFwdCache(msg) { - t.Fatalf("message %v should have EXPIRE record in cache but checkCache returned false", msg) - } - - if ps.checkFwdCache(msgtwo) { - t.Fatalf("message %v should NOT have EXPIRE record in cache but checkCache returned true", msgtwo) - } - - time.Sleep(pp.CacheTTL + 1*time.Second) - err = ps.addFwdCache(msgthree) - if err != nil { - t.Fatalf("write to pss expire cache failed: %v", err) - } - - if ps.checkFwdCache(msg) { - t.Fatalf("message %v should have expired from cache but checkCache returned true", msg) - } - - if _, ok := ps.fwdCache[digestthree]; !ok { - t.Fatalf("unexpired message should be in the cache: %v", digestthree) - } - - if _, ok := ps.fwdCache[digesttwo]; ok { - t.Fatalf("expired message should have been cleared from the cache: %v", digesttwo) - } -} - -// matching of address hints; whether a message could be or is for the node -func TestAddressMatch(t *testing.T) { - - localaddr := network.RandomAddr().Over() - copy(localaddr[:8], []byte("deadbeef")) - remoteaddr := []byte("feedbeef") - kadparams := network.NewKadParams() - kad := network.NewKademlia(localaddr, kadparams) - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - keys, err := wapi.NewKeyPair(ctx) - if err != nil { - t.Fatalf("Could not generate private key: %v", err) - } - privkey, err := w.GetPrivateKey(keys) - pssp := NewPssParams().WithPrivateKey(privkey) - ps, err := NewPss(kad, pssp) - if err != nil { - t.Fatal(err.Error()) - } - - pssmsg := &PssMsg{ - To: remoteaddr, - } - - // differ from first byte - if ps.isSelfRecipient(pssmsg) { - t.Fatalf("isSelfRecipient true but %x != %x", remoteaddr, localaddr) - } - if ps.isSelfPossibleRecipient(pssmsg, false) { - t.Fatalf("isSelfPossibleRecipient true but %x != %x", remoteaddr[:8], localaddr[:8]) - } - - // 8 first bytes same - copy(remoteaddr[:4], localaddr[:4]) - if ps.isSelfRecipient(pssmsg) { - t.Fatalf("isSelfRecipient true but %x != %x", remoteaddr, localaddr) - } - if !ps.isSelfPossibleRecipient(pssmsg, false) { - t.Fatalf("isSelfPossibleRecipient false but %x == %x", remoteaddr[:8], localaddr[:8]) - } - - // all bytes same - pssmsg.To = localaddr - if !ps.isSelfRecipient(pssmsg) { - t.Fatalf("isSelfRecipient false but %x == %x", remoteaddr, localaddr) - } - if !ps.isSelfPossibleRecipient(pssmsg, false) { - t.Fatalf("isSelfPossibleRecipient false but %x == %x", remoteaddr[:8], localaddr[:8]) - } - -} - -// test that message is handled by sender if a prox handler exists and sender is in prox of message -func TestProxShortCircuit(t *testing.T) { - - // sender node address - localAddr := network.RandomAddr().Over() - localPotAddr := pot.NewAddressFromBytes(localAddr) - - // set up kademlia - kadParams := network.NewKadParams() - kad := network.NewKademlia(localAddr, kadParams) - peerCount := kad.MinBinSize + 1 - - // set up pss - privKey, err := crypto.GenerateKey() - pssp := NewPssParams().WithPrivateKey(privKey) - ps, err := NewPss(kad, pssp) - if err != nil { - t.Fatal(err.Error()) - } - - // create kademlia peers, so we have peers both inside and outside minproxlimit - var peers []*network.Peer - proxMessageAddress := pot.RandomAddressAt(localPotAddr, peerCount).Bytes() - distantMessageAddress := pot.RandomAddressAt(localPotAddr, 0).Bytes() - - for i := 0; i < peerCount; i++ { - rw := &p2p.MsgPipeRW{} - ptpPeer := p2p.NewPeer(enode.ID{}, "wanna be with me? [ ] yes [ ] no", []p2p.Cap{}) - protoPeer := protocols.NewPeer(ptpPeer, rw, &protocols.Spec{}) - peerAddr := pot.RandomAddressAt(localPotAddr, i) - bzzPeer := &network.BzzPeer{ - Peer: protoPeer, - BzzAddr: &network.BzzAddr{ - OAddr: peerAddr.Bytes(), - UAddr: []byte(fmt.Sprintf("%x", peerAddr[:])), - }, - } - peer := network.NewPeer(bzzPeer, kad) - kad.On(peer) - peers = append(peers, peer) - } - - // register it marking prox capability - delivered := make(chan struct{}) - rawHandlerFunc := func(msg []byte, p *p2p.Peer, asymmetric bool, keyid string) error { - log.Trace("in allowraw handler") - delivered <- struct{}{} - return nil - } - topic := BytesToTopic([]byte{0x2a}) - hndlrProxDereg := ps.Register(&topic, &handler{ - f: rawHandlerFunc, - caps: &handlerCaps{ - raw: true, - prox: true, - }, - }) - defer hndlrProxDereg() - - // send message too far away for sender to be in prox - // reception of this message should time out - errC := make(chan error) - go func() { - err := ps.SendRaw(distantMessageAddress, topic, []byte("foo")) - if err != nil { - errC <- err - } - }() - - ctx, cancel := context.WithTimeout(context.TODO(), time.Second) - defer cancel() - select { - case <-delivered: - t.Fatal("raw distant message delivered") - case err := <-errC: - t.Fatal(err) - case <-ctx.Done(): - } - - // send message that should be within sender prox - // this message should be delivered - go func() { - err := ps.SendRaw(proxMessageAddress, topic, []byte("bar")) - if err != nil { - errC <- err - } - }() - - ctx, cancel = context.WithTimeout(context.TODO(), time.Second) - defer cancel() - select { - case <-delivered: - case err := <-errC: - t.Fatal(err) - case <-ctx.Done(): - t.Fatal("raw timeout") - } - - // try the same prox message with sym and asym send - proxAddrPss := PssAddress(proxMessageAddress) - symKeyId, err := ps.GenerateSymmetricKey(topic, proxAddrPss, true) - go func() { - err := ps.SendSym(symKeyId, topic, []byte("baz")) - if err != nil { - errC <- err - } - }() - ctx, cancel = context.WithTimeout(context.TODO(), time.Second) - defer cancel() - select { - case <-delivered: - case err := <-errC: - t.Fatal(err) - case <-ctx.Done(): - t.Fatal("sym timeout") - } - - err = ps.SetPeerPublicKey(&privKey.PublicKey, topic, proxAddrPss) - if err != nil { - t.Fatal(err) - } - pubKeyId := hexutil.Encode(crypto.FromECDSAPub(&privKey.PublicKey)) - go func() { - err := ps.SendAsym(pubKeyId, topic, []byte("xyzzy")) - if err != nil { - errC <- err - } - }() - ctx, cancel = context.WithTimeout(context.TODO(), time.Second) - defer cancel() - select { - case <-delivered: - case err := <-errC: - t.Fatal(err) - case <-ctx.Done(): - t.Fatal("asym timeout") - } -} - -// verify that node can be set as recipient regardless of explicit message address match if minimum one handler of a topic is explicitly set to allow it -// note that in these tests we use the raw capability on handlers for convenience -func TestAddressMatchProx(t *testing.T) { - - // recipient node address - localAddr := network.RandomAddr().Over() - localPotAddr := pot.NewAddressFromBytes(localAddr) - - // set up kademlia - kadparams := network.NewKadParams() - kad := network.NewKademlia(localAddr, kadparams) - nnPeerCount := kad.MinBinSize - peerCount := nnPeerCount + 2 - - // set up pss - privKey, err := crypto.GenerateKey() - pssp := NewPssParams().WithPrivateKey(privKey) - ps, err := NewPss(kad, pssp) - if err != nil { - t.Fatal(err.Error()) - } - - // create kademlia peers, so we have peers both inside and outside minproxlimit - var peers []*network.Peer - for i := 0; i < peerCount; i++ { - rw := &p2p.MsgPipeRW{} - ptpPeer := p2p.NewPeer(enode.ID{}, "362436 call me anytime", []p2p.Cap{}) - protoPeer := protocols.NewPeer(ptpPeer, rw, &protocols.Spec{}) - peerAddr := pot.RandomAddressAt(localPotAddr, i) - bzzPeer := &network.BzzPeer{ - Peer: protoPeer, - BzzAddr: &network.BzzAddr{ - OAddr: peerAddr.Bytes(), - UAddr: []byte(fmt.Sprintf("%x", peerAddr[:])), - }, - } - peer := network.NewPeer(bzzPeer, kad) - kad.On(peer) - peers = append(peers, peer) - } - - // TODO: create a test in the network package to make a table with n peers where n-m are proxpeers - // meanwhile test regression for kademlia since we are compiling the test parameters from different packages - var proxes int - var conns int - depth := kad.NeighbourhoodDepth() - kad.EachConn(nil, peerCount, func(p *network.Peer, po int) bool { - conns++ - if po >= depth { - proxes++ - } - return true - }) - if proxes != nnPeerCount { - t.Fatalf("expected %d proxpeers, have %d", nnPeerCount, proxes) - } else if conns != peerCount { - t.Fatalf("expected %d peers total, have %d", peerCount, proxes) - } - - // remote address distances from localAddr to try and the expected outcomes if we use prox handler - remoteDistances := []int{ - 255, - nnPeerCount + 1, - nnPeerCount, - nnPeerCount - 1, - 0, - } - expects := []bool{ - true, - true, - true, - false, - false, - } - - // first the unit test on the method that calculates possible receipient using prox - for i, distance := range remoteDistances { - pssMsg := newPssMsg(&msgParams{}) - pssMsg.To = make([]byte, len(localAddr)) - copy(pssMsg.To, localAddr) - var byteIdx = distance / 8 - pssMsg.To[byteIdx] ^= 1 << uint(7-(distance%8)) - log.Trace(fmt.Sprintf("addrmatch %v", bytes.Equal(pssMsg.To, localAddr))) - if ps.isSelfPossibleRecipient(pssMsg, true) != expects[i] { - t.Fatalf("expected distance %d to be %v", distance, expects[i]) - } - } - - // we move up to higher level and test the actual message handler - // for each distance check if we are possible recipient when prox variant is used is set - - // this handler will increment a counter for every message that gets passed to the handler - var receives int - rawHandlerFunc := func(msg []byte, p *p2p.Peer, asymmetric bool, keyid string) error { - log.Trace("in allowraw handler") - receives++ - return nil - } - - // register it marking prox capability - topic := BytesToTopic([]byte{0x2a}) - hndlrProxDereg := ps.Register(&topic, &handler{ - f: rawHandlerFunc, - caps: &handlerCaps{ - raw: true, - prox: true, - }, - }) - - // test the distances - var prevReceive int - for i, distance := range remoteDistances { - remotePotAddr := pot.RandomAddressAt(localPotAddr, distance) - remoteAddr := remotePotAddr.Bytes() - - var data [32]byte - rand.Read(data[:]) - pssMsg := newPssMsg(&msgParams{raw: true}) - pssMsg.To = remoteAddr - pssMsg.Expire = uint32(time.Now().Unix() + 4200) - pssMsg.Payload = &whisper.Envelope{ - Topic: whisper.TopicType(topic), - Data: data[:], - } - - log.Trace("withprox addrs", "local", localAddr, "remote", remoteAddr) - ps.handlePssMsg(context.TODO(), pssMsg) - if (!expects[i] && prevReceive != receives) || (expects[i] && prevReceive == receives) { - t.Fatalf("expected distance %d recipient %v when prox is set for handler", distance, expects[i]) - } - prevReceive = receives - } - - // now add a non prox-capable handler and test - ps.Register(&topic, &handler{ - f: rawHandlerFunc, - caps: &handlerCaps{ - raw: true, - }, - }) - receives = 0 - prevReceive = 0 - for i, distance := range remoteDistances { - remotePotAddr := pot.RandomAddressAt(localPotAddr, distance) - remoteAddr := remotePotAddr.Bytes() - - var data [32]byte - rand.Read(data[:]) - pssMsg := newPssMsg(&msgParams{raw: true}) - pssMsg.To = remoteAddr - pssMsg.Expire = uint32(time.Now().Unix() + 4200) - pssMsg.Payload = &whisper.Envelope{ - Topic: whisper.TopicType(topic), - Data: data[:], - } - - log.Trace("withprox addrs", "local", localAddr, "remote", remoteAddr) - ps.handlePssMsg(context.TODO(), pssMsg) - if (!expects[i] && prevReceive != receives) || (expects[i] && prevReceive == receives) { - t.Fatalf("expected distance %d recipient %v when prox is set for handler", distance, expects[i]) - } - prevReceive = receives - } - - // now deregister the prox capable handler, now none of the messages will be handled - hndlrProxDereg() - receives = 0 - - for _, distance := range remoteDistances { - remotePotAddr := pot.RandomAddressAt(localPotAddr, distance) - remoteAddr := remotePotAddr.Bytes() - - pssMsg := newPssMsg(&msgParams{raw: true}) - pssMsg.To = remoteAddr - pssMsg.Expire = uint32(time.Now().Unix() + 4200) - pssMsg.Payload = &whisper.Envelope{ - Topic: whisper.TopicType(topic), - Data: []byte(remotePotAddr.String()), - } - - log.Trace("noprox addrs", "local", localAddr, "remote", remoteAddr) - ps.handlePssMsg(context.TODO(), pssMsg) - if receives != 0 { - t.Fatalf("expected distance %d to not be recipient when prox is not set for handler", distance) - } - - } -} - -// verify that message queueing happens when it should, and that expired and corrupt messages are dropped -func TestMessageProcessing(t *testing.T) { - - t.Skip("Disabled due to probable faulty logic for outbox expectations") - // setup - privkey, err := crypto.GenerateKey() - if err != nil { - t.Fatal(err.Error()) - } - - addr := make([]byte, 32) - addr[0] = 0x01 - ps := newTestPss(privkey, network.NewKademlia(addr, network.NewKadParams()), NewPssParams()) - defer ps.Stop() - - // message should pass - msg := newPssMsg(&msgParams{}) - msg.To = addr - msg.Expire = uint32(time.Now().Add(time.Second * 60).Unix()) - msg.Payload = &whisper.Envelope{ - Topic: [4]byte{}, - Data: []byte{0x66, 0x6f, 0x6f}, - } - if err := ps.handlePssMsg(context.TODO(), msg); err != nil { - t.Fatal(err.Error()) - } - tmr := time.NewTimer(time.Millisecond * 100) - var outmsg *PssMsg - select { - case outmsg = <-ps.outbox: - case <-tmr.C: - default: - } - if outmsg != nil { - t.Fatalf("expected outbox empty after full address on msg, but had message %s", msg) - } - - // message should pass and queue due to partial length - msg.To = addr[0:1] - msg.Payload.Data = []byte{0x78, 0x79, 0x80, 0x80, 0x79} - if err := ps.handlePssMsg(context.TODO(), msg); err != nil { - t.Fatal(err.Error()) - } - tmr.Reset(time.Millisecond * 100) - outmsg = nil - select { - case outmsg = <-ps.outbox: - case <-tmr.C: - } - if outmsg == nil { - t.Fatal("expected message in outbox on encrypt fail, but empty") - } - outmsg = nil - select { - case outmsg = <-ps.outbox: - default: - } - if outmsg != nil { - t.Fatalf("expected only one queued message but also had message %v", msg) - } - - // full address mismatch should put message in queue - msg.To[0] = 0xff - if err := ps.handlePssMsg(context.TODO(), msg); err != nil { - t.Fatal(err.Error()) - } - tmr.Reset(time.Millisecond * 10) - outmsg = nil - select { - case outmsg = <-ps.outbox: - case <-tmr.C: - } - if outmsg == nil { - t.Fatal("expected message in outbox on address mismatch, but empty") - } - outmsg = nil - select { - case outmsg = <-ps.outbox: - default: - } - if outmsg != nil { - t.Fatalf("expected only one queued message but also had message %v", msg) - } - - // expired message should be dropped - msg.Expire = uint32(time.Now().Add(-time.Second).Unix()) - if err := ps.handlePssMsg(context.TODO(), msg); err != nil { - t.Fatal(err.Error()) - } - tmr.Reset(time.Millisecond * 10) - outmsg = nil - select { - case outmsg = <-ps.outbox: - case <-tmr.C: - default: - } - if outmsg != nil { - t.Fatalf("expected empty queue but have message %v", msg) - } - - // invalid message should return error - fckedupmsg := &struct { - pssMsg *PssMsg - }{ - pssMsg: &PssMsg{}, - } - if err := ps.handlePssMsg(context.TODO(), fckedupmsg); err == nil { - t.Fatalf("expected error from processMsg but error nil") - } - - // outbox full should return error - msg.Expire = uint32(time.Now().Add(time.Second * 60).Unix()) - for i := 0; i < defaultOutboxCapacity; i++ { - ps.outbox <- msg - } - msg.Payload.Data = []byte{0x62, 0x61, 0x72} - err = ps.handlePssMsg(context.TODO(), msg) - if err == nil { - t.Fatal("expected error when mailbox full, but was nil") - } -} - -// set and generate pubkeys and symkeys -func TestKeys(t *testing.T) { - // make our key and init pss with it - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - ourkeys, err := wapi.NewKeyPair(ctx) - if err != nil { - t.Fatalf("create 'our' key fail") - } - ctx, cancel2 := context.WithTimeout(context.Background(), time.Second) - defer cancel2() - theirkeys, err := wapi.NewKeyPair(ctx) - if err != nil { - t.Fatalf("create 'their' key fail") - } - ourprivkey, err := w.GetPrivateKey(ourkeys) - if err != nil { - t.Fatalf("failed to retrieve 'our' private key") - } - theirprivkey, err := w.GetPrivateKey(theirkeys) - if err != nil { - t.Fatalf("failed to retrieve 'their' private key") - } - ps := newTestPss(ourprivkey, nil, nil) - defer ps.Stop() - - // set up peer with mock address, mapped to mocked publicaddress and with mocked symkey - addr := make(PssAddress, 32) - copy(addr, network.RandomAddr().Over()) - outkey := network.RandomAddr().Over() - topicobj := BytesToTopic([]byte("foo:42")) - ps.SetPeerPublicKey(&theirprivkey.PublicKey, topicobj, addr) - outkeyid, err := ps.SetSymmetricKey(outkey, topicobj, addr, false) - if err != nil { - t.Fatalf("failed to set 'our' outgoing symmetric key") - } - - // make a symmetric key that we will send to peer for encrypting messages to us - inkeyid, err := ps.GenerateSymmetricKey(topicobj, addr, true) - if err != nil { - t.Fatalf("failed to set 'our' incoming symmetric key") - } - - // get the key back from whisper, check that it's still the same - outkeyback, err := ps.w.GetSymKey(outkeyid) - if err != nil { - t.Fatalf(err.Error()) - } - inkey, err := ps.w.GetSymKey(inkeyid) - if err != nil { - t.Fatalf(err.Error()) - } - if !bytes.Equal(outkeyback, outkey) { - t.Fatalf("passed outgoing symkey doesnt equal stored: %x / %x", outkey, outkeyback) - } - - t.Logf("symout: %v", outkeyback) - t.Logf("symin: %v", inkey) - - // check that the key is stored in the peerpool - psp := ps.symKeyPool[inkeyid][topicobj] - if !bytes.Equal(psp.address, addr) { - t.Fatalf("inkey address does not match; %p != %p", psp.address, addr) - } -} - -// check that we can retrieve previously added public key entires per topic and peer -func TestGetPublickeyEntries(t *testing.T) { - - privkey, err := crypto.GenerateKey() - if err != nil { - t.Fatal(err) - } - ps := newTestPss(privkey, nil, nil) - defer ps.Stop() - - peeraddr := network.RandomAddr().Over() - topicaddr := make(map[Topic]PssAddress) - topicaddr[Topic{0x13}] = peeraddr - topicaddr[Topic{0x2a}] = peeraddr[:16] - topicaddr[Topic{0x02, 0x9a}] = []byte{} - - remoteprivkey, err := crypto.GenerateKey() - if err != nil { - t.Fatal(err) - } - remotepubkeybytes := crypto.FromECDSAPub(&remoteprivkey.PublicKey) - remotepubkeyhex := common.ToHex(remotepubkeybytes) - - pssapi := NewAPI(ps) - - for to, a := range topicaddr { - err = pssapi.SetPeerPublicKey(remotepubkeybytes, to, a) - if err != nil { - t.Fatal(err) - } - } - - intopic, err := pssapi.GetPeerTopics(remotepubkeyhex) - if err != nil { - t.Fatal(err) - } - -OUTER: - for _, tnew := range intopic { - for torig, addr := range topicaddr { - if bytes.Equal(torig[:], tnew[:]) { - inaddr, err := pssapi.GetPeerAddress(remotepubkeyhex, torig) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(addr, inaddr) { - t.Fatalf("Address mismatch for topic %x; got %x, expected %x", torig, inaddr, addr) - } - delete(topicaddr, torig) - continue OUTER - } - } - t.Fatalf("received topic %x did not match any existing topics", tnew) - } - - if len(topicaddr) != 0 { - t.Fatalf("%d topics were not matched", len(topicaddr)) - } -} - -// forwarding should skip peers that do not have matching pss capabilities -func TestPeerCapabilityMismatch(t *testing.T) { - - // create privkey for forwarder node - privkey, err := crypto.GenerateKey() - if err != nil { - t.Fatal(err) - } - - // initialize kad - baseaddr := network.RandomAddr() - kad := network.NewKademlia((baseaddr).Over(), network.NewKadParams()) - rw := &p2p.MsgPipeRW{} - - // one peer has a mismatching version of pss - wrongpssaddr := network.RandomAddr() - wrongpsscap := p2p.Cap{ - Name: pssProtocolName, - Version: 0, - } - nid := enode.ID{0x01} - wrongpsspeer := network.NewPeer(&network.BzzPeer{ - Peer: protocols.NewPeer(p2p.NewPeer(nid, common.ToHex(wrongpssaddr.Over()), []p2p.Cap{wrongpsscap}), rw, nil), - BzzAddr: &network.BzzAddr{OAddr: wrongpssaddr.Over(), UAddr: nil}, - }, kad) - - // one peer doesn't even have pss (boo!) - nopssaddr := network.RandomAddr() - nopsscap := p2p.Cap{ - Name: "nopss", - Version: 1, - } - nid = enode.ID{0x02} - nopsspeer := network.NewPeer(&network.BzzPeer{ - Peer: protocols.NewPeer(p2p.NewPeer(nid, common.ToHex(nopssaddr.Over()), []p2p.Cap{nopsscap}), rw, nil), - BzzAddr: &network.BzzAddr{OAddr: nopssaddr.Over(), UAddr: nil}, - }, kad) - - // add peers to kademlia and activate them - // it's safe so don't check errors - kad.Register(wrongpsspeer.BzzAddr) - kad.On(wrongpsspeer) - kad.Register(nopsspeer.BzzAddr) - kad.On(nopsspeer) - - // create pss - pssmsg := &PssMsg{ - To: []byte{}, - Expire: uint32(time.Now().Add(time.Second).Unix()), - Payload: &whisper.Envelope{}, - } - ps := newTestPss(privkey, kad, nil) - defer ps.Stop() - - // run the forward - // it is enough that it completes; trying to send to incapable peers would create segfault - ps.forward(pssmsg) - -} - -// verifies that message handlers for raw messages only are invoked when minimum one handler for the topic exists in which raw messages are explicitly allowed -func TestRawAllow(t *testing.T) { - - // set up pss like so many times before - privKey, err := crypto.GenerateKey() - if err != nil { - t.Fatal(err) - } - baseAddr := network.RandomAddr() - kad := network.NewKademlia((baseAddr).Over(), network.NewKadParams()) - ps := newTestPss(privKey, kad, nil) - defer ps.Stop() - topic := BytesToTopic([]byte{0x2a}) - - // create handler innards that increments every time a message hits it - var receives int - rawHandlerFunc := func(msg []byte, p *p2p.Peer, asymmetric bool, keyid string) error { - log.Trace("in allowraw handler") - receives++ - return nil - } - - // wrap this handler function with a handler without raw capability and register it - hndlrNoRaw := &handler{ - f: rawHandlerFunc, - } - ps.Register(&topic, hndlrNoRaw) - - // test it with a raw message, should be poo-poo - pssMsg := newPssMsg(&msgParams{ - raw: true, - }) - pssMsg.To = baseAddr.OAddr - pssMsg.Expire = uint32(time.Now().Unix() + 4200) - pssMsg.Payload = &whisper.Envelope{ - Topic: whisper.TopicType(topic), - } - ps.handlePssMsg(context.TODO(), pssMsg) - if receives > 0 { - t.Fatalf("Expected handler not to be executed with raw cap off") - } - - // now wrap the same handler function with raw capabilities and register it - hndlrRaw := &handler{ - f: rawHandlerFunc, - caps: &handlerCaps{ - raw: true, - }, - } - deregRawHandler := ps.Register(&topic, hndlrRaw) - - // should work now - pssMsg.Payload.Data = []byte("Raw Deal") - ps.handlePssMsg(context.TODO(), pssMsg) - if receives == 0 { - t.Fatalf("Expected handler to be executed with raw cap on") - } - - // now deregister the raw capable handler - prevReceives := receives - deregRawHandler() - - // check that raw messages fail again - pssMsg.Payload.Data = []byte("Raw Trump") - ps.handlePssMsg(context.TODO(), pssMsg) - if receives != prevReceives { - t.Fatalf("Expected handler not to be executed when raw handler is retracted") - } -} - -// BELOW HERE ARE TESTS USING THE SIMULATION FRAMEWORK - -// tests that the API layer can handle edge case values -func TestApi(t *testing.T) { - clients, err := setupNetwork(2, true) - if err != nil { - t.Fatal(err) - } - - topic := "0xdeadbeef" - - err = clients[0].Call(nil, "pss_sendRaw", "0x", topic, "0x666f6f") - if err != nil { - t.Fatal(err) - } - - err = clients[0].Call(nil, "pss_sendRaw", "0xabcdef", topic, "0x") - if err == nil { - t.Fatal("expected error on empty msg") - } - - overflowAddr := [33]byte{} - err = clients[0].Call(nil, "pss_sendRaw", hexutil.Encode(overflowAddr[:]), topic, "0x666f6f") - if err == nil { - t.Fatal("expected error on send too big address") - } -} - -// verifies that nodes can send and receive raw (verbatim) messages -func TestSendRaw(t *testing.T) { - t.Run("32", testSendRaw) - t.Run("8", testSendRaw) - t.Run("0", testSendRaw) -} - -func testSendRaw(t *testing.T) { - - var addrsize int64 - var err error - - paramstring := strings.Split(t.Name(), "/") - - addrsize, _ = strconv.ParseInt(paramstring[1], 10, 0) - log.Info("raw send test", "addrsize", addrsize) - - clients, err := setupNetwork(2, true) - if err != nil { - t.Fatal(err) - } - - topic := "0xdeadbeef" - - var loaddrhex string - err = clients[0].Call(&loaddrhex, "pss_baseAddr") - if err != nil { - t.Fatalf("rpc get node 1 baseaddr fail: %v", err) - } - loaddrhex = loaddrhex[:2+(addrsize*2)] - var roaddrhex string - err = clients[1].Call(&roaddrhex, "pss_baseAddr") - if err != nil { - t.Fatalf("rpc get node 2 baseaddr fail: %v", err) - } - roaddrhex = roaddrhex[:2+(addrsize*2)] - - time.Sleep(time.Millisecond * 500) - - // at this point we've verified that symkeys are saved and match on each peer - // now try sending symmetrically encrypted message, both directions - lmsgC := make(chan APIMsg) - lctx, lcancel := context.WithTimeout(context.Background(), time.Second*10) - defer lcancel() - lsub, err := clients[0].Subscribe(lctx, "pss", lmsgC, "receive", topic, true, false) - log.Trace("lsub", "id", lsub) - defer lsub.Unsubscribe() - rmsgC := make(chan APIMsg) - rctx, rcancel := context.WithTimeout(context.Background(), time.Second*10) - defer rcancel() - rsub, err := clients[1].Subscribe(rctx, "pss", rmsgC, "receive", topic, true, false) - log.Trace("rsub", "id", rsub) - defer rsub.Unsubscribe() - - // send and verify delivery - lmsg := []byte("plugh") - err = clients[1].Call(nil, "pss_sendRaw", loaddrhex, topic, hexutil.Encode(lmsg)) - if err != nil { - t.Fatal(err) - } - select { - case recvmsg := <-lmsgC: - if !bytes.Equal(recvmsg.Msg, lmsg) { - t.Fatalf("node 1 received payload mismatch: expected %v, got %v", lmsg, recvmsg) - } - case cerr := <-lctx.Done(): - t.Fatalf("test message (left) timed out: %v", cerr) - } - rmsg := []byte("xyzzy") - err = clients[0].Call(nil, "pss_sendRaw", roaddrhex, topic, hexutil.Encode(rmsg)) - if err != nil { - t.Fatal(err) - } - select { - case recvmsg := <-rmsgC: - if !bytes.Equal(recvmsg.Msg, rmsg) { - t.Fatalf("node 2 received payload mismatch: expected %x, got %v", rmsg, recvmsg.Msg) - } - case cerr := <-rctx.Done(): - t.Fatalf("test message (right) timed out: %v", cerr) - } -} - -// send symmetrically encrypted message between two directly connected peers -func TestSendSym(t *testing.T) { - t.Run("32", testSendSym) - t.Run("8", testSendSym) - t.Run("0", testSendSym) -} - -func testSendSym(t *testing.T) { - - // address hint size - var addrsize int64 - var err error - paramstring := strings.Split(t.Name(), "/") - addrsize, _ = strconv.ParseInt(paramstring[1], 10, 0) - log.Info("sym send test", "addrsize", addrsize) - - clients, err := setupNetwork(2, false) - if err != nil { - t.Fatal(err) - } - - var topic string - err = clients[0].Call(&topic, "pss_stringToTopic", "foo:42") - if err != nil { - t.Fatal(err) - } - - var loaddrhex string - err = clients[0].Call(&loaddrhex, "pss_baseAddr") - if err != nil { - t.Fatalf("rpc get node 1 baseaddr fail: %v", err) - } - loaddrhex = loaddrhex[:2+(addrsize*2)] - var roaddrhex string - err = clients[1].Call(&roaddrhex, "pss_baseAddr") - if err != nil { - t.Fatalf("rpc get node 2 baseaddr fail: %v", err) - } - roaddrhex = roaddrhex[:2+(addrsize*2)] - - // retrieve public key from pss instance - // set this public key reciprocally - var lpubkeyhex string - err = clients[0].Call(&lpubkeyhex, "pss_getPublicKey") - if err != nil { - t.Fatalf("rpc get node 1 pubkey fail: %v", err) - } - var rpubkeyhex string - err = clients[1].Call(&rpubkeyhex, "pss_getPublicKey") - if err != nil { - t.Fatalf("rpc get node 2 pubkey fail: %v", err) - } - - time.Sleep(time.Millisecond * 500) - - // at this point we've verified that symkeys are saved and match on each peer - // now try sending symmetrically encrypted message, both directions - lmsgC := make(chan APIMsg) - lctx, lcancel := context.WithTimeout(context.Background(), time.Second*10) - defer lcancel() - lsub, err := clients[0].Subscribe(lctx, "pss", lmsgC, "receive", topic, false, false) - log.Trace("lsub", "id", lsub) - defer lsub.Unsubscribe() - rmsgC := make(chan APIMsg) - rctx, rcancel := context.WithTimeout(context.Background(), time.Second*10) - defer rcancel() - rsub, err := clients[1].Subscribe(rctx, "pss", rmsgC, "receive", topic, false, false) - log.Trace("rsub", "id", rsub) - defer rsub.Unsubscribe() - - lrecvkey := network.RandomAddr().Over() - rrecvkey := network.RandomAddr().Over() - - var lkeyids [2]string - var rkeyids [2]string - - // manually set reciprocal symkeys - err = clients[0].Call(&lkeyids, "psstest_setSymKeys", rpubkeyhex, lrecvkey, rrecvkey, defaultSymKeySendLimit, topic, roaddrhex) - if err != nil { - t.Fatal(err) - } - err = clients[1].Call(&rkeyids, "psstest_setSymKeys", lpubkeyhex, rrecvkey, lrecvkey, defaultSymKeySendLimit, topic, loaddrhex) - if err != nil { - t.Fatal(err) - } - - // send and verify delivery - lmsg := []byte("plugh") - err = clients[1].Call(nil, "pss_sendSym", rkeyids[1], topic, hexutil.Encode(lmsg)) - if err != nil { - t.Fatal(err) - } - select { - case recvmsg := <-lmsgC: - if !bytes.Equal(recvmsg.Msg, lmsg) { - t.Fatalf("node 1 received payload mismatch: expected %v, got %v", lmsg, recvmsg) - } - case cerr := <-lctx.Done(): - t.Fatalf("test message timed out: %v", cerr) - } - rmsg := []byte("xyzzy") - err = clients[0].Call(nil, "pss_sendSym", lkeyids[1], topic, hexutil.Encode(rmsg)) - if err != nil { - t.Fatal(err) - } - select { - case recvmsg := <-rmsgC: - if !bytes.Equal(recvmsg.Msg, rmsg) { - t.Fatalf("node 2 received payload mismatch: expected %x, got %v", rmsg, recvmsg.Msg) - } - case cerr := <-rctx.Done(): - t.Fatalf("test message timed out: %v", cerr) - } -} - -// send asymmetrically encrypted message between two directly connected peers -func TestSendAsym(t *testing.T) { - t.Run("32", testSendAsym) - t.Run("8", testSendAsym) - t.Run("0", testSendAsym) -} - -func testSendAsym(t *testing.T) { - - // address hint size - var addrsize int64 - var err error - paramstring := strings.Split(t.Name(), "/") - addrsize, _ = strconv.ParseInt(paramstring[1], 10, 0) - log.Info("asym send test", "addrsize", addrsize) - - clients, err := setupNetwork(2, false) - if err != nil { - t.Fatal(err) - } - - var topic string - err = clients[0].Call(&topic, "pss_stringToTopic", "foo:42") - if err != nil { - t.Fatal(err) - } - - time.Sleep(time.Millisecond * 250) - - var loaddrhex string - err = clients[0].Call(&loaddrhex, "pss_baseAddr") - if err != nil { - t.Fatalf("rpc get node 1 baseaddr fail: %v", err) - } - loaddrhex = loaddrhex[:2+(addrsize*2)] - var roaddrhex string - err = clients[1].Call(&roaddrhex, "pss_baseAddr") - if err != nil { - t.Fatalf("rpc get node 2 baseaddr fail: %v", err) - } - roaddrhex = roaddrhex[:2+(addrsize*2)] - - // retrieve public key from pss instance - // set this public key reciprocally - var lpubkey string - err = clients[0].Call(&lpubkey, "pss_getPublicKey") - if err != nil { - t.Fatalf("rpc get node 1 pubkey fail: %v", err) - } - var rpubkey string - err = clients[1].Call(&rpubkey, "pss_getPublicKey") - if err != nil { - t.Fatalf("rpc get node 2 pubkey fail: %v", err) - } - - time.Sleep(time.Millisecond * 500) // replace with hive healthy code - - lmsgC := make(chan APIMsg) - lctx, lcancel := context.WithTimeout(context.Background(), time.Second*10) - defer lcancel() - lsub, err := clients[0].Subscribe(lctx, "pss", lmsgC, "receive", topic, false, false) - log.Trace("lsub", "id", lsub) - defer lsub.Unsubscribe() - rmsgC := make(chan APIMsg) - rctx, rcancel := context.WithTimeout(context.Background(), time.Second*10) - defer rcancel() - rsub, err := clients[1].Subscribe(rctx, "pss", rmsgC, "receive", topic, false, false) - log.Trace("rsub", "id", rsub) - defer rsub.Unsubscribe() - - // store reciprocal public keys - err = clients[0].Call(nil, "pss_setPeerPublicKey", rpubkey, topic, roaddrhex) - if err != nil { - t.Fatal(err) - } - err = clients[1].Call(nil, "pss_setPeerPublicKey", lpubkey, topic, loaddrhex) - if err != nil { - t.Fatal(err) - } - - // send and verify delivery - rmsg := []byte("xyzzy") - err = clients[0].Call(nil, "pss_sendAsym", rpubkey, topic, hexutil.Encode(rmsg)) - if err != nil { - t.Fatal(err) - } - select { - case recvmsg := <-rmsgC: - if !bytes.Equal(recvmsg.Msg, rmsg) { - t.Fatalf("node 2 received payload mismatch: expected %v, got %v", rmsg, recvmsg.Msg) - } - case cerr := <-rctx.Done(): - t.Fatalf("test message timed out: %v", cerr) - } - lmsg := []byte("plugh") - err = clients[1].Call(nil, "pss_sendAsym", lpubkey, topic, hexutil.Encode(lmsg)) - if err != nil { - t.Fatal(err) - } - select { - case recvmsg := <-lmsgC: - if !bytes.Equal(recvmsg.Msg, lmsg) { - t.Fatalf("node 1 received payload mismatch: expected %v, got %v", lmsg, recvmsg.Msg) - } - case cerr := <-lctx.Done(): - t.Fatalf("test message timed out: %v", cerr) - } -} - -type Job struct { - Msg []byte - SendNode enode.ID - RecvNode enode.ID -} - -func worker(id int, jobs <-chan Job, rpcs map[enode.ID]*rpc.Client, pubkeys map[enode.ID]string, topic string) { - for j := range jobs { - rpcs[j.SendNode].Call(nil, "pss_sendAsym", pubkeys[j.RecvNode], topic, hexutil.Encode(j.Msg)) - } -} - -func TestNetwork(t *testing.T) { - t.Run("16/1000/4/sim", testNetwork) -} - -// params in run name: -// nodes/msgs/addrbytes/adaptertype -// if adaptertype is exec uses execadapter, simadapter otherwise -func TestNetwork2000(t *testing.T) { - //enableMetrics() - - if !*longrunning { - t.Skip("run with --longrunning flag to run extensive network tests") - } - t.Run("3/2000/4/sim", testNetwork) - t.Run("4/2000/4/sim", testNetwork) - t.Run("8/2000/4/sim", testNetwork) - t.Run("16/2000/4/sim", testNetwork) -} - -func TestNetwork5000(t *testing.T) { - //enableMetrics() - - if !*longrunning { - t.Skip("run with --longrunning flag to run extensive network tests") - } - t.Run("3/5000/4/sim", testNetwork) - t.Run("4/5000/4/sim", testNetwork) - t.Run("8/5000/4/sim", testNetwork) - t.Run("16/5000/4/sim", testNetwork) -} - -func TestNetwork10000(t *testing.T) { - //enableMetrics() - - if !*longrunning { - t.Skip("run with --longrunning flag to run extensive network tests") - } - t.Run("3/10000/4/sim", testNetwork) - t.Run("4/10000/4/sim", testNetwork) - t.Run("8/10000/4/sim", testNetwork) -} - -func testNetwork(t *testing.T) { - paramstring := strings.Split(t.Name(), "/") - nodecount, _ := strconv.ParseInt(paramstring[1], 10, 0) - msgcount, _ := strconv.ParseInt(paramstring[2], 10, 0) - addrsize, _ := strconv.ParseInt(paramstring[3], 10, 0) - adapter := paramstring[4] - - log.Info("network test", "nodecount", nodecount, "msgcount", msgcount, "addrhintsize", addrsize) - - nodes := make([]enode.ID, nodecount) - bzzaddrs := make(map[enode.ID]string, nodecount) - rpcs := make(map[enode.ID]*rpc.Client, nodecount) - pubkeys := make(map[enode.ID]string, nodecount) - - sentmsgs := make([][]byte, msgcount) - recvmsgs := make([]bool, msgcount) - nodemsgcount := make(map[enode.ID]int, nodecount) - - trigger := make(chan enode.ID) - - var a adapters.NodeAdapter - if adapter == "exec" { - dirname, err := ioutil.TempDir(".", "") - if err != nil { - t.Fatal(err) - } - a = adapters.NewExecAdapter(dirname) - } else if adapter == "tcp" { - a = adapters.NewTCPAdapter(newServices(false)) - } else if adapter == "sim" { - a = adapters.NewSimAdapter(newServices(false)) - } - net := simulations.NewNetwork(a, &simulations.NetworkConfig{ - ID: "0", - }) - defer net.Shutdown() - - f, err := os.Open(fmt.Sprintf("testdata/snapshot_%d.json", nodecount)) - if err != nil { - t.Fatal(err) - } - jsonbyte, err := ioutil.ReadAll(f) - if err != nil { - t.Fatal(err) - } - var snap simulations.Snapshot - err = json.Unmarshal(jsonbyte, &snap) - if err != nil { - t.Fatal(err) - } - err = net.Load(&snap) - if err != nil { - //TODO: Fix p2p simulation framework to not crash when loading 32-nodes - //t.Fatal(err) - } - - time.Sleep(1 * time.Second) - - triggerChecks := func(trigger chan enode.ID, id enode.ID, rpcclient *rpc.Client, topic string) error { - msgC := make(chan APIMsg) - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - sub, err := rpcclient.Subscribe(ctx, "pss", msgC, "receive", topic, false, false) - if err != nil { - t.Fatal(err) - } - go func() { - defer sub.Unsubscribe() - for { - select { - case recvmsg := <-msgC: - idx, _ := binary.Uvarint(recvmsg.Msg) - if !recvmsgs[idx] { - log.Debug("msg recv", "idx", idx, "id", id) - recvmsgs[idx] = true - trigger <- id - } - case <-sub.Err(): - return - } - } - }() - return nil - } - - var topic string - for i, nod := range net.GetNodes() { - nodes[i] = nod.ID() - rpcs[nodes[i]], err = nod.Client() - if err != nil { - t.Fatal(err) - } - if topic == "" { - err = rpcs[nodes[i]].Call(&topic, "pss_stringToTopic", "foo:42") - if err != nil { - t.Fatal(err) - } - } - var pubkey string - err = rpcs[nodes[i]].Call(&pubkey, "pss_getPublicKey") - if err != nil { - t.Fatal(err) - } - pubkeys[nod.ID()] = pubkey - var addrhex string - err = rpcs[nodes[i]].Call(&addrhex, "pss_baseAddr") - if err != nil { - t.Fatal(err) - } - bzzaddrs[nodes[i]] = addrhex - err = triggerChecks(trigger, nodes[i], rpcs[nodes[i]], topic) - if err != nil { - t.Fatal(err) - } - } - - time.Sleep(1 * time.Second) - - // setup workers - jobs := make(chan Job, 10) - for w := 1; w <= 10; w++ { - go worker(w, jobs, rpcs, pubkeys, topic) - } - - time.Sleep(1 * time.Second) - - for i := 0; i < int(msgcount); i++ { - sendnodeidx := rand.Intn(int(nodecount)) - recvnodeidx := rand.Intn(int(nodecount - 1)) - if recvnodeidx >= sendnodeidx { - recvnodeidx++ - } - nodemsgcount[nodes[recvnodeidx]]++ - sentmsgs[i] = make([]byte, 8) - c := binary.PutUvarint(sentmsgs[i], uint64(i)) - if c == 0 { - t.Fatal("0 byte message") - } - if err != nil { - t.Fatal(err) - } - err = rpcs[nodes[sendnodeidx]].Call(nil, "pss_setPeerPublicKey", pubkeys[nodes[recvnodeidx]], topic, bzzaddrs[nodes[recvnodeidx]]) - if err != nil { - t.Fatal(err) - } - - jobs <- Job{ - Msg: sentmsgs[i], - SendNode: nodes[sendnodeidx], - RecvNode: nodes[recvnodeidx], - } - } - - finalmsgcount := 0 - ctx, cancel := context.WithTimeout(context.Background(), 120*time.Second) - defer cancel() -outer: - for i := 0; i < int(msgcount); i++ { - select { - case id := <-trigger: - nodemsgcount[id]-- - finalmsgcount++ - case <-ctx.Done(): - log.Warn("timeout") - break outer - } - } - - for i, msg := range recvmsgs { - if !msg { - log.Debug("missing message", "idx", i) - } - } - t.Logf("%d of %d messages received", finalmsgcount, msgcount) - - if finalmsgcount != int(msgcount) { - t.Fatalf("%d messages were not received", int(msgcount)-finalmsgcount) - } - -} - -// check that in a network of a -> b -> c -> a -// a doesn't receive a sent message twice -func TestDeduplication(t *testing.T) { - var err error - - clients, err := setupNetwork(3, false) - if err != nil { - t.Fatal(err) - } - - var addrsize = 32 - var loaddrhex string - err = clients[0].Call(&loaddrhex, "pss_baseAddr") - if err != nil { - t.Fatalf("rpc get node 1 baseaddr fail: %v", err) - } - loaddrhex = loaddrhex[:2+(addrsize*2)] - var roaddrhex string - err = clients[1].Call(&roaddrhex, "pss_baseAddr") - if err != nil { - t.Fatalf("rpc get node 2 baseaddr fail: %v", err) - } - roaddrhex = roaddrhex[:2+(addrsize*2)] - var xoaddrhex string - err = clients[2].Call(&xoaddrhex, "pss_baseAddr") - if err != nil { - t.Fatalf("rpc get node 3 baseaddr fail: %v", err) - } - xoaddrhex = xoaddrhex[:2+(addrsize*2)] - - log.Info("peer", "l", loaddrhex, "r", roaddrhex, "x", xoaddrhex) - - var topic string - err = clients[0].Call(&topic, "pss_stringToTopic", "foo:42") - if err != nil { - t.Fatal(err) - } - - time.Sleep(time.Millisecond * 250) - - // retrieve public key from pss instance - // set this public key reciprocally - var rpubkey string - err = clients[1].Call(&rpubkey, "pss_getPublicKey") - if err != nil { - t.Fatalf("rpc get receivenode pubkey fail: %v", err) - } - - time.Sleep(time.Millisecond * 500) // replace with hive healthy code - - rmsgC := make(chan APIMsg) - rctx, cancel := context.WithTimeout(context.Background(), time.Second*1) - defer cancel() - rsub, err := clients[1].Subscribe(rctx, "pss", rmsgC, "receive", topic, false, false) - log.Trace("rsub", "id", rsub) - defer rsub.Unsubscribe() - - // store public key for recipient - // zero-length address means forward to all - // we have just two peers, they will be in proxbin, and will both receive - err = clients[0].Call(nil, "pss_setPeerPublicKey", rpubkey, topic, "0x") - if err != nil { - t.Fatal(err) - } - - // send and verify delivery - rmsg := []byte("xyzzy") - err = clients[0].Call(nil, "pss_sendAsym", rpubkey, topic, hexutil.Encode(rmsg)) - if err != nil { - t.Fatal(err) - } - - var receivedok bool -OUTER: - for { - select { - case <-rmsgC: - if receivedok { - t.Fatalf("duplicate message received") - } - receivedok = true - case <-rctx.Done(): - break OUTER - } - } - if !receivedok { - t.Fatalf("message did not arrive") - } -} - -// symmetric send performance with varying message sizes -func BenchmarkSymkeySend(b *testing.B) { - b.Run(fmt.Sprintf("%d", 256), benchmarkSymKeySend) - b.Run(fmt.Sprintf("%d", 1024), benchmarkSymKeySend) - b.Run(fmt.Sprintf("%d", 1024*1024), benchmarkSymKeySend) - b.Run(fmt.Sprintf("%d", 1024*1024*10), benchmarkSymKeySend) - b.Run(fmt.Sprintf("%d", 1024*1024*100), benchmarkSymKeySend) -} - -func benchmarkSymKeySend(b *testing.B) { - msgsizestring := strings.Split(b.Name(), "/") - if len(msgsizestring) != 2 { - b.Fatalf("benchmark called without msgsize param") - } - msgsize, err := strconv.ParseInt(msgsizestring[1], 10, 0) - if err != nil { - b.Fatalf("benchmark called with invalid msgsize param '%s': %v", msgsizestring[1], err) - } - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - keys, err := wapi.NewKeyPair(ctx) - privkey, err := w.GetPrivateKey(keys) - ps := newTestPss(privkey, nil, nil) - defer ps.Stop() - msg := make([]byte, msgsize) - rand.Read(msg) - topic := BytesToTopic([]byte("foo")) - to := make(PssAddress, 32) - copy(to[:], network.RandomAddr().Over()) - symkeyid, err := ps.GenerateSymmetricKey(topic, to, true) - if err != nil { - b.Fatalf("could not generate symkey: %v", err) - } - symkey, err := ps.w.GetSymKey(symkeyid) - if err != nil { - b.Fatalf("could not retrieve symkey: %v", err) - } - ps.SetSymmetricKey(symkey, topic, to, false) - - b.ResetTimer() - for i := 0; i < b.N; i++ { - ps.SendSym(symkeyid, topic, msg) - } -} - -// asymmetric send performance with varying message sizes -func BenchmarkAsymkeySend(b *testing.B) { - b.Run(fmt.Sprintf("%d", 256), benchmarkAsymKeySend) - b.Run(fmt.Sprintf("%d", 1024), benchmarkAsymKeySend) - b.Run(fmt.Sprintf("%d", 1024*1024), benchmarkAsymKeySend) - b.Run(fmt.Sprintf("%d", 1024*1024*10), benchmarkAsymKeySend) - b.Run(fmt.Sprintf("%d", 1024*1024*100), benchmarkAsymKeySend) -} - -func benchmarkAsymKeySend(b *testing.B) { - msgsizestring := strings.Split(b.Name(), "/") - if len(msgsizestring) != 2 { - b.Fatalf("benchmark called without msgsize param") - } - msgsize, err := strconv.ParseInt(msgsizestring[1], 10, 0) - if err != nil { - b.Fatalf("benchmark called with invalid msgsize param '%s': %v", msgsizestring[1], err) - } - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - keys, err := wapi.NewKeyPair(ctx) - privkey, err := w.GetPrivateKey(keys) - ps := newTestPss(privkey, nil, nil) - defer ps.Stop() - msg := make([]byte, msgsize) - rand.Read(msg) - topic := BytesToTopic([]byte("foo")) - to := make(PssAddress, 32) - copy(to[:], network.RandomAddr().Over()) - ps.SetPeerPublicKey(&privkey.PublicKey, topic, to) - b.ResetTimer() - for i := 0; i < b.N; i++ { - ps.SendAsym(common.ToHex(crypto.FromECDSAPub(&privkey.PublicKey)), topic, msg) - } -} -func BenchmarkSymkeyBruteforceChangeaddr(b *testing.B) { - for i := 100; i < 100000; i = i * 10 { - for j := 32; j < 10000; j = j * 8 { - b.Run(fmt.Sprintf("%d/%d", i, j), benchmarkSymkeyBruteforceChangeaddr) - } - //b.Run(fmt.Sprintf("%d", i), benchmarkSymkeyBruteforceChangeaddr) - } -} - -// decrypt performance using symkey cache, worst case -// (decrypt key always last in cache) -func benchmarkSymkeyBruteforceChangeaddr(b *testing.B) { - keycountstring := strings.Split(b.Name(), "/") - cachesize := int64(0) - var ps *Pss - if len(keycountstring) < 2 { - b.Fatalf("benchmark called without count param") - } - keycount, err := strconv.ParseInt(keycountstring[1], 10, 0) - if err != nil { - b.Fatalf("benchmark called with invalid count param '%s': %v", keycountstring[1], err) - } - if len(keycountstring) == 3 { - cachesize, err = strconv.ParseInt(keycountstring[2], 10, 0) - if err != nil { - b.Fatalf("benchmark called with invalid cachesize '%s': %v", keycountstring[2], err) - } - } - pssmsgs := make([]*PssMsg, 0, keycount) - var keyid string - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - keys, err := wapi.NewKeyPair(ctx) - privkey, err := w.GetPrivateKey(keys) - if cachesize > 0 { - ps = newTestPss(privkey, nil, &PssParams{SymKeyCacheCapacity: int(cachesize)}) - } else { - ps = newTestPss(privkey, nil, nil) - } - defer ps.Stop() - topic := BytesToTopic([]byte("foo")) - for i := 0; i < int(keycount); i++ { - to := make(PssAddress, 32) - copy(to[:], network.RandomAddr().Over()) - keyid, err = ps.GenerateSymmetricKey(topic, to, true) - if err != nil { - b.Fatalf("cant generate symkey #%d: %v", i, err) - } - symkey, err := ps.w.GetSymKey(keyid) - if err != nil { - b.Fatalf("could not retrieve symkey %s: %v", keyid, err) - } - wparams := &whisper.MessageParams{ - TTL: defaultWhisperTTL, - KeySym: symkey, - Topic: whisper.TopicType(topic), - WorkTime: defaultWhisperWorkTime, - PoW: defaultWhisperPoW, - Payload: []byte("xyzzy"), - Padding: []byte("1234567890abcdef"), - } - woutmsg, err := whisper.NewSentMessage(wparams) - if err != nil { - b.Fatalf("could not create whisper message: %v", err) - } - env, err := woutmsg.Wrap(wparams) - if err != nil { - b.Fatalf("could not generate whisper envelope: %v", err) - } - ps.Register(&topic, &handler{ - f: noopHandlerFunc, - }) - pssmsgs = append(pssmsgs, &PssMsg{ - To: to, - Payload: env, - }) - } - b.ResetTimer() - for i := 0; i < b.N; i++ { - if err := ps.process(pssmsgs[len(pssmsgs)-(i%len(pssmsgs))-1], false, false); err != nil { - b.Fatalf("pss processing failed: %v", err) - } - } -} - -func BenchmarkSymkeyBruteforceSameaddr(b *testing.B) { - for i := 100; i < 100000; i = i * 10 { - for j := 32; j < 10000; j = j * 8 { - b.Run(fmt.Sprintf("%d/%d", i, j), benchmarkSymkeyBruteforceSameaddr) - } - } -} - -// decrypt performance using symkey cache, best case -// (decrypt key always first in cache) -func benchmarkSymkeyBruteforceSameaddr(b *testing.B) { - var keyid string - var ps *Pss - cachesize := int64(0) - keycountstring := strings.Split(b.Name(), "/") - if len(keycountstring) < 2 { - b.Fatalf("benchmark called without count param") - } - keycount, err := strconv.ParseInt(keycountstring[1], 10, 0) - if err != nil { - b.Fatalf("benchmark called with invalid count param '%s': %v", keycountstring[1], err) - } - if len(keycountstring) == 3 { - cachesize, err = strconv.ParseInt(keycountstring[2], 10, 0) - if err != nil { - b.Fatalf("benchmark called with invalid cachesize '%s': %v", keycountstring[2], err) - } - } - addr := make([]PssAddress, keycount) - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - keys, err := wapi.NewKeyPair(ctx) - privkey, err := w.GetPrivateKey(keys) - if cachesize > 0 { - ps = newTestPss(privkey, nil, &PssParams{SymKeyCacheCapacity: int(cachesize)}) - } else { - ps = newTestPss(privkey, nil, nil) - } - defer ps.Stop() - topic := BytesToTopic([]byte("foo")) - for i := 0; i < int(keycount); i++ { - copy(addr[i], network.RandomAddr().Over()) - keyid, err = ps.GenerateSymmetricKey(topic, addr[i], true) - if err != nil { - b.Fatalf("cant generate symkey #%d: %v", i, err) - } - - } - symkey, err := ps.w.GetSymKey(keyid) - if err != nil { - b.Fatalf("could not retrieve symkey %s: %v", keyid, err) - } - wparams := &whisper.MessageParams{ - TTL: defaultWhisperTTL, - KeySym: symkey, - Topic: whisper.TopicType(topic), - WorkTime: defaultWhisperWorkTime, - PoW: defaultWhisperPoW, - Payload: []byte("xyzzy"), - Padding: []byte("1234567890abcdef"), - } - woutmsg, err := whisper.NewSentMessage(wparams) - if err != nil { - b.Fatalf("could not create whisper message: %v", err) - } - env, err := woutmsg.Wrap(wparams) - if err != nil { - b.Fatalf("could not generate whisper envelope: %v", err) - } - ps.Register(&topic, &handler{ - f: noopHandlerFunc, - }) - pssmsg := &PssMsg{ - To: addr[len(addr)-1][:], - Payload: env, - } - for i := 0; i < b.N; i++ { - if err := ps.process(pssmsg, false, false); err != nil { - b.Fatalf("pss processing failed: %v", err) - } - } -} - -// setup simulated network with bzz/discovery and pss services. -// connects nodes in a circle -// if allowRaw is set, omission of builtin pss encryption is enabled (see PssParams) -func setupNetwork(numnodes int, allowRaw bool) (clients []*rpc.Client, err error) { - nodes := make([]*simulations.Node, numnodes) - clients = make([]*rpc.Client, numnodes) - if numnodes < 2 { - return nil, fmt.Errorf("Minimum two nodes in network") - } - adapter := adapters.NewSimAdapter(newServices(allowRaw)) - net := simulations.NewNetwork(adapter, &simulations.NetworkConfig{ - ID: "0", - DefaultService: "bzz", - }) - for i := 0; i < numnodes; i++ { - nodeconf := adapters.RandomNodeConfig() - nodeconf.Services = []string{"bzz", pssProtocolName} - nodes[i], err = net.NewNodeWithConfig(nodeconf) - if err != nil { - return nil, fmt.Errorf("error creating node 1: %v", err) - } - err = net.Start(nodes[i].ID()) - if err != nil { - return nil, fmt.Errorf("error starting node 1: %v", err) - } - if i > 0 { - err = net.Connect(nodes[i].ID(), nodes[i-1].ID()) - if err != nil { - return nil, fmt.Errorf("error connecting nodes: %v", err) - } - } - clients[i], err = nodes[i].Client() - if err != nil { - return nil, fmt.Errorf("create node 1 rpc client fail: %v", err) - } - } - if numnodes > 2 { - err = net.Connect(nodes[0].ID(), nodes[len(nodes)-1].ID()) - if err != nil { - return nil, fmt.Errorf("error connecting first and last nodes") - } - } - return clients, nil -} - -func newServices(allowRaw bool) adapters.Services { - stateStore := state.NewInmemoryStore() - kademlias := make(map[enode.ID]*network.Kademlia) - kademlia := func(id enode.ID) *network.Kademlia { - if k, ok := kademlias[id]; ok { - return k - } - params := network.NewKadParams() - params.NeighbourhoodSize = 2 - params.MaxBinSize = 3 - params.MinBinSize = 1 - params.MaxRetries = 1000 - params.RetryExponent = 2 - params.RetryInterval = 1000000 - kademlias[id] = network.NewKademlia(id[:], params) - return kademlias[id] - } - return adapters.Services{ - pssProtocolName: func(ctx *adapters.ServiceContext) (node.Service, error) { - // execadapter does not exec init() - initTest() - - ctxlocal, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - keys, err := wapi.NewKeyPair(ctxlocal) - privkey, err := w.GetPrivateKey(keys) - pssp := NewPssParams().WithPrivateKey(privkey) - pssp.AllowRaw = allowRaw - pskad := kademlia(ctx.Config.ID) - ps, err := NewPss(pskad, pssp) - if err != nil { - return nil, err - } - - ping := &Ping{ - OutC: make(chan bool), - Pong: true, - } - p2pp := NewPingProtocol(ping) - pp, err := RegisterProtocol(ps, &PingTopic, PingProtocol, p2pp, &ProtocolParams{Asymmetric: true}) - if err != nil { - return nil, err - } - if useHandshake { - SetHandshakeController(ps, NewHandshakeParams()) - } - ps.Register(&PingTopic, &handler{ - f: pp.Handle, - caps: &handlerCaps{ - raw: true, - }, - }) - ps.addAPI(rpc.API{ - Namespace: "psstest", - Version: "0.3", - Service: NewAPITest(ps), - Public: false, - }) - if err != nil { - log.Error("Couldnt register pss protocol", "err", err) - os.Exit(1) - } - pssprotocols[ctx.Config.ID.String()] = &protoCtrl{ - C: ping.OutC, - protocol: pp, - run: p2pp.Run, - } - return ps, nil - }, - "bzz": func(ctx *adapters.ServiceContext) (node.Service, error) { - addr := network.NewAddr(ctx.Config.Node()) - hp := network.NewHiveParams() - hp.Discovery = false - config := &network.BzzConfig{ - OverlayAddr: addr.Over(), - UnderlayAddr: addr.Under(), - HiveParams: hp, - } - return network.NewBzz(config, kademlia(ctx.Config.ID), stateStore, nil, nil), nil - }, - } -} - -func newTestPss(privkey *ecdsa.PrivateKey, kad *network.Kademlia, ppextra *PssParams) *Pss { - nid := enode.PubkeyToIDV4(&privkey.PublicKey) - // set up routing if kademlia is not passed to us - if kad == nil { - kp := network.NewKadParams() - kp.NeighbourhoodSize = 3 - kad = network.NewKademlia(nid[:], kp) - } - - // create pss - pp := NewPssParams().WithPrivateKey(privkey) - if ppextra != nil { - pp.SymKeyCacheCapacity = ppextra.SymKeyCacheCapacity - } - ps, err := NewPss(kad, pp) - if err != nil { - return nil - } - ps.Start(nil) - - return ps -} - -// API calls for test/development use -type APITest struct { - *Pss -} - -func NewAPITest(ps *Pss) *APITest { - return &APITest{Pss: ps} -} - -func (apitest *APITest) SetSymKeys(pubkeyid string, recvsymkey []byte, sendsymkey []byte, limit uint16, topic Topic, to hexutil.Bytes) ([2]string, error) { - - recvsymkeyid, err := apitest.SetSymmetricKey(recvsymkey, topic, PssAddress(to), true) - if err != nil { - return [2]string{}, err - } - sendsymkeyid, err := apitest.SetSymmetricKey(sendsymkey, topic, PssAddress(to), false) - if err != nil { - return [2]string{}, err - } - return [2]string{recvsymkeyid, sendsymkeyid}, nil -} - -func (apitest *APITest) Clean() (int, error) { - return apitest.Pss.cleanKeys(), nil -} - -// enableMetrics is starting InfluxDB reporter so that we collect stats when running tests locally -func enableMetrics() { - metrics.Enabled = true - go influxdb.InfluxDBWithTags(metrics.DefaultRegistry, 1*time.Second, "http://localhost:8086", "metrics", "admin", "admin", "swarm.", map[string]string{ - "host": "test", - }) -} diff --git a/swarm/pss/testdata/addpsstodiscoverytestsnapshot.pl b/swarm/pss/testdata/addpsstodiscoverytestsnapshot.pl deleted file mode 100644 index b75cc9894add..000000000000 --- a/swarm/pss/testdata/addpsstodiscoverytestsnapshot.pl +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/perl - -use JSON; - -my $f; -my $jsontext; -my $nodelist; -my $network; - -open($f, "<", $ARGV[0]) || die "cant open " . $ARGV[0]; -while (<$f>) { - $jsontext .= $_; -} -close($f); - -$network = decode_json($jsontext); -$nodelist = $network->{'nodes'}; - -for ($i = 0; $i < 0+@$nodelist; $i++) { - #my $protocollist = $$nodelist[$i]{'node'}{'info'}{'protocols'}; - #$$protocollist{'pss'} = "pss"; - my $svc = $$nodelist[$i]{'node'}{'config'}{'services'}; - pop(@$svc); - push(@$svc, "pss"); - push(@$svc, "bzz"); -} - -print encode_json($network); diff --git a/swarm/pss/testdata/addpsstodiscoverytestsnapshot.sh b/swarm/pss/testdata/addpsstodiscoverytestsnapshot.sh deleted file mode 100644 index 7d3c2849169d..000000000000 --- a/swarm/pss/testdata/addpsstodiscoverytestsnapshot.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash - -sed -e 's/\(\"services\"\):\["discovery\"]/\1:["pss","bzz"]/' diff --git a/swarm/pss/testdata/snapshot_128.json b/swarm/pss/testdata/snapshot_128.json deleted file mode 100644 index 7bafd358fb22..000000000000 --- a/swarm/pss/testdata/snapshot_128.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes":[{"node":{"config":{"id":"c42f36bf6728b24721ce5ac1272a1058e202a236d276af1265a1eaa675f8fd85","private_key":"79eaaa1c3a9339a90cf54c511649caf683f2910588a872d2c12919355b7d5d28","name":"node01","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"11b53fa40926ac84d75d9d698f46b9611b90194857c1397e117931a890447078","private_key":"b067839f81534251ade8651e682dbd8324dfb83c7034aff4a48909e9310c990a","name":"node02","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"60cbf513366da3f6ee288d3572dc3971ae256b02e5492595e63c31a7b1b0d5a8","private_key":"4233e4c480ae197c265975cc7c83cc7b0cf1a8d67e4728bac4bcecaee63ad935","name":"node03","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"869fc21fff150ce9e1bc4512b183559433767cfc1cb7b6baae6d2a1313245ae8","private_key":"ca0c9f1baad4f60ddeaafe287d43b4ef8ec4b96c4ef12da194074325ca6cc4ef","name":"node04","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"a20d591ea15722712e83fcc6c17732f2370034ef870234a0fa0b2f4aea572fd5","private_key":"b6c09a581c2a6d85a63c11e586391346fe9d9d24292de15333ae230a33c52c1e","name":"node05","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"18b0fcbba2350241088129e8f6a07742857c3edaacd6ef1ec0861cc8e48492ce","private_key":"378e3e11e738557d2eea27e070d52c8355f8abe0c5f8607ac0792455a1d50bae","name":"node06","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"f84317ef488d8c1495b429b7a1fa21ea53a73e737b3c1aa890c20e38658ab148","private_key":"e6710b29bb9b7f00ee1e921bd548fc7622d73a0aae3f25de7a3f3650191147a8","name":"node07","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"0f19fb4d0c6cfe36f7f59c8fe54d2e169687603e1bde0fe6b3a5e864fa51f2f0","private_key":"2bed1cf9737dbf8239f560ab8b4e57dc47cf57a28ebd203e6fb159093fbe52c0","name":"node08","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"7c992ceefe03187bce1edbf8f80ccd1cab80b3908e625a61fef21ec6d35d04a3","private_key":"3f63c3dd3bf2b5be6e9af3ce596eb65cb58a36749d0baff1759d8dc6f4da8993","name":"node09","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"66f6ff5fbc2526c1db5db491e49138362bef23032c6344ed4ead7dcb58bd1e6c","private_key":"38ffbfff1b5ab2f905daabcbbc12a5e28aad826a80d0a40988a04d6653942a50","name":"node10","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","private_key":"aa2cc30f2f6e589ca122890fc95845f4a81ad7e57f2661343ff6af3d401c46f3","name":"node11","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"74a4ffb0d717a523ec72e07cc5efe3e98d55932d6e0592ef45fe54b378e4e952","private_key":"5b4a4da121c72f3d0d453394e6f09fc9ddaa5a13e44acb1ef6684867b4cac14c","name":"node12","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"20c4c3a3523960ad74538e726e1caaeda987cbc16dcd3beeb63fca6eebd31405","private_key":"98f5b9f4be5d997b66834ae619c58f54d941623dfcb9a6783bfce77fdb3f3d4b","name":"node13","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"45cdaac4c087e6b737507fa29936d09f2fbea14f49ef0ce91c18080456a7459b","private_key":"cbbe3c5ab5aafd2dae03deda6db9a3e7e58ffaad5c1edd37d7a13951aa733590","name":"node14","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"81748a1a38162f9e8613ffa562add1b75893b66199840e74c0774db92bfd7ca0","private_key":"62a83d45655860933bf8a2348695dd695176f3225f4ba72e70284b648130d330","name":"node15","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"cd944ad4c50c4357005ff26ef26369ba3375a1205eec532b6e51b22bf25510bc","private_key":"8c72a0c564073065c5300e822a476fe3dd8b373cfd0dabaf0cdb056ca0ece2f9","name":"node16","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"6b7da636e8eccac658c0abf6aa8ddd74be6d4cf9c5e0333bd28d9a6d1fe43ab3","private_key":"b6eb72555f9952a32406b7576ac85ead5cbe9004f37b6ebe8f7c3b6e17973104","name":"node17","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"6421894e25e421d16eccd8858aed3354fce2d04c9d5c46d4005ee1f16944ccfd","private_key":"e9bdb2a275f0f2fd6ef4266bca55fad475c5d9e4ee0db2951ae91fd629cb2029","name":"node18","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"051c8d430b54d843feb325c2b56e36524566d64f48f6c770739748409af44364","private_key":"d9a2bcdb02288fd5844d0be689ea4286f27991bfc82c76f6050a3e3d2f0858a1","name":"node19","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"459a945c8d6e67437bdfa0e067962b3591dfa0aa7c1177f8327ba1557bbf1569","private_key":"8ee0c4634570903c3d7383adb12cd35a82bb1c9be593755bd77c90a1bd6bbdfe","name":"node20","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"9a8c60732ff0d0b7403b181cdc5bb9e6db0aa4cf5c7158c40e548fcb76726f87","private_key":"16df12116ebc93bdc671ba1351bf03b763d3e67b2c9c468e56177c80b0dcbf84","name":"node21","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"6b1f3a9b65ce3a168fc98dee2b14b1de6912fd02ee2609b047b2c56f6c3b53e9","private_key":"1a78b7fedaab9310b17675717481b1331eed3fa3c77cd96addf6bf9abd778aae","name":"node22","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1263f18bf8cb4f5a3335f545cdd5d42f1b84da9edf4fa7346572c2bc38c87eb3","private_key":"3d37e996f181b4f57d5c5a61a07f86f1869c760604dfe9166d5b3552acf1a43b","name":"node23","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"580a650c2c3e95d35e66264df6f48e28a091bacd20dc2c9828a5b563353d2052","private_key":"5895ce723440eaa77daf0a8779cc52992427524e420e3ce71ce1b24f3bd4658c","name":"node24","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"6c2fd129c8493cea7634a91aae559ceb64f01cebc45ec59aebd7e709b65941be","private_key":"cf20ef905d7d3d1141c472afefd12332bff10edecc695e409af38086c7a1a5d1","name":"node25","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","private_key":"1a7c744024c7baee8c43425861a5a4ff2ea80533fe6549a58e61b50c93059fed","name":"node26","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"4a2d15b867a620653f099d66839b80c2dd32aec95caf906f0d5c8146192edf62","private_key":"7399aa5562abb3ca2add08c810d607ad6fed7a036622eaea561da8a5aa51c0af","name":"node27","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"f9e4dbb28099af8ca5f28c641a51672f4c6ab54b7037f765a7172a399acb8edc","private_key":"ded7b34b0c8218bfba59e8d061b50cea365a9f137b9a66064e2287ef660dc789","name":"node28","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"9265383ecf776ce1da66e99e5f0a9c1af593d8e56ceef21f42aad4be7e34bc45","private_key":"39ac5498c92329fc18c4eb0ec36cca3c5270f322084a1fc42fad1be0b5f32081","name":"node29","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"fb211f405a7dc3a07b6d4f18522c6273ffa4e18325958eea159e33f768eae846","private_key":"add3ad2926ebf0b30f111796475cf160bcd1f1756866dfaa19e048c6954975c9","name":"node30","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"7d14fd0b6c554f218249fed7709df51946baf91a3045ed99fb662c32cc97541b","private_key":"d5290ae40b68ae7b51fe7ce7d83ab96841aa97a3457fc1bcf70065a2d2b60c20","name":"node31","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"816609d5b9bdb0c7bad6cbe9400fd506df8662b14ad0e0a61bc7b49482548e39","private_key":"ba7abd532b10496c6363f35e231ff80aef25246315302fd138dd977d5ece20e5","name":"node32","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","private_key":"e58bb287592c2b89814ed3475004f5c9b2eb226483fcb8235619b6b42747d10c","name":"node33","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"8ff601de9537c9e5d79a3707a312fe893383bef000d00acee3d4552068ef6030","private_key":"53e9afc6c039fee226bd9a0b537355f23e93457dc0eabbdce75e4d1ad7a473e6","name":"node34","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"5f8eb6ff0f6ed720eaae86390757833f4b9cdfa52564457ed90c3de260ab6c30","private_key":"079cf6730627562bbbff031d22ab1ae9e65b7747497adc327830e5d6768d6b04","name":"node35","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"85845413850f5e38cce69548e20179b477bf383dd2b22428358715d9d72421ac","private_key":"c3157fe034d3a477f697b756cd9ae1de532b0ae42bb5039f6d2bf399bb2ddbbd","name":"node36","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"57d3361ee59d006e1dba3e1ae9ea624444ee09f70410dff492d624d7e7786d2d","private_key":"d4472d7bc821536231d70dbdb3f0a5e3fdd104dcbf5a97c9521b0778d9491680","name":"node37","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"776fdbabbca42ecb49cc5451b860305ec3020889b47adaa2add6ea359afe379f","private_key":"23c79a6a5af06f9e9cdb6f4b4e40e25eca8793ac91db22cde17e0a3851c1f48e","name":"node38","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"22b84df73059c92bf59fdad51fe574e027c7cc555dddbf363d745c1c422a89fd","private_key":"0295fa1706ccdcbdc8d7943b8d2011c6f46225d85b574d2d02b4a8dec66f9a29","name":"node39","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"2c3d7e056f87daf540821a31ea8fe9377d00ed3a55ffba67b8bb716cdd9b7303","private_key":"34d1685a48b56e62b30247fffb44ba2b41f2d806344fe52f7dc9049a778c667f","name":"node40","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"746898d197934d17cd93b958ab20aaa85dba99057495048d2cabc8a996926e05","private_key":"b6273af6a94c07db54566d0d2f93121d0ddf239921e8e46af19babd2fa9930a7","name":"node41","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"a93b2360b87425860185535aca8fbf3225e0c141d27690d60144689ae0f28534","private_key":"362244251a7f1bf4bc855ff3b272b1c7c7fe5d8338af0c581fee2f49e2939ed8","name":"node42","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1d531b5c8dd7bbbbf8c2db8f6984d2c9b23d5fee33876379e9d3e62a920e04b9","private_key":"bf6cd10025d018c0abfae2c88aa7c46c3d12d612580e6b4aacdc51fd52476270","name":"node43","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"78db863d7f60eda6a275cc9f4d1696cb32f5d46de747d278aa17ecd06c791135","private_key":"0659e3a41adc716b493c6ad765c5b26d35c95a1edb254efc8ab967e71e3e0a16","name":"node44","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"e22c1f74c4b480fdd2fbe84ad321a98230efb98fb1cdec4f79e4e50b2b0d5cc4","private_key":"f6d48b4c10f3257bba2f626d48192d94a1b1de3ab1480b618a0bff07eb20396e","name":"node45","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"aff775d47ebe35fbaaad26c627470abb8481558cb5a08c42a44597a98408a919","private_key":"4a487eb7a3b924414d988104fce6f87a70502db2d5d71dcbc1a115b37212cb06","name":"node46","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"5958a4ccd5333e8bfed5f18c78d365b201ffc2b9af3afd31748adb8e4f8118bc","private_key":"9e2309fa485ace73ce907035f5113e7b13e5719c819479b10fd9386a3ad5236f","name":"node47","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"fd54f34d1deead333014c472182bf5cb2db1d1b1ab55caa70f5dc70108622f17","private_key":"e511c730e803371042c631512a12d74b1c31a53caab237719b8fa007e4cef9ea","name":"node48","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"ac3887308a4c3591293c43f5d5f312874b30e0dc19faa57626fdc6b830d3e433","private_key":"bb4c6b3c0931311ef5e31087f74a1a95aa39da470737c4c1a2a730ea2cac1c76","name":"node49","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"b87c0ee719d0656cd325300b756f203310675021f3c508b30542deb1114ec7ac","private_key":"88c0abdb64c6dee8117b7b720d5782321ce583fcc76e6eed2ee1f6279a82ea39","name":"node50","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"6d307f348cc71e8a471ba662c1b5bd9d6e6930c22024f350465cccc7f2208a59","private_key":"e6eef1e846329e10e247843f7cee455af8ad3579e5a1e6360aea0ecc51982759","name":"node51","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","private_key":"ffd961376b67cbfafd47d89610291ec8fc2af2c16bc31e6851f804e15b2e9cd7","name":"node52","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"4610c06c8f5e1e24a75d9e46a432f4c1a6b9da6c3dbd122d3d3881752d930736","private_key":"db9ca337fd3ecf30fa6c217606072c214028b8d723ce82de57cfb4f0266a653d","name":"node53","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"ef40fdb37f2f89ec77833e48b1bd210b2241949b9ef8f6dca6bf6189503abb2c","private_key":"40e9de0543bd2c35509ebfcf51aa5a543d9616831505b5644e982144f4971f3a","name":"node54","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"bb9c9f07ee2f8bc2bb0f011309f8314de7c1777a6f683f90506b180ef6f5aab4","private_key":"f2806927e5ba924b002b05116a66bdd62d4eed7900e91f3e31892288bd06ebd1","name":"node55","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"960eb6f745966f47aadb13d488aee1187486c99e6bc78c679a6d9b3d421d8eb0","private_key":"f38a84e8d30f9c12d52071b696ff7fbd355dc875cbf937d2f491f4f3e193fc8e","name":"node56","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"160c0401bcd1b38db7b4ae82470244ac9a9923755fc475aa67eb6dedc703c223","private_key":"a3895eb5276ca39ba15c02895c3537a6c3a7be75de7b2ee2bee1fc5b9a313240","name":"node57","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"b77832f3f26323f09b37f31d208361d77ca08cec3ff829b9451fa3f00ff61fd0","private_key":"8135cdd3f1b3d517b1f4a11407dfcdf6a31b3dc087ddfe2224999f16ee7ca9de","name":"node58","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"351d1ed5c4012f8870ef2acadc7c0fea2cb697f5688b92e0150c5f670661c617","private_key":"29e270aecc8603f2224bee7f11039231b7a28efc5b29deeb9d98d0af388a87d0","name":"node59","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"fcf3ccf76c4699752670103e9cb98ac1294339a357876c71673f7e486d02f08b","private_key":"04d1ab0b03908f14773c60464c51526f925e192645efc3781a7117f22bdc4835","name":"node60","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"fe9d68b6f0d0add106d4a9e132308cf5caa867624784d652f071f5d1fe87ff8b","private_key":"f7320ebd494ac4fd8d6871123b7531dee97fba428ff30994f4d3ecc3f9312001","name":"node61","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","private_key":"b71a899f42faf2bdf9824d145f6f5959178f61f05e460e888c862ba8b03b5448","name":"node62","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"f9265e3c23cd931ad51faec142636a6a8c8bd90a23e77f5327861c81cf4dfe17","private_key":"43ea846524b82ef37cdaa1546b555e1a8d7510fc0cc7f11a6e040b79a5fcf054","name":"node63","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"d6a203c341b618c47ae53b339563b22c31dfc12d89dea7ba0c33e1812829684c","private_key":"0f863dee7eca46274fc2ec03645bf96424ffebbe6f5c26631051127cf730e223","name":"node64","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"0561b40886edc84497731c7b64acdabb63a22abe573847b78f74887afd86d71e","private_key":"a70d3a2696371a3cdee8702bbc4b008a564f36a8570b3bef778d00e5c4bc7da6","name":"node65","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1030a5d27a502aeb868e7158373c48af6a85e829ee3a84f4a0b78f9a30bd5a6a","private_key":"24cc61b4c4e59317c2927bd635bd3ad2863c0598321f0e5d60c3b534ed151558","name":"node66","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"d6e3d3ffd3858b8284bc9495b2d90b4b1124009a4a7d7394182977a8ae39df55","private_key":"36459a9e26fc4c00dd4c89bdf4c86c717b9701169ad7154228b8fbfff55661d9","name":"node67","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"43d797cb7fced78dd7141ef452ea367d7484d5c0fec2431caebe1317c4aa6340","private_key":"b947082437b645032dfff6e9d20e2eed52aace2d5e29cc268b06898cededdabd","name":"node68","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"a3e87b083776e34d10941832ec078fe9a7b8bae8cefe6fecdd5561549561a7f3","private_key":"5ac248334fa8c619d900ac284274784dc99fe0ae517e749c989a15bad1652ccf","name":"node69","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c1c13885e3f543804e62e3b34c0d9762778866e5b8fe9d9df0caa6a64ec41428","private_key":"7c46fa70253c48efad70d0b3da97e5c5680b1fb430147ac6f821729a836c667d","name":"node70","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1f1580f6bf3df2ce7a50ec53be2511d22149fede99ca6194b611471adabfafaa","private_key":"2d39f1bc0c0b3b7bdd1b9ef4fdfd54dd5b7db9743a16baa7c5f8b50948062e8d","name":"node71","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"88a9b808cabfadb31c1a01b5a09eb4dc6cb7e011348f095920dd12e330ba9c0a","private_key":"d4e45cb0946161c0f4333c4db19bdeabceb81b4db44982a776556e8da0bf3928","name":"node72","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"b26eff0d882918a40b909b2fc79489a671a9f990cc0dd3d835493bcb52a468ea","private_key":"df8fd4bcf5cb62281500f76bc0b09d7ac1576ffd0edadbb8d39301406ac8e0fd","name":"node73","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"893773caa8e126430f189186956f3e80b49107ce52777972f5e8c11027c072d6","private_key":"5ec7e6e237997309e30846fed2a2074e5a150ae82804f581cb4a69ea69fe0118","name":"node74","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"4e6eab6eb3d49b20e2f3dee169d4647ebf26d07044698ff93d51c5fc30fb4ae0","private_key":"06d5dc287feafe3797b6302002258d7ea058679dc501e7a05f64fabd41b1b701","name":"node75","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"13165fffc318dd5f182601384e1ac69dd591236a03e88dfd1c637ea21a01b2dd","private_key":"a06a77e7469d86991954524d4a1495b5aeb80bb413c0b1293479dcc8ce511108","name":"node76","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"39b5d09d1227a776f8d65b7239cd03e04499e7d519e5d1ff4c4870a1a949dd91","private_key":"9e6c3d21c05d371fd69225b2eef1d1eabedad577ae026b6d8ad8f728a53d657b","name":"node77","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"5258fdc94d78bb45d6634f2672bc2eacdbc896fc5a39784ecc9d8a7dd52cb1e7","private_key":"cd8b5f4a6c0d361bc118318b1f1c5e69ef7b546e5ded44742e97124cfb80c52e","name":"node78","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"24263ec4b92f390e07193d00e5a032ea2649f560027be02878da656e37d4a1b1","private_key":"04ed808eb12d991a68104f16e8965f3e6d60ba0b0dabff4fe33b3878c63d25f7","name":"node79","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","private_key":"175d97bb42b8f0effb21274a929a499f0e49e8e6ecad97b853a164464ad20bde","name":"node80","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1a16e158f1d35f2573d20b05ba51a2c9cc196547038e3b7be97ddc7598e81257","private_key":"34dbf4adc051f2ab18ea18c1faaec6726857cc5e0fcb3181fb296a723d2971c7","name":"node81","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"4f7abbc51265dfe22d58f3ac5cde2c05ec56053efe79e3ebb6ece96183dcc17a","private_key":"21c776bf36961c727b36ff521a7527764077944b7932dfb901ca6489b2e123e7","name":"node82","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"07c79aaa7fa759f797b88d8c495c5bfaa12f15289c6a567bbc363fd070227830","private_key":"db4ace065dad27967a83ad918dedd4b4d7b1aaa331057ca1a2033fcba3e16df6","name":"node83","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"e8d0bd04fa5b56fcd50cb4b1d550855c21192f1294f2954401348c8a28b455ab","private_key":"2642ed9d36375a48a74d6aee878a935a15e7bd219d39bbdf455b0a168c98a8b5","name":"node84","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"0066352177d6c225842362ab424a632f569980b02ac55a13a71593af94cb8c2d","private_key":"c1c1f7cd104f6f7163fe144041570269558b335ae6ddbdb80c79687faf55f5bb","name":"node85","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"d48642f8008e8aa791ba89c9b3a5b8d2d7aeb7e9078caee8813777679608075c","private_key":"89501ac0b58fa2ee82ba6ef2b45a3c0ab6d8f54f4b92da1111d97ecfedbf5fc3","name":"node86","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"944e8231f9f661435f1f94abfaa17862a005877487df5c5d3a566c4dbe46be3f","private_key":"cf649d632a25375b28cc6f7821de3e0df16b52ad9e0ff8978b231e20d6ed37ee","name":"node87","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"795d813cab67300b8c59c78b6b99403c8111920ca47c1e56edfb513bc4999f3f","private_key":"bceddc4ac81042ad71089e4c861518f8d018601263d1faa17238f1c326e4b317","name":"node88","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"766b61b773ad34c6cfd30b9c2e94840d151c31ab8cbcf546943eb5821d5a8f36","private_key":"a870aecb16e345ef241f69348d08489eb250b113f2072ab6371dda815d799f3f","name":"node89","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"958e075a7f22c092360d325dd0c0449e9442c398b2882009c5c3720cbcd3584f","private_key":"07af0af8e7e43f2822c2c0c3d34a1742faf6e11328b6194a760e9acefb5dedc8","name":"node90","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"0ef0bf53ddadd2c942f21e7e09d5a902d166f13920aabb2aa6652b70c423f575","private_key":"683b9cd98aab26ece4c2e53dd44a1fde3ae2303f80f99dc7e7e5d4b80e5a40e2","name":"node91","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"4067997c26042749c16f17169caac66e647b71e3557f7581dbec739d0baa2ef0","private_key":"31b3da34d338fb902b718378f7b5ebbdcdff30e4e3d3deff8b021e3979a7c6de","name":"node92","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1b6168d6a369029690e836d26ffe017e26769ce504c142abf2a4a65774ecb66d","private_key":"997bcdc19c47350a268aa991a33d767bb6fc29de16593e0b099e793aa1db638d","name":"node93","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"247dcb2c787abfa845b39cb9d7f9a6d59ea915006f250a4d760cdd8e16b3a5a6","private_key":"393e54787cdfec2d8d987f785700170fbcb31fd541c9c05199cd77d3a16a6dc4","name":"node94","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"9b240fbf12d9de7975803b09aacdbab94cd978130de62bc89497bb1056e462c0","private_key":"0cd4a911f2b1193b22efc0823fc2ed9beddafb7705f5597ce6d7335aadae0e1a","name":"node95","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"5f1c751641e1727871c44f0d5779b278871c77f5e05324dc91a8620bed72e627","private_key":"fb0590eb4eb2624363f0740cbc794f9adb8356ccbaf6650c8baca183edfde3b8","name":"node96","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"6ad608f1087b8af40359db45e7d78e43c3465fdae5cc01a9a9ab6c149cb6fdf3","private_key":"40d4caee240073f0bfc9307eed26d4286f944f467837b7250ee206f40d2880d4","name":"node97","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"ede2568d60ce7a12b4380818a31d2895fcb9b815cfb5526d28b2a82ff62e0381","private_key":"207c5a4e99506c7afdfff66611cf0baefe929f7c8a1a7a802cb44df3fa650618","name":"node98","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"30c022df9209b4515213fe3fa67135c05d960afbd76f8455caa9c83ab5e493b6","private_key":"fa964e311f099e564ffa3ff9820a9ad3a8723f738fce6da11be604636f275831","name":"node99","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"41cd903abdaf445bfbcc3d5f28c10aba473be8a3ddc614c7f124b0ed7f91fd1d","private_key":"23f2913103e5295ddfdc6485c2ea3c33bfda3e0ceea62cac5401ceabdda0668a","name":"node100","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"b270e0d21a173f4717882b1c6dc942d703fe11cd6adf997b3ecd0944fcea6c7b","private_key":"f3c37d7a8e80e1e71fba834055bf934536fd9e117f496b156d46bca96632ba5c","name":"node101","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"a6cad8da6a7949d7624b7ea636fd2d72de6a9b14f899df6263ccb4b3e5351c6a","private_key":"f4151729479b0ae76a7b853aa9d3460ee67adcedc364ac97248fb383478ba113","name":"node102","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"9835fa9db1ce53ad2a526d36050eefd2c593121df4036c269cd09e1be4746a15","private_key":"482aa546e8e665988c7329424342961c10084e439d562aed129d21a8c212d007","name":"node103","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"adfde69b7633188d7c5a44e51e856d023c946d4cd8fd16bf539e6d1764dd7e04","private_key":"2641708c3c6101db41db1a7eba5ed6b54e7ebc3014cb575ed71d291a6aacfc28","name":"node104","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"6c29a1bc0f1025207b4524fadd7c8cd3dd956816fdfc57bb525c4ad3bcca9169","private_key":"9b9c1c2253292c4de58f82fd6bba15922acfa246fa0717a869c0d651ce19e826","name":"node105","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"88dc5f31a2b295e8b43bc6e7abaae130061c2d44c49c95bcca3d9dd0a268324c","private_key":"7cc79c34ac4847aaba7f1e2de8d23910301dbfe606d052cce33ad0340a1f82fb","name":"node106","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"e884bd666b0215cfe88ee19457c67c747fb1c6815855160f1c243c149b24e923","private_key":"4047502d07951bf2380ef595036f9e99db3b0f7e1229040e21da5fbc49e7d820","name":"node107","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"8357eeaa65808f09b9a83362cb07508878becee9f843a5e1e70d025167b198ea","private_key":"02a6713184cf6e413a6ed6a6839150cad9c72d40951b265a754e56b5bdb74cbf","name":"node108","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"69baf7ee43b360a3238ad3b175e0ad9c24967dae60c7e91d1ca7ee55167b2bdb","private_key":"12d74d71de5166524deeed2ba475f9ad46c296668af272f0ade12162bed0f50f","name":"node109","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"0104c3fa3f6b9237565759debed3f1fcebfa75efba7f01e81f06a53274cdf619","private_key":"4fff513c0f905a42d6d18a90ae6a78c60757490480579162c9e0760361baf184","name":"node110","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"7a46bf03f327b100dc00140c97828a3b607280b6cd57220703be393ae4fdddbc","private_key":"5db638bd9bfaf4c2e1af1f3f1dc1e89382a6a2982f303fc80504b44aac1a6264","name":"node111","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"016e174a29dc18857107fbebc876689b401c1174964ac6cacb31a4cb847a4d30","private_key":"6caa9dcb10b84a658d4ef791909b6532395f0793f9f8bce99a3a1b985ce619b9","name":"node112","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"00b98e790fb954975837ff95abb1bdc2673dd8a0ce315986cc0dda5b0cefb05d","private_key":"63375740be7dc5d3a76a7b3249786a4c7382eaf8b648e5a39a7a850722bad29a","name":"node113","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"ce12d7b26736d08eaa424a5f972a5a1fd50d05aab330340f3605dbab8007bf28","private_key":"110a610c6c2f1720584929baf4ab9c8490923fc7b421bd251e444b752f8f8957","name":"node114","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","private_key":"7a8380aa7312fe4859408a51876e9f44b56151086e4bc36569a8f55bfb3a007b","name":"node115","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"089fe22a0bd48d3120cc8171eb9d698532c17f36768dbc25f554f2c53de11ab0","private_key":"1e498dce32dcdfdf4b6c691fa203e3809fddf1b19b1b1da0b1162b9037ecc303","name":"node116","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1a696462de3d83ff271f30f6d66de968325e37a36877dd67b45d9cdd7e644b5b","private_key":"03de0803048f078de61e3eef039a9ecb0e761216573392a6692630f3f291cc25","name":"node117","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","private_key":"934aa39349989614a1b0a71785880e61c60bb2579a9d52b832887849de94ec24","name":"node118","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","private_key":"011d6fce7eed10fc8c5a7a9ca21769efc6581023c2c857c28d97a6ebb1c43a53","name":"node119","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"58207fc8d92e44a8274398d6105b90d763981afba7021b57be98591e7b739272","private_key":"76d98f9c684d01fe8121cf715f01457e9fc38146a717958c8bb325a3b4ea44ce","name":"node120","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"212479d1ab813951780af2bc09a8119dbf62e603c05c0ea26329625036f6708a","private_key":"7aa614585809bea3b748e6df2e1a8da2b201a9ae84f11c819b5669234a10f76d","name":"node121","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"0d90040b9d0cd742149654262198aa8dc4fc96cfdf9b84cfe833277c3e23831f","private_key":"71857ed16ee507ae0dd576370348a196d43274a3895f26fb8659ec79c1ecb79c","name":"node122","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"41f407b92b1462b93400819a6af8815797b9dbc505afaf0162bbaea795205716","private_key":"3e5c543d406054ba1338ea28c37198eb8153a157eb5a0aecc186dceb04e10632","name":"node123","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"567206d70bc58993b08719afd9c007c562aad99437599356763b74cd4707083c","private_key":"3771d716bd74a4be3b8e154d3aa3b2302700b5ca1607923f7414c147a7cf67b7","name":"node124","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"9d60b67d731aee2e6ac86d17c9e6a6aa5aaeec6b6803bae5c5fbc02643f7358c","private_key":"25dc939bff90ac541a61b59e0d2b4d3b9891379de3893645f06891c5be0d5695","name":"node125","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"2742110997dcb690946885e5a4aaa039f72c9f92025e62f1d371922acf5e27a7","private_key":"e6ad803abcef9554bdff08a4f4b6a7a65dc574bf92d32ee882413c9269fd31f3","name":"node126","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"cb704e26ceff5b613d8211423375fe0ded4d867bc892894a70d71289f1a9c1c1","private_key":"a482a87960aae2e446cd2aeb304e7baeff9a24d2bace4d5f919b5bda00a5f0eb","name":"node127","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c6edfd613f216f48a1ea05be84faa5b9a69dd97c6e5d72e0bd339ea62c1b19e1","private_key":"214126811a121d6fc0443ce66e59372bc72dea9e220ab6e7d6da961741590d47","name":"node128","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}}],"conns":[{"one":"60cbf513366da3f6ee288d3572dc3971ae256b02e5492595e63c31a7b1b0d5a8","other":"869fc21fff150ce9e1bc4512b183559433767cfc1cb7b6baae6d2a1313245ae8","up":true},{"one":"c42f36bf6728b24721ce5ac1272a1058e202a236d276af1265a1eaa675f8fd85","other":"11b53fa40926ac84d75d9d698f46b9611b90194857c1397e117931a890447078","up":true},{"one":"0f19fb4d0c6cfe36f7f59c8fe54d2e169687603e1bde0fe6b3a5e864fa51f2f0","other":"7c992ceefe03187bce1edbf8f80ccd1cab80b3908e625a61fef21ec6d35d04a3","up":true},{"one":"66f6ff5fbc2526c1db5db491e49138362bef23032c6344ed4ead7dcb58bd1e6c","other":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","up":true},{"one":"9265383ecf776ce1da66e99e5f0a9c1af593d8e56ceef21f42aad4be7e34bc45","other":"fb211f405a7dc3a07b6d4f18522c6273ffa4e18325958eea159e33f768eae846","up":true},{"one":"81748a1a38162f9e8613ffa562add1b75893b66199840e74c0774db92bfd7ca0","other":"cd944ad4c50c4357005ff26ef26369ba3375a1205eec532b6e51b22bf25510bc","up":true},{"one":"f84317ef488d8c1495b429b7a1fa21ea53a73e737b3c1aa890c20e38658ab148","other":"0f19fb4d0c6cfe36f7f59c8fe54d2e169687603e1bde0fe6b3a5e864fa51f2f0","up":true},{"one":"6b1f3a9b65ce3a168fc98dee2b14b1de6912fd02ee2609b047b2c56f6c3b53e9","other":"1263f18bf8cb4f5a3335f545cdd5d42f1b84da9edf4fa7346572c2bc38c87eb3","up":true},{"one":"6b7da636e8eccac658c0abf6aa8ddd74be6d4cf9c5e0333bd28d9a6d1fe43ab3","other":"6421894e25e421d16eccd8858aed3354fce2d04c9d5c46d4005ee1f16944ccfd","up":true},{"one":"c6edfd613f216f48a1ea05be84faa5b9a69dd97c6e5d72e0bd339ea62c1b19e1","other":"c42f36bf6728b24721ce5ac1272a1058e202a236d276af1265a1eaa675f8fd85","up":true},{"one":"1263f18bf8cb4f5a3335f545cdd5d42f1b84da9edf4fa7346572c2bc38c87eb3","other":"580a650c2c3e95d35e66264df6f48e28a091bacd20dc2c9828a5b563353d2052","up":true},{"one":"6421894e25e421d16eccd8858aed3354fce2d04c9d5c46d4005ee1f16944ccfd","other":"051c8d430b54d843feb325c2b56e36524566d64f48f6c770739748409af44364","up":true},{"one":"fb211f405a7dc3a07b6d4f18522c6273ffa4e18325958eea159e33f768eae846","other":"7d14fd0b6c554f218249fed7709df51946baf91a3045ed99fb662c32cc97541b","up":true},{"one":"6c2fd129c8493cea7634a91aae559ceb64f01cebc45ec59aebd7e709b65941be","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":true},{"one":"459a945c8d6e67437bdfa0e067962b3591dfa0aa7c1177f8327ba1557bbf1569","other":"9a8c60732ff0d0b7403b181cdc5bb9e6db0aa4cf5c7158c40e548fcb76726f87","up":true},{"one":"7d14fd0b6c554f218249fed7709df51946baf91a3045ed99fb662c32cc97541b","other":"816609d5b9bdb0c7bad6cbe9400fd506df8662b14ad0e0a61bc7b49482548e39","up":true},{"one":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","other":"4a2d15b867a620653f099d66839b80c2dd32aec95caf906f0d5c8146192edf62","up":true},{"one":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","other":"74a4ffb0d717a523ec72e07cc5efe3e98d55932d6e0592ef45fe54b378e4e952","up":true},{"one":"816609d5b9bdb0c7bad6cbe9400fd506df8662b14ad0e0a61bc7b49482548e39","other":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","up":true},{"one":"f9e4dbb28099af8ca5f28c641a51672f4c6ab54b7037f765a7172a399acb8edc","other":"9265383ecf776ce1da66e99e5f0a9c1af593d8e56ceef21f42aad4be7e34bc45","up":true},{"one":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","other":"8ff601de9537c9e5d79a3707a312fe893383bef000d00acee3d4552068ef6030","up":true},{"one":"a20d591ea15722712e83fcc6c17732f2370034ef870234a0fa0b2f4aea572fd5","other":"18b0fcbba2350241088129e8f6a07742857c3edaacd6ef1ec0861cc8e48492ce","up":true},{"one":"8ff601de9537c9e5d79a3707a312fe893383bef000d00acee3d4552068ef6030","other":"5f8eb6ff0f6ed720eaae86390757833f4b9cdfa52564457ed90c3de260ab6c30","up":true},{"one":"5f8eb6ff0f6ed720eaae86390757833f4b9cdfa52564457ed90c3de260ab6c30","other":"85845413850f5e38cce69548e20179b477bf383dd2b22428358715d9d72421ac","up":true},{"one":"85845413850f5e38cce69548e20179b477bf383dd2b22428358715d9d72421ac","other":"57d3361ee59d006e1dba3e1ae9ea624444ee09f70410dff492d624d7e7786d2d","up":true},{"one":"57d3361ee59d006e1dba3e1ae9ea624444ee09f70410dff492d624d7e7786d2d","other":"776fdbabbca42ecb49cc5451b860305ec3020889b47adaa2add6ea359afe379f","up":true},{"one":"776fdbabbca42ecb49cc5451b860305ec3020889b47adaa2add6ea359afe379f","other":"22b84df73059c92bf59fdad51fe574e027c7cc555dddbf363d745c1c422a89fd","up":true},{"one":"22b84df73059c92bf59fdad51fe574e027c7cc555dddbf363d745c1c422a89fd","other":"2c3d7e056f87daf540821a31ea8fe9377d00ed3a55ffba67b8bb716cdd9b7303","up":true},{"one":"2c3d7e056f87daf540821a31ea8fe9377d00ed3a55ffba67b8bb716cdd9b7303","other":"746898d197934d17cd93b958ab20aaa85dba99057495048d2cabc8a996926e05","up":true},{"one":"746898d197934d17cd93b958ab20aaa85dba99057495048d2cabc8a996926e05","other":"a93b2360b87425860185535aca8fbf3225e0c141d27690d60144689ae0f28534","up":true},{"one":"a93b2360b87425860185535aca8fbf3225e0c141d27690d60144689ae0f28534","other":"1d531b5c8dd7bbbbf8c2db8f6984d2c9b23d5fee33876379e9d3e62a920e04b9","up":true},{"one":"1d531b5c8dd7bbbbf8c2db8f6984d2c9b23d5fee33876379e9d3e62a920e04b9","other":"78db863d7f60eda6a275cc9f4d1696cb32f5d46de747d278aa17ecd06c791135","up":true},{"one":"78db863d7f60eda6a275cc9f4d1696cb32f5d46de747d278aa17ecd06c791135","other":"e22c1f74c4b480fdd2fbe84ad321a98230efb98fb1cdec4f79e4e50b2b0d5cc4","up":true},{"one":"e22c1f74c4b480fdd2fbe84ad321a98230efb98fb1cdec4f79e4e50b2b0d5cc4","other":"aff775d47ebe35fbaaad26c627470abb8481558cb5a08c42a44597a98408a919","up":true},{"one":"aff775d47ebe35fbaaad26c627470abb8481558cb5a08c42a44597a98408a919","other":"5958a4ccd5333e8bfed5f18c78d365b201ffc2b9af3afd31748adb8e4f8118bc","up":true},{"one":"5958a4ccd5333e8bfed5f18c78d365b201ffc2b9af3afd31748adb8e4f8118bc","other":"fd54f34d1deead333014c472182bf5cb2db1d1b1ab55caa70f5dc70108622f17","up":true},{"one":"fd54f34d1deead333014c472182bf5cb2db1d1b1ab55caa70f5dc70108622f17","other":"ac3887308a4c3591293c43f5d5f312874b30e0dc19faa57626fdc6b830d3e433","up":true},{"one":"ac3887308a4c3591293c43f5d5f312874b30e0dc19faa57626fdc6b830d3e433","other":"b87c0ee719d0656cd325300b756f203310675021f3c508b30542deb1114ec7ac","up":true},{"one":"b87c0ee719d0656cd325300b756f203310675021f3c508b30542deb1114ec7ac","other":"6d307f348cc71e8a471ba662c1b5bd9d6e6930c22024f350465cccc7f2208a59","up":true},{"one":"6d307f348cc71e8a471ba662c1b5bd9d6e6930c22024f350465cccc7f2208a59","other":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","up":true},{"one":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","other":"4610c06c8f5e1e24a75d9e46a432f4c1a6b9da6c3dbd122d3d3881752d930736","up":true},{"one":"4610c06c8f5e1e24a75d9e46a432f4c1a6b9da6c3dbd122d3d3881752d930736","other":"ef40fdb37f2f89ec77833e48b1bd210b2241949b9ef8f6dca6bf6189503abb2c","up":true},{"one":"ef40fdb37f2f89ec77833e48b1bd210b2241949b9ef8f6dca6bf6189503abb2c","other":"bb9c9f07ee2f8bc2bb0f011309f8314de7c1777a6f683f90506b180ef6f5aab4","up":true},{"one":"bb9c9f07ee2f8bc2bb0f011309f8314de7c1777a6f683f90506b180ef6f5aab4","other":"960eb6f745966f47aadb13d488aee1187486c99e6bc78c679a6d9b3d421d8eb0","up":true},{"one":"960eb6f745966f47aadb13d488aee1187486c99e6bc78c679a6d9b3d421d8eb0","other":"160c0401bcd1b38db7b4ae82470244ac9a9923755fc475aa67eb6dedc703c223","up":true},{"one":"160c0401bcd1b38db7b4ae82470244ac9a9923755fc475aa67eb6dedc703c223","other":"b77832f3f26323f09b37f31d208361d77ca08cec3ff829b9451fa3f00ff61fd0","up":true},{"one":"b77832f3f26323f09b37f31d208361d77ca08cec3ff829b9451fa3f00ff61fd0","other":"351d1ed5c4012f8870ef2acadc7c0fea2cb697f5688b92e0150c5f670661c617","up":true},{"one":"351d1ed5c4012f8870ef2acadc7c0fea2cb697f5688b92e0150c5f670661c617","other":"fcf3ccf76c4699752670103e9cb98ac1294339a357876c71673f7e486d02f08b","up":true},{"one":"7c992ceefe03187bce1edbf8f80ccd1cab80b3908e625a61fef21ec6d35d04a3","other":"66f6ff5fbc2526c1db5db491e49138362bef23032c6344ed4ead7dcb58bd1e6c","up":true},{"one":"fcf3ccf76c4699752670103e9cb98ac1294339a357876c71673f7e486d02f08b","other":"fe9d68b6f0d0add106d4a9e132308cf5caa867624784d652f071f5d1fe87ff8b","up":true},{"one":"fe9d68b6f0d0add106d4a9e132308cf5caa867624784d652f071f5d1fe87ff8b","other":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","up":true},{"one":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","other":"f9265e3c23cd931ad51faec142636a6a8c8bd90a23e77f5327861c81cf4dfe17","up":true},{"one":"f9265e3c23cd931ad51faec142636a6a8c8bd90a23e77f5327861c81cf4dfe17","other":"d6a203c341b618c47ae53b339563b22c31dfc12d89dea7ba0c33e1812829684c","up":true},{"one":"0561b40886edc84497731c7b64acdabb63a22abe573847b78f74887afd86d71e","other":"1030a5d27a502aeb868e7158373c48af6a85e829ee3a84f4a0b78f9a30bd5a6a","up":true},{"one":"74a4ffb0d717a523ec72e07cc5efe3e98d55932d6e0592ef45fe54b378e4e952","other":"20c4c3a3523960ad74538e726e1caaeda987cbc16dcd3beeb63fca6eebd31405","up":true},{"one":"a3e87b083776e34d10941832ec078fe9a7b8bae8cefe6fecdd5561549561a7f3","other":"c1c13885e3f543804e62e3b34c0d9762778866e5b8fe9d9df0caa6a64ec41428","up":true},{"one":"c1c13885e3f543804e62e3b34c0d9762778866e5b8fe9d9df0caa6a64ec41428","other":"1f1580f6bf3df2ce7a50ec53be2511d22149fede99ca6194b611471adabfafaa","up":true},{"one":"1f1580f6bf3df2ce7a50ec53be2511d22149fede99ca6194b611471adabfafaa","other":"88a9b808cabfadb31c1a01b5a09eb4dc6cb7e011348f095920dd12e330ba9c0a","up":true},{"one":"88a9b808cabfadb31c1a01b5a09eb4dc6cb7e011348f095920dd12e330ba9c0a","other":"b26eff0d882918a40b909b2fc79489a671a9f990cc0dd3d835493bcb52a468ea","up":true},{"one":"b26eff0d882918a40b909b2fc79489a671a9f990cc0dd3d835493bcb52a468ea","other":"893773caa8e126430f189186956f3e80b49107ce52777972f5e8c11027c072d6","up":true},{"one":"893773caa8e126430f189186956f3e80b49107ce52777972f5e8c11027c072d6","other":"4e6eab6eb3d49b20e2f3dee169d4647ebf26d07044698ff93d51c5fc30fb4ae0","up":true},{"one":"4e6eab6eb3d49b20e2f3dee169d4647ebf26d07044698ff93d51c5fc30fb4ae0","other":"13165fffc318dd5f182601384e1ac69dd591236a03e88dfd1c637ea21a01b2dd","up":true},{"one":"13165fffc318dd5f182601384e1ac69dd591236a03e88dfd1c637ea21a01b2dd","other":"39b5d09d1227a776f8d65b7239cd03e04499e7d519e5d1ff4c4870a1a949dd91","up":true},{"one":"39b5d09d1227a776f8d65b7239cd03e04499e7d519e5d1ff4c4870a1a949dd91","other":"5258fdc94d78bb45d6634f2672bc2eacdbc896fc5a39784ecc9d8a7dd52cb1e7","up":true},{"one":"5258fdc94d78bb45d6634f2672bc2eacdbc896fc5a39784ecc9d8a7dd52cb1e7","other":"24263ec4b92f390e07193d00e5a032ea2649f560027be02878da656e37d4a1b1","up":true},{"one":"24263ec4b92f390e07193d00e5a032ea2649f560027be02878da656e37d4a1b1","other":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","up":true},{"one":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","other":"1a16e158f1d35f2573d20b05ba51a2c9cc196547038e3b7be97ddc7598e81257","up":true},{"one":"1a16e158f1d35f2573d20b05ba51a2c9cc196547038e3b7be97ddc7598e81257","other":"4f7abbc51265dfe22d58f3ac5cde2c05ec56053efe79e3ebb6ece96183dcc17a","up":true},{"one":"4f7abbc51265dfe22d58f3ac5cde2c05ec56053efe79e3ebb6ece96183dcc17a","other":"07c79aaa7fa759f797b88d8c495c5bfaa12f15289c6a567bbc363fd070227830","up":true},{"one":"07c79aaa7fa759f797b88d8c495c5bfaa12f15289c6a567bbc363fd070227830","other":"e8d0bd04fa5b56fcd50cb4b1d550855c21192f1294f2954401348c8a28b455ab","up":true},{"one":"e8d0bd04fa5b56fcd50cb4b1d550855c21192f1294f2954401348c8a28b455ab","other":"0066352177d6c225842362ab424a632f569980b02ac55a13a71593af94cb8c2d","up":true},{"one":"0066352177d6c225842362ab424a632f569980b02ac55a13a71593af94cb8c2d","other":"d48642f8008e8aa791ba89c9b3a5b8d2d7aeb7e9078caee8813777679608075c","up":true},{"one":"d48642f8008e8aa791ba89c9b3a5b8d2d7aeb7e9078caee8813777679608075c","other":"944e8231f9f661435f1f94abfaa17862a005877487df5c5d3a566c4dbe46be3f","up":true},{"one":"944e8231f9f661435f1f94abfaa17862a005877487df5c5d3a566c4dbe46be3f","other":"795d813cab67300b8c59c78b6b99403c8111920ca47c1e56edfb513bc4999f3f","up":true},{"one":"795d813cab67300b8c59c78b6b99403c8111920ca47c1e56edfb513bc4999f3f","other":"766b61b773ad34c6cfd30b9c2e94840d151c31ab8cbcf546943eb5821d5a8f36","up":true},{"one":"766b61b773ad34c6cfd30b9c2e94840d151c31ab8cbcf546943eb5821d5a8f36","other":"958e075a7f22c092360d325dd0c0449e9442c398b2882009c5c3720cbcd3584f","up":true},{"one":"958e075a7f22c092360d325dd0c0449e9442c398b2882009c5c3720cbcd3584f","other":"0ef0bf53ddadd2c942f21e7e09d5a902d166f13920aabb2aa6652b70c423f575","up":true},{"one":"0ef0bf53ddadd2c942f21e7e09d5a902d166f13920aabb2aa6652b70c423f575","other":"4067997c26042749c16f17169caac66e647b71e3557f7581dbec739d0baa2ef0","up":true},{"one":"4067997c26042749c16f17169caac66e647b71e3557f7581dbec739d0baa2ef0","other":"1b6168d6a369029690e836d26ffe017e26769ce504c142abf2a4a65774ecb66d","up":true},{"one":"1b6168d6a369029690e836d26ffe017e26769ce504c142abf2a4a65774ecb66d","other":"247dcb2c787abfa845b39cb9d7f9a6d59ea915006f250a4d760cdd8e16b3a5a6","up":true},{"one":"247dcb2c787abfa845b39cb9d7f9a6d59ea915006f250a4d760cdd8e16b3a5a6","other":"9b240fbf12d9de7975803b09aacdbab94cd978130de62bc89497bb1056e462c0","up":true},{"one":"9b240fbf12d9de7975803b09aacdbab94cd978130de62bc89497bb1056e462c0","other":"5f1c751641e1727871c44f0d5779b278871c77f5e05324dc91a8620bed72e627","up":true},{"one":"5f1c751641e1727871c44f0d5779b278871c77f5e05324dc91a8620bed72e627","other":"6ad608f1087b8af40359db45e7d78e43c3465fdae5cc01a9a9ab6c149cb6fdf3","up":true},{"one":"6ad608f1087b8af40359db45e7d78e43c3465fdae5cc01a9a9ab6c149cb6fdf3","other":"ede2568d60ce7a12b4380818a31d2895fcb9b815cfb5526d28b2a82ff62e0381","up":true},{"one":"ede2568d60ce7a12b4380818a31d2895fcb9b815cfb5526d28b2a82ff62e0381","other":"30c022df9209b4515213fe3fa67135c05d960afbd76f8455caa9c83ab5e493b6","up":true},{"one":"30c022df9209b4515213fe3fa67135c05d960afbd76f8455caa9c83ab5e493b6","other":"41cd903abdaf445bfbcc3d5f28c10aba473be8a3ddc614c7f124b0ed7f91fd1d","up":true},{"one":"41cd903abdaf445bfbcc3d5f28c10aba473be8a3ddc614c7f124b0ed7f91fd1d","other":"b270e0d21a173f4717882b1c6dc942d703fe11cd6adf997b3ecd0944fcea6c7b","up":true},{"one":"b270e0d21a173f4717882b1c6dc942d703fe11cd6adf997b3ecd0944fcea6c7b","other":"a6cad8da6a7949d7624b7ea636fd2d72de6a9b14f899df6263ccb4b3e5351c6a","up":true},{"one":"a6cad8da6a7949d7624b7ea636fd2d72de6a9b14f899df6263ccb4b3e5351c6a","other":"9835fa9db1ce53ad2a526d36050eefd2c593121df4036c269cd09e1be4746a15","up":true},{"one":"9835fa9db1ce53ad2a526d36050eefd2c593121df4036c269cd09e1be4746a15","other":"adfde69b7633188d7c5a44e51e856d023c946d4cd8fd16bf539e6d1764dd7e04","up":true},{"one":"adfde69b7633188d7c5a44e51e856d023c946d4cd8fd16bf539e6d1764dd7e04","other":"6c29a1bc0f1025207b4524fadd7c8cd3dd956816fdfc57bb525c4ad3bcca9169","up":true},{"one":"6c29a1bc0f1025207b4524fadd7c8cd3dd956816fdfc57bb525c4ad3bcca9169","other":"88dc5f31a2b295e8b43bc6e7abaae130061c2d44c49c95bcca3d9dd0a268324c","up":true},{"one":"88dc5f31a2b295e8b43bc6e7abaae130061c2d44c49c95bcca3d9dd0a268324c","other":"e884bd666b0215cfe88ee19457c67c747fb1c6815855160f1c243c149b24e923","up":true},{"one":"e884bd666b0215cfe88ee19457c67c747fb1c6815855160f1c243c149b24e923","other":"8357eeaa65808f09b9a83362cb07508878becee9f843a5e1e70d025167b198ea","up":true},{"one":"8357eeaa65808f09b9a83362cb07508878becee9f843a5e1e70d025167b198ea","other":"69baf7ee43b360a3238ad3b175e0ad9c24967dae60c7e91d1ca7ee55167b2bdb","up":true},{"one":"69baf7ee43b360a3238ad3b175e0ad9c24967dae60c7e91d1ca7ee55167b2bdb","other":"0104c3fa3f6b9237565759debed3f1fcebfa75efba7f01e81f06a53274cdf619","up":true},{"one":"0104c3fa3f6b9237565759debed3f1fcebfa75efba7f01e81f06a53274cdf619","other":"7a46bf03f327b100dc00140c97828a3b607280b6cd57220703be393ae4fdddbc","up":true},{"one":"7a46bf03f327b100dc00140c97828a3b607280b6cd57220703be393ae4fdddbc","other":"016e174a29dc18857107fbebc876689b401c1174964ac6cacb31a4cb847a4d30","up":true},{"one":"016e174a29dc18857107fbebc876689b401c1174964ac6cacb31a4cb847a4d30","other":"00b98e790fb954975837ff95abb1bdc2673dd8a0ce315986cc0dda5b0cefb05d","up":true},{"one":"00b98e790fb954975837ff95abb1bdc2673dd8a0ce315986cc0dda5b0cefb05d","other":"ce12d7b26736d08eaa424a5f972a5a1fd50d05aab330340f3605dbab8007bf28","up":true},{"one":"ce12d7b26736d08eaa424a5f972a5a1fd50d05aab330340f3605dbab8007bf28","other":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","up":true},{"one":"580a650c2c3e95d35e66264df6f48e28a091bacd20dc2c9828a5b563353d2052","other":"6c2fd129c8493cea7634a91aae559ceb64f01cebc45ec59aebd7e709b65941be","up":true},{"one":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","other":"089fe22a0bd48d3120cc8171eb9d698532c17f36768dbc25f554f2c53de11ab0","up":true},{"one":"089fe22a0bd48d3120cc8171eb9d698532c17f36768dbc25f554f2c53de11ab0","other":"1a696462de3d83ff271f30f6d66de968325e37a36877dd67b45d9cdd7e644b5b","up":true},{"one":"1a696462de3d83ff271f30f6d66de968325e37a36877dd67b45d9cdd7e644b5b","other":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","up":true},{"one":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","other":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","up":true},{"one":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","other":"58207fc8d92e44a8274398d6105b90d763981afba7021b57be98591e7b739272","up":true},{"one":"58207fc8d92e44a8274398d6105b90d763981afba7021b57be98591e7b739272","other":"212479d1ab813951780af2bc09a8119dbf62e603c05c0ea26329625036f6708a","up":true},{"one":"212479d1ab813951780af2bc09a8119dbf62e603c05c0ea26329625036f6708a","other":"0d90040b9d0cd742149654262198aa8dc4fc96cfdf9b84cfe833277c3e23831f","up":true},{"one":"1030a5d27a502aeb868e7158373c48af6a85e829ee3a84f4a0b78f9a30bd5a6a","other":"d6e3d3ffd3858b8284bc9495b2d90b4b1124009a4a7d7394182977a8ae39df55","up":true},{"one":"0d90040b9d0cd742149654262198aa8dc4fc96cfdf9b84cfe833277c3e23831f","other":"41f407b92b1462b93400819a6af8815797b9dbc505afaf0162bbaea795205716","up":true},{"one":"41f407b92b1462b93400819a6af8815797b9dbc505afaf0162bbaea795205716","other":"567206d70bc58993b08719afd9c007c562aad99437599356763b74cd4707083c","up":true},{"one":"567206d70bc58993b08719afd9c007c562aad99437599356763b74cd4707083c","other":"9d60b67d731aee2e6ac86d17c9e6a6aa5aaeec6b6803bae5c5fbc02643f7358c","up":true},{"one":"18b0fcbba2350241088129e8f6a07742857c3edaacd6ef1ec0861cc8e48492ce","other":"f84317ef488d8c1495b429b7a1fa21ea53a73e737b3c1aa890c20e38658ab148","up":true},{"one":"20c4c3a3523960ad74538e726e1caaeda987cbc16dcd3beeb63fca6eebd31405","other":"45cdaac4c087e6b737507fa29936d09f2fbea14f49ef0ce91c18080456a7459b","up":true},{"one":"d6e3d3ffd3858b8284bc9495b2d90b4b1124009a4a7d7394182977a8ae39df55","other":"43d797cb7fced78dd7141ef452ea367d7484d5c0fec2431caebe1317c4aa6340","up":true},{"one":"43d797cb7fced78dd7141ef452ea367d7484d5c0fec2431caebe1317c4aa6340","other":"a3e87b083776e34d10941832ec078fe9a7b8bae8cefe6fecdd5561549561a7f3","up":true},{"one":"11b53fa40926ac84d75d9d698f46b9611b90194857c1397e117931a890447078","other":"60cbf513366da3f6ee288d3572dc3971ae256b02e5492595e63c31a7b1b0d5a8","up":true},{"one":"869fc21fff150ce9e1bc4512b183559433767cfc1cb7b6baae6d2a1313245ae8","other":"a20d591ea15722712e83fcc6c17732f2370034ef870234a0fa0b2f4aea572fd5","up":true},{"one":"9a8c60732ff0d0b7403b181cdc5bb9e6db0aa4cf5c7158c40e548fcb76726f87","other":"6b1f3a9b65ce3a168fc98dee2b14b1de6912fd02ee2609b047b2c56f6c3b53e9","up":true},{"one":"45cdaac4c087e6b737507fa29936d09f2fbea14f49ef0ce91c18080456a7459b","other":"81748a1a38162f9e8613ffa562add1b75893b66199840e74c0774db92bfd7ca0","up":true},{"one":"2742110997dcb690946885e5a4aaa039f72c9f92025e62f1d371922acf5e27a7","other":"cb704e26ceff5b613d8211423375fe0ded4d867bc892894a70d71289f1a9c1c1","up":true},{"one":"9d60b67d731aee2e6ac86d17c9e6a6aa5aaeec6b6803bae5c5fbc02643f7358c","other":"2742110997dcb690946885e5a4aaa039f72c9f92025e62f1d371922acf5e27a7","up":true},{"one":"4a2d15b867a620653f099d66839b80c2dd32aec95caf906f0d5c8146192edf62","other":"f9e4dbb28099af8ca5f28c641a51672f4c6ab54b7037f765a7172a399acb8edc","up":true},{"one":"cd944ad4c50c4357005ff26ef26369ba3375a1205eec532b6e51b22bf25510bc","other":"6b7da636e8eccac658c0abf6aa8ddd74be6d4cf9c5e0333bd28d9a6d1fe43ab3","up":true},{"one":"051c8d430b54d843feb325c2b56e36524566d64f48f6c770739748409af44364","other":"459a945c8d6e67437bdfa0e067962b3591dfa0aa7c1177f8327ba1557bbf1569","up":true},{"one":"d6a203c341b618c47ae53b339563b22c31dfc12d89dea7ba0c33e1812829684c","other":"0561b40886edc84497731c7b64acdabb63a22abe573847b78f74887afd86d71e","up":true},{"one":"cb704e26ceff5b613d8211423375fe0ded4d867bc892894a70d71289f1a9c1c1","other":"c6edfd613f216f48a1ea05be84faa5b9a69dd97c6e5d72e0bd339ea62c1b19e1","up":true},{"one":"869fc21fff150ce9e1bc4512b183559433767cfc1cb7b6baae6d2a1313245ae8","other":"c6edfd613f216f48a1ea05be84faa5b9a69dd97c6e5d72e0bd339ea62c1b19e1","up":true},{"one":"a20d591ea15722712e83fcc6c17732f2370034ef870234a0fa0b2f4aea572fd5","other":"c6edfd613f216f48a1ea05be84faa5b9a69dd97c6e5d72e0bd339ea62c1b19e1","up":true},{"one":"c42f36bf6728b24721ce5ac1272a1058e202a236d276af1265a1eaa675f8fd85","other":"a20d591ea15722712e83fcc6c17732f2370034ef870234a0fa0b2f4aea572fd5","up":true},{"one":"6b7da636e8eccac658c0abf6aa8ddd74be6d4cf9c5e0333bd28d9a6d1fe43ab3","other":"459a945c8d6e67437bdfa0e067962b3591dfa0aa7c1177f8327ba1557bbf1569","up":true},{"one":"fb211f405a7dc3a07b6d4f18522c6273ffa4e18325958eea159e33f768eae846","other":"4a2d15b867a620653f099d66839b80c2dd32aec95caf906f0d5c8146192edf62","up":true},{"one":"20c4c3a3523960ad74538e726e1caaeda987cbc16dcd3beeb63fca6eebd31405","other":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","up":true},{"one":"580a650c2c3e95d35e66264df6f48e28a091bacd20dc2c9828a5b563353d2052","other":"6b1f3a9b65ce3a168fc98dee2b14b1de6912fd02ee2609b047b2c56f6c3b53e9","up":true},{"one":"2c3d7e056f87daf540821a31ea8fe9377d00ed3a55ffba67b8bb716cdd9b7303","other":"57d3361ee59d006e1dba3e1ae9ea624444ee09f70410dff492d624d7e7786d2d","up":true},{"one":"45cdaac4c087e6b737507fa29936d09f2fbea14f49ef0ce91c18080456a7459b","other":"6b7da636e8eccac658c0abf6aa8ddd74be6d4cf9c5e0333bd28d9a6d1fe43ab3","up":true},{"one":"a93b2360b87425860185535aca8fbf3225e0c141d27690d60144689ae0f28534","other":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","up":true},{"one":"1d531b5c8dd7bbbbf8c2db8f6984d2c9b23d5fee33876379e9d3e62a920e04b9","other":"746898d197934d17cd93b958ab20aaa85dba99057495048d2cabc8a996926e05","up":true},{"one":"051c8d430b54d843feb325c2b56e36524566d64f48f6c770739748409af44364","other":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","up":true},{"one":"459a945c8d6e67437bdfa0e067962b3591dfa0aa7c1177f8327ba1557bbf1569","other":"6b1f3a9b65ce3a168fc98dee2b14b1de6912fd02ee2609b047b2c56f6c3b53e9","up":true},{"one":"ac3887308a4c3591293c43f5d5f312874b30e0dc19faa57626fdc6b830d3e433","other":"a93b2360b87425860185535aca8fbf3225e0c141d27690d60144689ae0f28534","up":true},{"one":"bb9c9f07ee2f8bc2bb0f011309f8314de7c1777a6f683f90506b180ef6f5aab4","other":"a93b2360b87425860185535aca8fbf3225e0c141d27690d60144689ae0f28534","up":true},{"one":"6c2fd129c8493cea7634a91aae559ceb64f01cebc45ec59aebd7e709b65941be","other":"6b1f3a9b65ce3a168fc98dee2b14b1de6912fd02ee2609b047b2c56f6c3b53e9","up":true},{"one":"aff775d47ebe35fbaaad26c627470abb8481558cb5a08c42a44597a98408a919","other":"a93b2360b87425860185535aca8fbf3225e0c141d27690d60144689ae0f28534","up":true},{"one":"4610c06c8f5e1e24a75d9e46a432f4c1a6b9da6c3dbd122d3d3881752d930736","other":"6d307f348cc71e8a471ba662c1b5bd9d6e6930c22024f350465cccc7f2208a59","up":true},{"one":"ef40fdb37f2f89ec77833e48b1bd210b2241949b9ef8f6dca6bf6189503abb2c","other":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","up":true},{"one":"5958a4ccd5333e8bfed5f18c78d365b201ffc2b9af3afd31748adb8e4f8118bc","other":"57d3361ee59d006e1dba3e1ae9ea624444ee09f70410dff492d624d7e7786d2d","up":true},{"one":"cd944ad4c50c4357005ff26ef26369ba3375a1205eec532b6e51b22bf25510bc","other":"f9e4dbb28099af8ca5f28c641a51672f4c6ab54b7037f765a7172a399acb8edc","up":true},{"one":"66f6ff5fbc2526c1db5db491e49138362bef23032c6344ed4ead7dcb58bd1e6c","other":"6b7da636e8eccac658c0abf6aa8ddd74be6d4cf9c5e0333bd28d9a6d1fe43ab3","up":true},{"one":"13165fffc318dd5f182601384e1ac69dd591236a03e88dfd1c637ea21a01b2dd","other":"24263ec4b92f390e07193d00e5a032ea2649f560027be02878da656e37d4a1b1","up":true},{"one":"d6a203c341b618c47ae53b339563b22c31dfc12d89dea7ba0c33e1812829684c","other":"fe9d68b6f0d0add106d4a9e132308cf5caa867624784d652f071f5d1fe87ff8b","up":true},{"one":"4a2d15b867a620653f099d66839b80c2dd32aec95caf906f0d5c8146192edf62","other":"580a650c2c3e95d35e66264df6f48e28a091bacd20dc2c9828a5b563353d2052","up":true},{"one":"b26eff0d882918a40b909b2fc79489a671a9f990cc0dd3d835493bcb52a468ea","other":"5258fdc94d78bb45d6634f2672bc2eacdbc896fc5a39784ecc9d8a7dd52cb1e7","up":true},{"one":"0561b40886edc84497731c7b64acdabb63a22abe573847b78f74887afd86d71e","other":"1f1580f6bf3df2ce7a50ec53be2511d22149fede99ca6194b611471adabfafaa","up":true},{"one":"43d797cb7fced78dd7141ef452ea367d7484d5c0fec2431caebe1317c4aa6340","other":"5258fdc94d78bb45d6634f2672bc2eacdbc896fc5a39784ecc9d8a7dd52cb1e7","up":true},{"one":"07c79aaa7fa759f797b88d8c495c5bfaa12f15289c6a567bbc363fd070227830","other":"24263ec4b92f390e07193d00e5a032ea2649f560027be02878da656e37d4a1b1","up":true},{"one":"247dcb2c787abfa845b39cb9d7f9a6d59ea915006f250a4d760cdd8e16b3a5a6","other":"016e174a29dc18857107fbebc876689b401c1174964ac6cacb31a4cb847a4d30","up":true},{"one":"e8d0bd04fa5b56fcd50cb4b1d550855c21192f1294f2954401348c8a28b455ab","other":"958e075a7f22c092360d325dd0c0449e9442c398b2882009c5c3720cbcd3584f","up":true},{"one":"d48642f8008e8aa791ba89c9b3a5b8d2d7aeb7e9078caee8813777679608075c","other":"e8d0bd04fa5b56fcd50cb4b1d550855c21192f1294f2954401348c8a28b455ab","up":true},{"one":"944e8231f9f661435f1f94abfaa17862a005877487df5c5d3a566c4dbe46be3f","other":"958e075a7f22c092360d325dd0c0449e9442c398b2882009c5c3720cbcd3584f","up":true},{"one":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","other":"089fe22a0bd48d3120cc8171eb9d698532c17f36768dbc25f554f2c53de11ab0","up":true},{"one":"958e075a7f22c092360d325dd0c0449e9442c398b2882009c5c3720cbcd3584f","other":"9b240fbf12d9de7975803b09aacdbab94cd978130de62bc89497bb1056e462c0","up":true},{"one":"30c022df9209b4515213fe3fa67135c05d960afbd76f8455caa9c83ab5e493b6","other":"247dcb2c787abfa845b39cb9d7f9a6d59ea915006f250a4d760cdd8e16b3a5a6","up":true},{"one":"c6edfd613f216f48a1ea05be84faa5b9a69dd97c6e5d72e0bd339ea62c1b19e1","other":"0d90040b9d0cd742149654262198aa8dc4fc96cfdf9b84cfe833277c3e23831f","up":true},{"one":"18b0fcbba2350241088129e8f6a07742857c3edaacd6ef1ec0861cc8e48492ce","other":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","up":true},{"one":"1a696462de3d83ff271f30f6d66de968325e37a36877dd67b45d9cdd7e644b5b","other":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","up":true},{"one":"567206d70bc58993b08719afd9c007c562aad99437599356763b74cd4707083c","other":"58207fc8d92e44a8274398d6105b90d763981afba7021b57be98591e7b739272","up":true},{"one":"9d60b67d731aee2e6ac86d17c9e6a6aa5aaeec6b6803bae5c5fbc02643f7358c","other":"c6edfd613f216f48a1ea05be84faa5b9a69dd97c6e5d72e0bd339ea62c1b19e1","up":true},{"one":"88dc5f31a2b295e8b43bc6e7abaae130061c2d44c49c95bcca3d9dd0a268324c","other":"944e8231f9f661435f1f94abfaa17862a005877487df5c5d3a566c4dbe46be3f","up":true},{"one":"0066352177d6c225842362ab424a632f569980b02ac55a13a71593af94cb8c2d","other":"07c79aaa7fa759f797b88d8c495c5bfaa12f15289c6a567bbc363fd070227830","up":true},{"one":"7a46bf03f327b100dc00140c97828a3b607280b6cd57220703be393ae4fdddbc","other":"74a4ffb0d717a523ec72e07cc5efe3e98d55932d6e0592ef45fe54b378e4e952","up":true},{"one":"2742110997dcb690946885e5a4aaa039f72c9f92025e62f1d371922acf5e27a7","other":"212479d1ab813951780af2bc09a8119dbf62e603c05c0ea26329625036f6708a","up":true},{"one":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","other":"45cdaac4c087e6b737507fa29936d09f2fbea14f49ef0ce91c18080456a7459b","up":true},{"one":"fd54f34d1deead333014c472182bf5cb2db1d1b1ab55caa70f5dc70108622f17","other":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","up":true},{"one":"8357eeaa65808f09b9a83362cb07508878becee9f843a5e1e70d025167b198ea","other":"88dc5f31a2b295e8b43bc6e7abaae130061c2d44c49c95bcca3d9dd0a268324c","up":true},{"one":"089fe22a0bd48d3120cc8171eb9d698532c17f36768dbc25f554f2c53de11ab0","other":"212479d1ab813951780af2bc09a8119dbf62e603c05c0ea26329625036f6708a","up":true},{"one":"85845413850f5e38cce69548e20179b477bf383dd2b22428358715d9d72421ac","other":"816609d5b9bdb0c7bad6cbe9400fd506df8662b14ad0e0a61bc7b49482548e39","up":true},{"one":"b87c0ee719d0656cd325300b756f203310675021f3c508b30542deb1114ec7ac","other":"bb9c9f07ee2f8bc2bb0f011309f8314de7c1777a6f683f90506b180ef6f5aab4","up":true},{"one":"4067997c26042749c16f17169caac66e647b71e3557f7581dbec739d0baa2ef0","other":"5f1c751641e1727871c44f0d5779b278871c77f5e05324dc91a8620bed72e627","up":true},{"one":"fcf3ccf76c4699752670103e9cb98ac1294339a357876c71673f7e486d02f08b","other":"f9265e3c23cd931ad51faec142636a6a8c8bd90a23e77f5327861c81cf4dfe17","up":true},{"one":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","other":"5f8eb6ff0f6ed720eaae86390757833f4b9cdfa52564457ed90c3de260ab6c30","up":true},{"one":"a6cad8da6a7949d7624b7ea636fd2d72de6a9b14f899df6263ccb4b3e5351c6a","other":"958e075a7f22c092360d325dd0c0449e9442c398b2882009c5c3720cbcd3584f","up":true},{"one":"88a9b808cabfadb31c1a01b5a09eb4dc6cb7e011348f095920dd12e330ba9c0a","other":"893773caa8e126430f189186956f3e80b49107ce52777972f5e8c11027c072d6","up":true},{"one":"f84317ef488d8c1495b429b7a1fa21ea53a73e737b3c1aa890c20e38658ab148","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":true},{"one":"746898d197934d17cd93b958ab20aaa85dba99057495048d2cabc8a996926e05","other":"57d3361ee59d006e1dba3e1ae9ea624444ee09f70410dff492d624d7e7786d2d","up":true},{"one":"816609d5b9bdb0c7bad6cbe9400fd506df8662b14ad0e0a61bc7b49482548e39","other":"9a8c60732ff0d0b7403b181cdc5bb9e6db0aa4cf5c7158c40e548fcb76726f87","up":true},{"one":"7c992ceefe03187bce1edbf8f80ccd1cab80b3908e625a61fef21ec6d35d04a3","other":"7d14fd0b6c554f218249fed7709df51946baf91a3045ed99fb662c32cc97541b","up":true},{"one":"5f8eb6ff0f6ed720eaae86390757833f4b9cdfa52564457ed90c3de260ab6c30","other":"580a650c2c3e95d35e66264df6f48e28a091bacd20dc2c9828a5b563353d2052","up":true},{"one":"6421894e25e421d16eccd8858aed3354fce2d04c9d5c46d4005ee1f16944ccfd","other":"66f6ff5fbc2526c1db5db491e49138362bef23032c6344ed4ead7dcb58bd1e6c","up":true},{"one":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","other":"30c022df9209b4515213fe3fa67135c05d960afbd76f8455caa9c83ab5e493b6","up":true},{"one":"6b1f3a9b65ce3a168fc98dee2b14b1de6912fd02ee2609b047b2c56f6c3b53e9","other":"6b7da636e8eccac658c0abf6aa8ddd74be6d4cf9c5e0333bd28d9a6d1fe43ab3","up":true},{"one":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","other":"795d813cab67300b8c59c78b6b99403c8111920ca47c1e56edfb513bc4999f3f","up":true},{"one":"fe9d68b6f0d0add106d4a9e132308cf5caa867624784d652f071f5d1fe87ff8b","other":"fd54f34d1deead333014c472182bf5cb2db1d1b1ab55caa70f5dc70108622f17","up":true},{"one":"e884bd666b0215cfe88ee19457c67c747fb1c6815855160f1c243c149b24e923","other":"e8d0bd04fa5b56fcd50cb4b1d550855c21192f1294f2954401348c8a28b455ab","up":true},{"one":"e22c1f74c4b480fdd2fbe84ad321a98230efb98fb1cdec4f79e4e50b2b0d5cc4","other":"fd54f34d1deead333014c472182bf5cb2db1d1b1ab55caa70f5dc70108622f17","up":true},{"one":"b270e0d21a173f4717882b1c6dc942d703fe11cd6adf997b3ecd0944fcea6c7b","other":"958e075a7f22c092360d325dd0c0449e9442c398b2882009c5c3720cbcd3584f","up":true},{"one":"869fc21fff150ce9e1bc4512b183559433767cfc1cb7b6baae6d2a1313245ae8","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":true},{"one":"c42f36bf6728b24721ce5ac1272a1058e202a236d276af1265a1eaa675f8fd85","other":"cb704e26ceff5b613d8211423375fe0ded4d867bc892894a70d71289f1a9c1c1","up":true},{"one":"580a650c2c3e95d35e66264df6f48e28a091bacd20dc2c9828a5b563353d2052","other":"57d3361ee59d006e1dba3e1ae9ea624444ee09f70410dff492d624d7e7786d2d","up":true},{"one":"f84317ef488d8c1495b429b7a1fa21ea53a73e737b3c1aa890c20e38658ab148","other":"c6edfd613f216f48a1ea05be84faa5b9a69dd97c6e5d72e0bd339ea62c1b19e1","up":true},{"one":"74a4ffb0d717a523ec72e07cc5efe3e98d55932d6e0592ef45fe54b378e4e952","other":"7c992ceefe03187bce1edbf8f80ccd1cab80b3908e625a61fef21ec6d35d04a3","up":true},{"one":"4e6eab6eb3d49b20e2f3dee169d4647ebf26d07044698ff93d51c5fc30fb4ae0","other":"5258fdc94d78bb45d6634f2672bc2eacdbc896fc5a39784ecc9d8a7dd52cb1e7","up":true},{"one":"a20d591ea15722712e83fcc6c17732f2370034ef870234a0fa0b2f4aea572fd5","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":true},{"one":"20c4c3a3523960ad74538e726e1caaeda987cbc16dcd3beeb63fca6eebd31405","other":"212479d1ab813951780af2bc09a8119dbf62e603c05c0ea26329625036f6708a","up":true},{"one":"9a8c60732ff0d0b7403b181cdc5bb9e6db0aa4cf5c7158c40e548fcb76726f87","other":"a93b2360b87425860185535aca8fbf3225e0c141d27690d60144689ae0f28534","up":true},{"one":"459a945c8d6e67437bdfa0e067962b3591dfa0aa7c1177f8327ba1557bbf1569","other":"45cdaac4c087e6b737507fa29936d09f2fbea14f49ef0ce91c18080456a7459b","up":true},{"one":"7c992ceefe03187bce1edbf8f80ccd1cab80b3908e625a61fef21ec6d35d04a3","other":"7a46bf03f327b100dc00140c97828a3b607280b6cd57220703be393ae4fdddbc","up":true},{"one":"6c2fd129c8493cea7634a91aae559ceb64f01cebc45ec59aebd7e709b65941be","other":"6b7da636e8eccac658c0abf6aa8ddd74be6d4cf9c5e0333bd28d9a6d1fe43ab3","up":true},{"one":"6b1f3a9b65ce3a168fc98dee2b14b1de6912fd02ee2609b047b2c56f6c3b53e9","other":"66f6ff5fbc2526c1db5db491e49138362bef23032c6344ed4ead7dcb58bd1e6c","up":true},{"one":"18b0fcbba2350241088129e8f6a07742857c3edaacd6ef1ec0861cc8e48492ce","other":"0f19fb4d0c6cfe36f7f59c8fe54d2e169687603e1bde0fe6b3a5e864fa51f2f0","up":true},{"one":"45cdaac4c087e6b737507fa29936d09f2fbea14f49ef0ce91c18080456a7459b","other":"4a2d15b867a620653f099d66839b80c2dd32aec95caf906f0d5c8146192edf62","up":true},{"one":"4a2d15b867a620653f099d66839b80c2dd32aec95caf906f0d5c8146192edf62","other":"459a945c8d6e67437bdfa0e067962b3591dfa0aa7c1177f8327ba1557bbf1569","up":true},{"one":"051c8d430b54d843feb325c2b56e36524566d64f48f6c770739748409af44364","other":"18b0fcbba2350241088129e8f6a07742857c3edaacd6ef1ec0861cc8e48492ce","up":true},{"one":"66f6ff5fbc2526c1db5db491e49138362bef23032c6344ed4ead7dcb58bd1e6c","other":"6c2fd129c8493cea7634a91aae559ceb64f01cebc45ec59aebd7e709b65941be","up":true},{"one":"cd944ad4c50c4357005ff26ef26369ba3375a1205eec532b6e51b22bf25510bc","other":"fb211f405a7dc3a07b6d4f18522c6273ffa4e18325958eea159e33f768eae846","up":true},{"one":"22b84df73059c92bf59fdad51fe574e027c7cc555dddbf363d745c1c422a89fd","other":"1d531b5c8dd7bbbbf8c2db8f6984d2c9b23d5fee33876379e9d3e62a920e04b9","up":true},{"one":"6b7da636e8eccac658c0abf6aa8ddd74be6d4cf9c5e0333bd28d9a6d1fe43ab3","other":"20c4c3a3523960ad74538e726e1caaeda987cbc16dcd3beeb63fca6eebd31405","up":true},{"one":"5958a4ccd5333e8bfed5f18c78d365b201ffc2b9af3afd31748adb8e4f8118bc","other":"5f8eb6ff0f6ed720eaae86390757833f4b9cdfa52564457ed90c3de260ab6c30","up":true},{"one":"6421894e25e421d16eccd8858aed3354fce2d04c9d5c46d4005ee1f16944ccfd","other":"6b1f3a9b65ce3a168fc98dee2b14b1de6912fd02ee2609b047b2c56f6c3b53e9","up":true},{"one":"fb211f405a7dc3a07b6d4f18522c6273ffa4e18325958eea159e33f768eae846","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":true},{"one":"22b84df73059c92bf59fdad51fe574e027c7cc555dddbf363d745c1c422a89fd","other":"051c8d430b54d843feb325c2b56e36524566d64f48f6c770739748409af44364","up":true},{"one":"1d531b5c8dd7bbbbf8c2db8f6984d2c9b23d5fee33876379e9d3e62a920e04b9","other":"0561b40886edc84497731c7b64acdabb63a22abe573847b78f74887afd86d71e","up":true},{"one":"e22c1f74c4b480fdd2fbe84ad321a98230efb98fb1cdec4f79e4e50b2b0d5cc4","other":"d6a203c341b618c47ae53b339563b22c31dfc12d89dea7ba0c33e1812829684c","up":true},{"one":"aff775d47ebe35fbaaad26c627470abb8481558cb5a08c42a44597a98408a919","other":"ac3887308a4c3591293c43f5d5f312874b30e0dc19faa57626fdc6b830d3e433","up":true},{"one":"0561b40886edc84497731c7b64acdabb63a22abe573847b78f74887afd86d71e","other":"0066352177d6c225842362ab424a632f569980b02ac55a13a71593af94cb8c2d","up":true},{"one":"fd54f34d1deead333014c472182bf5cb2db1d1b1ab55caa70f5dc70108622f17","other":"d6a203c341b618c47ae53b339563b22c31dfc12d89dea7ba0c33e1812829684c","up":true},{"one":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","other":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","up":true},{"one":"ac3887308a4c3591293c43f5d5f312874b30e0dc19faa57626fdc6b830d3e433","other":"bb9c9f07ee2f8bc2bb0f011309f8314de7c1777a6f683f90506b180ef6f5aab4","up":true},{"one":"2c3d7e056f87daf540821a31ea8fe9377d00ed3a55ffba67b8bb716cdd9b7303","other":"1d531b5c8dd7bbbbf8c2db8f6984d2c9b23d5fee33876379e9d3e62a920e04b9","up":true},{"one":"816609d5b9bdb0c7bad6cbe9400fd506df8662b14ad0e0a61bc7b49482548e39","other":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","up":true},{"one":"b87c0ee719d0656cd325300b756f203310675021f3c508b30542deb1114ec7ac","other":"aff775d47ebe35fbaaad26c627470abb8481558cb5a08c42a44597a98408a919","up":true},{"one":"4610c06c8f5e1e24a75d9e46a432f4c1a6b9da6c3dbd122d3d3881752d930736","other":"57d3361ee59d006e1dba3e1ae9ea624444ee09f70410dff492d624d7e7786d2d","up":true},{"one":"ef40fdb37f2f89ec77833e48b1bd210b2241949b9ef8f6dca6bf6189503abb2c","other":"f9265e3c23cd931ad51faec142636a6a8c8bd90a23e77f5327861c81cf4dfe17","up":true},{"one":"5258fdc94d78bb45d6634f2672bc2eacdbc896fc5a39784ecc9d8a7dd52cb1e7","other":"4067997c26042749c16f17169caac66e647b71e3557f7581dbec739d0baa2ef0","up":true},{"one":"746898d197934d17cd93b958ab20aaa85dba99057495048d2cabc8a996926e05","other":"776fdbabbca42ecb49cc5451b860305ec3020889b47adaa2add6ea359afe379f","up":true},{"one":"bb9c9f07ee2f8bc2bb0f011309f8314de7c1777a6f683f90506b180ef6f5aab4","other":"aff775d47ebe35fbaaad26c627470abb8481558cb5a08c42a44597a98408a919","up":true},{"one":"1263f18bf8cb4f5a3335f545cdd5d42f1b84da9edf4fa7346572c2bc38c87eb3","other":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","up":true},{"one":"0f19fb4d0c6cfe36f7f59c8fe54d2e169687603e1bde0fe6b3a5e864fa51f2f0","other":"051c8d430b54d843feb325c2b56e36524566d64f48f6c770739748409af44364","up":true},{"one":"5f8eb6ff0f6ed720eaae86390757833f4b9cdfa52564457ed90c3de260ab6c30","other":"57d3361ee59d006e1dba3e1ae9ea624444ee09f70410dff492d624d7e7786d2d","up":true},{"one":"a93b2360b87425860185535aca8fbf3225e0c141d27690d60144689ae0f28534","other":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","up":true},{"one":"fcf3ccf76c4699752670103e9cb98ac1294339a357876c71673f7e486d02f08b","other":"fd54f34d1deead333014c472182bf5cb2db1d1b1ab55caa70f5dc70108622f17","up":true},{"one":"fe9d68b6f0d0add106d4a9e132308cf5caa867624784d652f071f5d1fe87ff8b","other":"f9265e3c23cd931ad51faec142636a6a8c8bd90a23e77f5327861c81cf4dfe17","up":true},{"one":"d6a203c341b618c47ae53b339563b22c31dfc12d89dea7ba0c33e1812829684c","other":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","up":true},{"one":"85845413850f5e38cce69548e20179b477bf383dd2b22428358715d9d72421ac","other":"9a8c60732ff0d0b7403b181cdc5bb9e6db0aa4cf5c7158c40e548fcb76726f87","up":true},{"one":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","other":"43d797cb7fced78dd7141ef452ea367d7484d5c0fec2431caebe1317c4aa6340","up":true},{"one":"07c79aaa7fa759f797b88d8c495c5bfaa12f15289c6a567bbc363fd070227830","other":"00b98e790fb954975837ff95abb1bdc2673dd8a0ce315986cc0dda5b0cefb05d","up":true},{"one":"43d797cb7fced78dd7141ef452ea367d7484d5c0fec2431caebe1317c4aa6340","other":"6d307f348cc71e8a471ba662c1b5bd9d6e6930c22024f350465cccc7f2208a59","up":true},{"one":"4067997c26042749c16f17169caac66e647b71e3557f7581dbec739d0baa2ef0","other":"766b61b773ad34c6cfd30b9c2e94840d151c31ab8cbcf546943eb5821d5a8f36","up":true},{"one":"e8d0bd04fa5b56fcd50cb4b1d550855c21192f1294f2954401348c8a28b455ab","other":"d6a203c341b618c47ae53b339563b22c31dfc12d89dea7ba0c33e1812829684c","up":true},{"one":"7a46bf03f327b100dc00140c97828a3b607280b6cd57220703be393ae4fdddbc","other":"69baf7ee43b360a3238ad3b175e0ad9c24967dae60c7e91d1ca7ee55167b2bdb","up":true},{"one":"b77832f3f26323f09b37f31d208361d77ca08cec3ff829b9451fa3f00ff61fd0","other":"bb9c9f07ee2f8bc2bb0f011309f8314de7c1777a6f683f90506b180ef6f5aab4","up":true},{"one":"8357eeaa65808f09b9a83362cb07508878becee9f843a5e1e70d025167b198ea","other":"893773caa8e126430f189186956f3e80b49107ce52777972f5e8c11027c072d6","up":true},{"one":"88a9b808cabfadb31c1a01b5a09eb4dc6cb7e011348f095920dd12e330ba9c0a","other":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","up":true},{"one":"247dcb2c787abfa845b39cb9d7f9a6d59ea915006f250a4d760cdd8e16b3a5a6","other":"24263ec4b92f390e07193d00e5a032ea2649f560027be02878da656e37d4a1b1","up":true},{"one":"b26eff0d882918a40b909b2fc79489a671a9f990cc0dd3d835493bcb52a468ea","other":"b77832f3f26323f09b37f31d208361d77ca08cec3ff829b9451fa3f00ff61fd0","up":true},{"one":"958e075a7f22c092360d325dd0c0449e9442c398b2882009c5c3720cbcd3584f","other":"b26eff0d882918a40b909b2fc79489a671a9f990cc0dd3d835493bcb52a468ea","up":true},{"one":"0066352177d6c225842362ab424a632f569980b02ac55a13a71593af94cb8c2d","other":"016e174a29dc18857107fbebc876689b401c1174964ac6cacb31a4cb847a4d30","up":true},{"one":"d48642f8008e8aa791ba89c9b3a5b8d2d7aeb7e9078caee8813777679608075c","other":"d6a203c341b618c47ae53b339563b22c31dfc12d89dea7ba0c33e1812829684c","up":true},{"one":"944e8231f9f661435f1f94abfaa17862a005877487df5c5d3a566c4dbe46be3f","other":"9b240fbf12d9de7975803b09aacdbab94cd978130de62bc89497bb1056e462c0","up":true},{"one":"30c022df9209b4515213fe3fa67135c05d960afbd76f8455caa9c83ab5e493b6","other":"24263ec4b92f390e07193d00e5a032ea2649f560027be02878da656e37d4a1b1","up":true},{"one":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","other":"fcf3ccf76c4699752670103e9cb98ac1294339a357876c71673f7e486d02f08b","up":true},{"one":"b270e0d21a173f4717882b1c6dc942d703fe11cd6adf997b3ecd0944fcea6c7b","other":"944e8231f9f661435f1f94abfaa17862a005877487df5c5d3a566c4dbe46be3f","up":true},{"one":"c6edfd613f216f48a1ea05be84faa5b9a69dd97c6e5d72e0bd339ea62c1b19e1","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":true},{"one":"4e6eab6eb3d49b20e2f3dee169d4647ebf26d07044698ff93d51c5fc30fb4ae0","other":"43d797cb7fced78dd7141ef452ea367d7484d5c0fec2431caebe1317c4aa6340","up":true},{"one":"5f1c751641e1727871c44f0d5779b278871c77f5e05324dc91a8620bed72e627","other":"6c29a1bc0f1025207b4524fadd7c8cd3dd956816fdfc57bb525c4ad3bcca9169","up":true},{"one":"a6cad8da6a7949d7624b7ea636fd2d72de6a9b14f899df6263ccb4b3e5351c6a","other":"adfde69b7633188d7c5a44e51e856d023c946d4cd8fd16bf539e6d1764dd7e04","up":true},{"one":"13165fffc318dd5f182601384e1ac69dd591236a03e88dfd1c637ea21a01b2dd","other":"1a16e158f1d35f2573d20b05ba51a2c9cc196547038e3b7be97ddc7598e81257","up":true},{"one":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","other":"212479d1ab813951780af2bc09a8119dbf62e603c05c0ea26329625036f6708a","up":true},{"one":"5258fdc94d78bb45d6634f2672bc2eacdbc896fc5a39784ecc9d8a7dd52cb1e7","other":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","up":true},{"one":"e884bd666b0215cfe88ee19457c67c747fb1c6815855160f1c243c149b24e923","other":"ede2568d60ce7a12b4380818a31d2895fcb9b815cfb5526d28b2a82ff62e0381","up":true},{"one":"88dc5f31a2b295e8b43bc6e7abaae130061c2d44c49c95bcca3d9dd0a268324c","other":"893773caa8e126430f189186956f3e80b49107ce52777972f5e8c11027c072d6","up":true},{"one":"7d14fd0b6c554f218249fed7709df51946baf91a3045ed99fb662c32cc97541b","other":"7a46bf03f327b100dc00140c97828a3b607280b6cd57220703be393ae4fdddbc","up":true},{"one":"089fe22a0bd48d3120cc8171eb9d698532c17f36768dbc25f554f2c53de11ab0","other":"0d90040b9d0cd742149654262198aa8dc4fc96cfdf9b84cfe833277c3e23831f","up":true},{"one":"1a696462de3d83ff271f30f6d66de968325e37a36877dd67b45d9cdd7e644b5b","other":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","up":true},{"one":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","other":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","up":true},{"one":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":true},{"one":"567206d70bc58993b08719afd9c007c562aad99437599356763b74cd4707083c","other":"57d3361ee59d006e1dba3e1ae9ea624444ee09f70410dff492d624d7e7786d2d","up":true},{"one":"9d60b67d731aee2e6ac86d17c9e6a6aa5aaeec6b6803bae5c5fbc02643f7358c","other":"869fc21fff150ce9e1bc4512b183559433767cfc1cb7b6baae6d2a1313245ae8","up":true},{"one":"9835fa9db1ce53ad2a526d36050eefd2c593121df4036c269cd09e1be4746a15","other":"944e8231f9f661435f1f94abfaa17862a005877487df5c5d3a566c4dbe46be3f","up":true},{"one":"78db863d7f60eda6a275cc9f4d1696cb32f5d46de747d278aa17ecd06c791135","other":"746898d197934d17cd93b958ab20aaa85dba99057495048d2cabc8a996926e05","up":true},{"one":"1f1580f6bf3df2ce7a50ec53be2511d22149fede99ca6194b611471adabfafaa","other":"351d1ed5c4012f8870ef2acadc7c0fea2cb697f5688b92e0150c5f670661c617","up":true},{"one":"2742110997dcb690946885e5a4aaa039f72c9f92025e62f1d371922acf5e27a7","other":"20c4c3a3523960ad74538e726e1caaeda987cbc16dcd3beeb63fca6eebd31405","up":true},{"one":"cb704e26ceff5b613d8211423375fe0ded4d867bc892894a70d71289f1a9c1c1","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":true},{"one":"1b6168d6a369029690e836d26ffe017e26769ce504c142abf2a4a65774ecb66d","other":"1a16e158f1d35f2573d20b05ba51a2c9cc196547038e3b7be97ddc7598e81257","up":true},{"one":"d6e3d3ffd3858b8284bc9495b2d90b4b1124009a4a7d7394182977a8ae39df55","other":"d6a203c341b618c47ae53b339563b22c31dfc12d89dea7ba0c33e1812829684c","up":true},{"one":"6ad608f1087b8af40359db45e7d78e43c3465fdae5cc01a9a9ab6c149cb6fdf3","other":"6c29a1bc0f1025207b4524fadd7c8cd3dd956816fdfc57bb525c4ad3bcca9169","up":true},{"one":"016e174a29dc18857107fbebc876689b401c1174964ac6cacb31a4cb847a4d30","other":"0104c3fa3f6b9237565759debed3f1fcebfa75efba7f01e81f06a53274cdf619","up":true},{"one":"39b5d09d1227a776f8d65b7239cd03e04499e7d519e5d1ff4c4870a1a949dd91","other":"30c022df9209b4515213fe3fa67135c05d960afbd76f8455caa9c83ab5e493b6","up":true},{"one":"f9265e3c23cd931ad51faec142636a6a8c8bd90a23e77f5327861c81cf4dfe17","other":"fd54f34d1deead333014c472182bf5cb2db1d1b1ab55caa70f5dc70108622f17","up":true},{"one":"776fdbabbca42ecb49cc5451b860305ec3020889b47adaa2add6ea359afe379f","other":"6b7da636e8eccac658c0abf6aa8ddd74be6d4cf9c5e0333bd28d9a6d1fe43ab3","up":true},{"one":"69baf7ee43b360a3238ad3b175e0ad9c24967dae60c7e91d1ca7ee55167b2bdb","other":"6c2fd129c8493cea7634a91aae559ceb64f01cebc45ec59aebd7e709b65941be","up":true},{"one":"ce12d7b26736d08eaa424a5f972a5a1fd50d05aab330340f3605dbab8007bf28","other":"c6edfd613f216f48a1ea05be84faa5b9a69dd97c6e5d72e0bd339ea62c1b19e1","up":true},{"one":"00b98e790fb954975837ff95abb1bdc2673dd8a0ce315986cc0dda5b0cefb05d","other":"0066352177d6c225842362ab424a632f569980b02ac55a13a71593af94cb8c2d","up":true},{"one":"1a16e158f1d35f2573d20b05ba51a2c9cc196547038e3b7be97ddc7598e81257","other":"07c79aaa7fa759f797b88d8c495c5bfaa12f15289c6a567bbc363fd070227830","up":true},{"one":"41cd903abdaf445bfbcc3d5f28c10aba473be8a3ddc614c7f124b0ed7f91fd1d","other":"4067997c26042749c16f17169caac66e647b71e3557f7581dbec739d0baa2ef0","up":true},{"one":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","other":"0104c3fa3f6b9237565759debed3f1fcebfa75efba7f01e81f06a53274cdf619","up":true},{"one":"795d813cab67300b8c59c78b6b99403c8111920ca47c1e56edfb513bc4999f3f","other":"6ad608f1087b8af40359db45e7d78e43c3465fdae5cc01a9a9ab6c149cb6fdf3","up":true},{"one":"0104c3fa3f6b9237565759debed3f1fcebfa75efba7f01e81f06a53274cdf619","other":"0066352177d6c225842362ab424a632f569980b02ac55a13a71593af94cb8c2d","up":true},{"one":"81748a1a38162f9e8613ffa562add1b75893b66199840e74c0774db92bfd7ca0","other":"85845413850f5e38cce69548e20179b477bf383dd2b22428358715d9d72421ac","up":true},{"one":"869fc21fff150ce9e1bc4512b183559433767cfc1cb7b6baae6d2a1313245ae8","other":"fb211f405a7dc3a07b6d4f18522c6273ffa4e18325958eea159e33f768eae846","up":true},{"one":"a20d591ea15722712e83fcc6c17732f2370034ef870234a0fa0b2f4aea572fd5","other":"bb9c9f07ee2f8bc2bb0f011309f8314de7c1777a6f683f90506b180ef6f5aab4","up":true},{"one":"351d1ed5c4012f8870ef2acadc7c0fea2cb697f5688b92e0150c5f670661c617","other":"2c3d7e056f87daf540821a31ea8fe9377d00ed3a55ffba67b8bb716cdd9b7303","up":true},{"one":"6b1f3a9b65ce3a168fc98dee2b14b1de6912fd02ee2609b047b2c56f6c3b53e9","other":"7a46bf03f327b100dc00140c97828a3b607280b6cd57220703be393ae4fdddbc","up":true},{"one":"4a2d15b867a620653f099d66839b80c2dd32aec95caf906f0d5c8146192edf62","other":"2c3d7e056f87daf540821a31ea8fe9377d00ed3a55ffba67b8bb716cdd9b7303","up":true},{"one":"c42f36bf6728b24721ce5ac1272a1058e202a236d276af1265a1eaa675f8fd85","other":"cd944ad4c50c4357005ff26ef26369ba3375a1205eec532b6e51b22bf25510bc","up":true},{"one":"18b0fcbba2350241088129e8f6a07742857c3edaacd6ef1ec0861cc8e48492ce","other":"1a696462de3d83ff271f30f6d66de968325e37a36877dd67b45d9cdd7e644b5b","up":true},{"one":"1263f18bf8cb4f5a3335f545cdd5d42f1b84da9edf4fa7346572c2bc38c87eb3","other":"18b0fcbba2350241088129e8f6a07742857c3edaacd6ef1ec0861cc8e48492ce","up":true},{"one":"f84317ef488d8c1495b429b7a1fa21ea53a73e737b3c1aa890c20e38658ab148","other":"c42f36bf6728b24721ce5ac1272a1058e202a236d276af1265a1eaa675f8fd85","up":true},{"one":"960eb6f745966f47aadb13d488aee1187486c99e6bc78c679a6d9b3d421d8eb0","other":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","up":true},{"one":"6c29a1bc0f1025207b4524fadd7c8cd3dd956816fdfc57bb525c4ad3bcca9169","other":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","up":true},{"one":"893773caa8e126430f189186956f3e80b49107ce52777972f5e8c11027c072d6","other":"fd54f34d1deead333014c472182bf5cb2db1d1b1ab55caa70f5dc70108622f17","up":true},{"one":"0f19fb4d0c6cfe36f7f59c8fe54d2e169687603e1bde0fe6b3a5e864fa51f2f0","other":"0d90040b9d0cd742149654262198aa8dc4fc96cfdf9b84cfe833277c3e23831f","up":true},{"one":"776fdbabbca42ecb49cc5451b860305ec3020889b47adaa2add6ea359afe379f","other":"6b1f3a9b65ce3a168fc98dee2b14b1de6912fd02ee2609b047b2c56f6c3b53e9","up":true},{"one":"580a650c2c3e95d35e66264df6f48e28a091bacd20dc2c9828a5b563353d2052","other":"5958a4ccd5333e8bfed5f18c78d365b201ffc2b9af3afd31748adb8e4f8118bc","up":true},{"one":"746898d197934d17cd93b958ab20aaa85dba99057495048d2cabc8a996926e05","other":"6b7da636e8eccac658c0abf6aa8ddd74be6d4cf9c5e0333bd28d9a6d1fe43ab3","up":true},{"one":"2c3d7e056f87daf540821a31ea8fe9377d00ed3a55ffba67b8bb716cdd9b7303","other":"20c4c3a3523960ad74538e726e1caaeda987cbc16dcd3beeb63fca6eebd31405","up":true},{"one":"6c2fd129c8493cea7634a91aae559ceb64f01cebc45ec59aebd7e709b65941be","other":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","up":true},{"one":"7c992ceefe03187bce1edbf8f80ccd1cab80b3908e625a61fef21ec6d35d04a3","other":"869fc21fff150ce9e1bc4512b183559433767cfc1cb7b6baae6d2a1313245ae8","up":true},{"one":"66f6ff5fbc2526c1db5db491e49138362bef23032c6344ed4ead7dcb58bd1e6c","other":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","up":true},{"one":"fcf3ccf76c4699752670103e9cb98ac1294339a357876c71673f7e486d02f08b","other":"88a9b808cabfadb31c1a01b5a09eb4dc6cb7e011348f095920dd12e330ba9c0a","up":true},{"one":"fb211f405a7dc3a07b6d4f18522c6273ffa4e18325958eea159e33f768eae846","other":"f9e4dbb28099af8ca5f28c641a51672f4c6ab54b7037f765a7172a399acb8edc","up":true},{"one":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","other":"1d531b5c8dd7bbbbf8c2db8f6984d2c9b23d5fee33876379e9d3e62a920e04b9","up":true},{"one":"22b84df73059c92bf59fdad51fe574e027c7cc555dddbf363d745c1c422a89fd","other":"20c4c3a3523960ad74538e726e1caaeda987cbc16dcd3beeb63fca6eebd31405","up":true},{"one":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","other":"fb211f405a7dc3a07b6d4f18522c6273ffa4e18325958eea159e33f768eae846","up":true},{"one":"1d531b5c8dd7bbbbf8c2db8f6984d2c9b23d5fee33876379e9d3e62a920e04b9","other":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","up":true},{"one":"7d14fd0b6c554f218249fed7709df51946baf91a3045ed99fb662c32cc97541b","other":"2c3d7e056f87daf540821a31ea8fe9377d00ed3a55ffba67b8bb716cdd9b7303","up":true},{"one":"5f8eb6ff0f6ed720eaae86390757833f4b9cdfa52564457ed90c3de260ab6c30","other":"6421894e25e421d16eccd8858aed3354fce2d04c9d5c46d4005ee1f16944ccfd","up":true},{"one":"d6e3d3ffd3858b8284bc9495b2d90b4b1124009a4a7d7394182977a8ae39df55","other":"d48642f8008e8aa791ba89c9b3a5b8d2d7aeb7e9078caee8813777679608075c","up":true},{"one":"74a4ffb0d717a523ec72e07cc5efe3e98d55932d6e0592ef45fe54b378e4e952","other":"7d14fd0b6c554f218249fed7709df51946baf91a3045ed99fb662c32cc97541b","up":true},{"one":"78db863d7f60eda6a275cc9f4d1696cb32f5d46de747d278aa17ecd06c791135","other":"776fdbabbca42ecb49cc5451b860305ec3020889b47adaa2add6ea359afe379f","up":true},{"one":"816609d5b9bdb0c7bad6cbe9400fd506df8662b14ad0e0a61bc7b49482548e39","other":"893773caa8e126430f189186956f3e80b49107ce52777972f5e8c11027c072d6","up":true},{"one":"fe9d68b6f0d0add106d4a9e132308cf5caa867624784d652f071f5d1fe87ff8b","other":"6d307f348cc71e8a471ba662c1b5bd9d6e6930c22024f350465cccc7f2208a59","up":true},{"one":"c1c13885e3f543804e62e3b34c0d9762778866e5b8fe9d9df0caa6a64ec41428","other":"d6e3d3ffd3858b8284bc9495b2d90b4b1124009a4a7d7394182977a8ae39df55","up":true},{"one":"20c4c3a3523960ad74538e726e1caaeda987cbc16dcd3beeb63fca6eebd31405","other":"247dcb2c787abfa845b39cb9d7f9a6d59ea915006f250a4d760cdd8e16b3a5a6","up":true},{"one":"e22c1f74c4b480fdd2fbe84ad321a98230efb98fb1cdec4f79e4e50b2b0d5cc4","other":"e8d0bd04fa5b56fcd50cb4b1d550855c21192f1294f2954401348c8a28b455ab","up":true},{"one":"85845413850f5e38cce69548e20179b477bf383dd2b22428358715d9d72421ac","other":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","up":true},{"one":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","other":"d48642f8008e8aa791ba89c9b3a5b8d2d7aeb7e9078caee8813777679608075c","up":true},{"one":"45cdaac4c087e6b737507fa29936d09f2fbea14f49ef0ce91c18080456a7459b","other":"580a650c2c3e95d35e66264df6f48e28a091bacd20dc2c9828a5b563353d2052","up":true},{"one":"aff775d47ebe35fbaaad26c627470abb8481558cb5a08c42a44597a98408a919","other":"b26eff0d882918a40b909b2fc79489a671a9f990cc0dd3d835493bcb52a468ea","up":true},{"one":"f9265e3c23cd931ad51faec142636a6a8c8bd90a23e77f5327861c81cf4dfe17","other":"5258fdc94d78bb45d6634f2672bc2eacdbc896fc5a39784ecc9d8a7dd52cb1e7","up":true},{"one":"cd944ad4c50c4357005ff26ef26369ba3375a1205eec532b6e51b22bf25510bc","other":"f84317ef488d8c1495b429b7a1fa21ea53a73e737b3c1aa890c20e38658ab148","up":true},{"one":"5958a4ccd5333e8bfed5f18c78d365b201ffc2b9af3afd31748adb8e4f8118bc","other":"567206d70bc58993b08719afd9c007c562aad99437599356763b74cd4707083c","up":true},{"one":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","other":"9a8c60732ff0d0b7403b181cdc5bb9e6db0aa4cf5c7158c40e548fcb76726f87","up":true},{"one":"d6a203c341b618c47ae53b339563b22c31dfc12d89dea7ba0c33e1812829684c","other":"893773caa8e126430f189186956f3e80b49107ce52777972f5e8c11027c072d6","up":true},{"one":"ac3887308a4c3591293c43f5d5f312874b30e0dc19faa57626fdc6b830d3e433","other":"0561b40886edc84497731c7b64acdabb63a22abe573847b78f74887afd86d71e","up":true},{"one":"43d797cb7fced78dd7141ef452ea367d7484d5c0fec2431caebe1317c4aa6340","other":"4067997c26042749c16f17169caac66e647b71e3557f7581dbec739d0baa2ef0","up":true},{"one":"351d1ed5c4012f8870ef2acadc7c0fea2cb697f5688b92e0150c5f670661c617","other":"0561b40886edc84497731c7b64acdabb63a22abe573847b78f74887afd86d71e","up":true},{"one":"6b7da636e8eccac658c0abf6aa8ddd74be6d4cf9c5e0333bd28d9a6d1fe43ab3","other":"69baf7ee43b360a3238ad3b175e0ad9c24967dae60c7e91d1ca7ee55167b2bdb","up":true},{"one":"0561b40886edc84497731c7b64acdabb63a22abe573847b78f74887afd86d71e","other":"00b98e790fb954975837ff95abb1bdc2673dd8a0ce315986cc0dda5b0cefb05d","up":true},{"one":"c1c13885e3f543804e62e3b34c0d9762778866e5b8fe9d9df0caa6a64ec41428","other":"d6a203c341b618c47ae53b339563b22c31dfc12d89dea7ba0c33e1812829684c","up":true},{"one":"b87c0ee719d0656cd325300b756f203310675021f3c508b30542deb1114ec7ac","other":"b26eff0d882918a40b909b2fc79489a671a9f990cc0dd3d835493bcb52a468ea","up":true},{"one":"1f1580f6bf3df2ce7a50ec53be2511d22149fede99ca6194b611471adabfafaa","other":"1d531b5c8dd7bbbbf8c2db8f6984d2c9b23d5fee33876379e9d3e62a920e04b9","up":true},{"one":"944e8231f9f661435f1f94abfaa17862a005877487df5c5d3a566c4dbe46be3f","other":"8357eeaa65808f09b9a83362cb07508878becee9f843a5e1e70d025167b198ea","up":true},{"one":"81748a1a38162f9e8613ffa562add1b75893b66199840e74c0774db92bfd7ca0","other":"869fc21fff150ce9e1bc4512b183559433767cfc1cb7b6baae6d2a1313245ae8","up":true},{"one":"4610c06c8f5e1e24a75d9e46a432f4c1a6b9da6c3dbd122d3d3881752d930736","other":"459a945c8d6e67437bdfa0e067962b3591dfa0aa7c1177f8327ba1557bbf1569","up":true},{"one":"88a9b808cabfadb31c1a01b5a09eb4dc6cb7e011348f095920dd12e330ba9c0a","other":"88dc5f31a2b295e8b43bc6e7abaae130061c2d44c49c95bcca3d9dd0a268324c","up":true},{"one":"ef40fdb37f2f89ec77833e48b1bd210b2241949b9ef8f6dca6bf6189503abb2c","other":"e8d0bd04fa5b56fcd50cb4b1d550855c21192f1294f2954401348c8a28b455ab","up":true},{"one":"b26eff0d882918a40b909b2fc79489a671a9f990cc0dd3d835493bcb52a468ea","other":"bb9c9f07ee2f8bc2bb0f011309f8314de7c1777a6f683f90506b180ef6f5aab4","up":true},{"one":"fd54f34d1deead333014c472182bf5cb2db1d1b1ab55caa70f5dc70108622f17","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":true},{"one":"6421894e25e421d16eccd8858aed3354fce2d04c9d5c46d4005ee1f16944ccfd","other":"6c2fd129c8493cea7634a91aae559ceb64f01cebc45ec59aebd7e709b65941be","up":true},{"one":"8357eeaa65808f09b9a83362cb07508878becee9f843a5e1e70d025167b198ea","other":"88a9b808cabfadb31c1a01b5a09eb4dc6cb7e011348f095920dd12e330ba9c0a","up":true},{"one":"795d813cab67300b8c59c78b6b99403c8111920ca47c1e56edfb513bc4999f3f","other":"6c29a1bc0f1025207b4524fadd7c8cd3dd956816fdfc57bb525c4ad3bcca9169","up":true},{"one":"5258fdc94d78bb45d6634f2672bc2eacdbc896fc5a39784ecc9d8a7dd52cb1e7","other":"795d813cab67300b8c59c78b6b99403c8111920ca47c1e56edfb513bc4999f3f","up":true},{"one":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","other":"e22c1f74c4b480fdd2fbe84ad321a98230efb98fb1cdec4f79e4e50b2b0d5cc4","up":true},{"one":"1a16e158f1d35f2573d20b05ba51a2c9cc196547038e3b7be97ddc7598e81257","other":"adfde69b7633188d7c5a44e51e856d023c946d4cd8fd16bf539e6d1764dd7e04","up":true},{"one":"051c8d430b54d843feb325c2b56e36524566d64f48f6c770739748409af44364","other":"0561b40886edc84497731c7b64acdabb63a22abe573847b78f74887afd86d71e","up":true},{"one":"958e075a7f22c092360d325dd0c0449e9442c398b2882009c5c3720cbcd3584f","other":"8357eeaa65808f09b9a83362cb07508878becee9f843a5e1e70d025167b198ea","up":true},{"one":"69baf7ee43b360a3238ad3b175e0ad9c24967dae60c7e91d1ca7ee55167b2bdb","other":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","up":true},{"one":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","other":"6ad608f1087b8af40359db45e7d78e43c3465fdae5cc01a9a9ab6c149cb6fdf3","up":true},{"one":"893773caa8e126430f189186956f3e80b49107ce52777972f5e8c11027c072d6","other":"ef40fdb37f2f89ec77833e48b1bd210b2241949b9ef8f6dca6bf6189503abb2c","up":true},{"one":"459a945c8d6e67437bdfa0e067962b3591dfa0aa7c1177f8327ba1557bbf1569","other":"580a650c2c3e95d35e66264df6f48e28a091bacd20dc2c9828a5b563353d2052","up":true},{"one":"4067997c26042749c16f17169caac66e647b71e3557f7581dbec739d0baa2ef0","other":"e884bd666b0215cfe88ee19457c67c747fb1c6815855160f1c243c149b24e923","up":true},{"one":"9a8c60732ff0d0b7403b181cdc5bb9e6db0aa4cf5c7158c40e548fcb76726f87","other":"cd944ad4c50c4357005ff26ef26369ba3375a1205eec532b6e51b22bf25510bc","up":true},{"one":"0104c3fa3f6b9237565759debed3f1fcebfa75efba7f01e81f06a53274cdf619","other":"00b98e790fb954975837ff95abb1bdc2673dd8a0ce315986cc0dda5b0cefb05d","up":true},{"one":"212479d1ab813951780af2bc09a8119dbf62e603c05c0ea26329625036f6708a","other":"247dcb2c787abfa845b39cb9d7f9a6d59ea915006f250a4d760cdd8e16b3a5a6","up":true},{"one":"4e6eab6eb3d49b20e2f3dee169d4647ebf26d07044698ff93d51c5fc30fb4ae0","other":"795d813cab67300b8c59c78b6b99403c8111920ca47c1e56edfb513bc4999f3f","up":true},{"one":"1b6168d6a369029690e836d26ffe017e26769ce504c142abf2a4a65774ecb66d","other":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","up":true},{"one":"7a46bf03f327b100dc00140c97828a3b607280b6cd57220703be393ae4fdddbc","other":"ede2568d60ce7a12b4380818a31d2895fcb9b815cfb5526d28b2a82ff62e0381","up":true},{"one":"b77832f3f26323f09b37f31d208361d77ca08cec3ff829b9451fa3f00ff61fd0","other":"b87c0ee719d0656cd325300b756f203310675021f3c508b30542deb1114ec7ac","up":true},{"one":"e8d0bd04fa5b56fcd50cb4b1d550855c21192f1294f2954401348c8a28b455ab","other":"fd54f34d1deead333014c472182bf5cb2db1d1b1ab55caa70f5dc70108622f17","up":true},{"one":"9265383ecf776ce1da66e99e5f0a9c1af593d8e56ceef21f42aad4be7e34bc45","other":"9a8c60732ff0d0b7403b181cdc5bb9e6db0aa4cf5c7158c40e548fcb76726f87","up":true},{"one":"0066352177d6c225842362ab424a632f569980b02ac55a13a71593af94cb8c2d","other":"43d797cb7fced78dd7141ef452ea367d7484d5c0fec2431caebe1317c4aa6340","up":true},{"one":"247dcb2c787abfa845b39cb9d7f9a6d59ea915006f250a4d760cdd8e16b3a5a6","other":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","up":true},{"one":"016e174a29dc18857107fbebc876689b401c1174964ac6cacb31a4cb847a4d30","other":"ce12d7b26736d08eaa424a5f972a5a1fd50d05aab330340f3605dbab8007bf28","up":true},{"one":"d48642f8008e8aa791ba89c9b3a5b8d2d7aeb7e9078caee8813777679608075c","other":"e884bd666b0215cfe88ee19457c67c747fb1c6815855160f1c243c149b24e923","up":true},{"one":"07c79aaa7fa759f797b88d8c495c5bfaa12f15289c6a567bbc363fd070227830","other":"0561b40886edc84497731c7b64acdabb63a22abe573847b78f74887afd86d71e","up":true},{"one":"e884bd666b0215cfe88ee19457c67c747fb1c6815855160f1c243c149b24e923","other":"20c4c3a3523960ad74538e726e1caaeda987cbc16dcd3beeb63fca6eebd31405","up":true},{"one":"13165fffc318dd5f182601384e1ac69dd591236a03e88dfd1c637ea21a01b2dd","other":"1b6168d6a369029690e836d26ffe017e26769ce504c142abf2a4a65774ecb66d","up":true},{"one":"5f1c751641e1727871c44f0d5779b278871c77f5e05324dc91a8620bed72e627","other":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","up":true},{"one":"00b98e790fb954975837ff95abb1bdc2673dd8a0ce315986cc0dda5b0cefb05d","other":"051c8d430b54d843feb325c2b56e36524566d64f48f6c770739748409af44364","up":true},{"one":"39b5d09d1227a776f8d65b7239cd03e04499e7d519e5d1ff4c4870a1a949dd91","other":"24263ec4b92f390e07193d00e5a032ea2649f560027be02878da656e37d4a1b1","up":true},{"one":"6ad608f1087b8af40359db45e7d78e43c3465fdae5cc01a9a9ab6c149cb6fdf3","other":"766b61b773ad34c6cfd30b9c2e94840d151c31ab8cbcf546943eb5821d5a8f36","up":true},{"one":"ce12d7b26736d08eaa424a5f972a5a1fd50d05aab330340f3605dbab8007bf28","other":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","up":true},{"one":"8ff601de9537c9e5d79a3707a312fe893383bef000d00acee3d4552068ef6030","other":"85845413850f5e38cce69548e20179b477bf383dd2b22428358715d9d72421ac","up":true},{"one":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","other":"20c4c3a3523960ad74538e726e1caaeda987cbc16dcd3beeb63fca6eebd31405","up":true},{"one":"30c022df9209b4515213fe3fa67135c05d960afbd76f8455caa9c83ab5e493b6","other":"20c4c3a3523960ad74538e726e1caaeda987cbc16dcd3beeb63fca6eebd31405","up":true},{"one":"a3e87b083776e34d10941832ec078fe9a7b8bae8cefe6fecdd5561549561a7f3","other":"a93b2360b87425860185535aca8fbf3225e0c141d27690d60144689ae0f28534","up":true},{"one":"567206d70bc58993b08719afd9c007c562aad99437599356763b74cd4707083c","other":"580a650c2c3e95d35e66264df6f48e28a091bacd20dc2c9828a5b563353d2052","up":true},{"one":"adfde69b7633188d7c5a44e51e856d023c946d4cd8fd16bf539e6d1764dd7e04","other":"b26eff0d882918a40b909b2fc79489a671a9f990cc0dd3d835493bcb52a468ea","up":true},{"one":"41cd903abdaf445bfbcc3d5f28c10aba473be8a3ddc614c7f124b0ed7f91fd1d","other":"6c29a1bc0f1025207b4524fadd7c8cd3dd956816fdfc57bb525c4ad3bcca9169","up":true},{"one":"1a696462de3d83ff271f30f6d66de968325e37a36877dd67b45d9cdd7e644b5b","other":"1a16e158f1d35f2573d20b05ba51a2c9cc196547038e3b7be97ddc7598e81257","up":true},{"one":"b270e0d21a173f4717882b1c6dc942d703fe11cd6adf997b3ecd0944fcea6c7b","other":"b26eff0d882918a40b909b2fc79489a671a9f990cc0dd3d835493bcb52a468ea","up":true},{"one":"60cbf513366da3f6ee288d3572dc3971ae256b02e5492595e63c31a7b1b0d5a8","other":"6b7da636e8eccac658c0abf6aa8ddd74be6d4cf9c5e0333bd28d9a6d1fe43ab3","up":true},{"one":"a6cad8da6a7949d7624b7ea636fd2d72de6a9b14f899df6263ccb4b3e5351c6a","other":"b26eff0d882918a40b909b2fc79489a671a9f990cc0dd3d835493bcb52a468ea","up":true},{"one":"1030a5d27a502aeb868e7158373c48af6a85e829ee3a84f4a0b78f9a30bd5a6a","other":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","up":true},{"one":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","other":"1b6168d6a369029690e836d26ffe017e26769ce504c142abf2a4a65774ecb66d","up":true},{"one":"6c29a1bc0f1025207b4524fadd7c8cd3dd956816fdfc57bb525c4ad3bcca9169","other":"766b61b773ad34c6cfd30b9c2e94840d151c31ab8cbcf546943eb5821d5a8f36","up":true},{"one":"0d90040b9d0cd742149654262198aa8dc4fc96cfdf9b84cfe833277c3e23831f","other":"0066352177d6c225842362ab424a632f569980b02ac55a13a71593af94cb8c2d","up":true},{"one":"11b53fa40926ac84d75d9d698f46b9611b90194857c1397e117931a890447078","other":"0d90040b9d0cd742149654262198aa8dc4fc96cfdf9b84cfe833277c3e23831f","up":true},{"one":"88dc5f31a2b295e8b43bc6e7abaae130061c2d44c49c95bcca3d9dd0a268324c","other":"b26eff0d882918a40b909b2fc79489a671a9f990cc0dd3d835493bcb52a468ea","up":true},{"one":"9835fa9db1ce53ad2a526d36050eefd2c593121df4036c269cd09e1be4746a15","other":"958e075a7f22c092360d325dd0c0449e9442c398b2882009c5c3720cbcd3584f","up":true},{"one":"960eb6f745966f47aadb13d488aee1187486c99e6bc78c679a6d9b3d421d8eb0","other":"88a9b808cabfadb31c1a01b5a09eb4dc6cb7e011348f095920dd12e330ba9c0a","up":true},{"one":"bb9c9f07ee2f8bc2bb0f011309f8314de7c1777a6f683f90506b180ef6f5aab4","other":"6d307f348cc71e8a471ba662c1b5bd9d6e6930c22024f350465cccc7f2208a59","up":true},{"one":"cb704e26ceff5b613d8211423375fe0ded4d867bc892894a70d71289f1a9c1c1","other":"a20d591ea15722712e83fcc6c17732f2370034ef870234a0fa0b2f4aea572fd5","up":true},{"one":"089fe22a0bd48d3120cc8171eb9d698532c17f36768dbc25f554f2c53de11ab0","other":"0104c3fa3f6b9237565759debed3f1fcebfa75efba7f01e81f06a53274cdf619","up":true},{"one":"160c0401bcd1b38db7b4ae82470244ac9a9923755fc475aa67eb6dedc703c223","other":"1f1580f6bf3df2ce7a50ec53be2511d22149fede99ca6194b611471adabfafaa","up":true},{"one":"ede2568d60ce7a12b4380818a31d2895fcb9b815cfb5526d28b2a82ff62e0381","other":"e8d0bd04fa5b56fcd50cb4b1d550855c21192f1294f2954401348c8a28b455ab","up":true},{"one":"9d60b67d731aee2e6ac86d17c9e6a6aa5aaeec6b6803bae5c5fbc02643f7358c","other":"a20d591ea15722712e83fcc6c17732f2370034ef870234a0fa0b2f4aea572fd5","up":true},{"one":"2742110997dcb690946885e5a4aaa039f72c9f92025e62f1d371922acf5e27a7","other":"247dcb2c787abfa845b39cb9d7f9a6d59ea915006f250a4d760cdd8e16b3a5a6","up":true},{"one":"c6edfd613f216f48a1ea05be84faa5b9a69dd97c6e5d72e0bd339ea62c1b19e1","other":"cd944ad4c50c4357005ff26ef26369ba3375a1205eec532b6e51b22bf25510bc","up":true},{"one":"0ef0bf53ddadd2c942f21e7e09d5a902d166f13920aabb2aa6652b70c423f575","other":"0d90040b9d0cd742149654262198aa8dc4fc96cfdf9b84cfe833277c3e23831f","up":true},{"one":"f9e4dbb28099af8ca5f28c641a51672f4c6ab54b7037f765a7172a399acb8edc","other":"f84317ef488d8c1495b429b7a1fa21ea53a73e737b3c1aa890c20e38658ab148","up":true},{"one":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","other":"f9e4dbb28099af8ca5f28c641a51672f4c6ab54b7037f765a7172a399acb8edc","up":true},{"one":"766b61b773ad34c6cfd30b9c2e94840d151c31ab8cbcf546943eb5821d5a8f36","other":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","up":true},{"one":"58207fc8d92e44a8274398d6105b90d763981afba7021b57be98591e7b739272","other":"580a650c2c3e95d35e66264df6f48e28a091bacd20dc2c9828a5b563353d2052","up":true},{"one":"6d307f348cc71e8a471ba662c1b5bd9d6e6930c22024f350465cccc7f2208a59","other":"78db863d7f60eda6a275cc9f4d1696cb32f5d46de747d278aa17ecd06c791135","up":true},{"one":"11b53fa40926ac84d75d9d698f46b9611b90194857c1397e117931a890447078","other":"212479d1ab813951780af2bc09a8119dbf62e603c05c0ea26329625036f6708a","up":true},{"one":"c42f36bf6728b24721ce5ac1272a1058e202a236d276af1265a1eaa675f8fd85","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":true},{"one":"869fc21fff150ce9e1bc4512b183559433767cfc1cb7b6baae6d2a1313245ae8","other":"f84317ef488d8c1495b429b7a1fa21ea53a73e737b3c1aa890c20e38658ab148","up":true},{"one":"60cbf513366da3f6ee288d3572dc3971ae256b02e5492595e63c31a7b1b0d5a8","other":"74a4ffb0d717a523ec72e07cc5efe3e98d55932d6e0592ef45fe54b378e4e952","up":true},{"one":"7d14fd0b6c554f218249fed7709df51946baf91a3045ed99fb662c32cc97541b","other":"5f8eb6ff0f6ed720eaae86390757833f4b9cdfa52564457ed90c3de260ab6c30","up":true},{"one":"fb211f405a7dc3a07b6d4f18522c6273ffa4e18325958eea159e33f768eae846","other":"f84317ef488d8c1495b429b7a1fa21ea53a73e737b3c1aa890c20e38658ab148","up":true},{"one":"816609d5b9bdb0c7bad6cbe9400fd506df8662b14ad0e0a61bc7b49482548e39","other":"88a9b808cabfadb31c1a01b5a09eb4dc6cb7e011348f095920dd12e330ba9c0a","up":true},{"one":"8ff601de9537c9e5d79a3707a312fe893383bef000d00acee3d4552068ef6030","other":"88a9b808cabfadb31c1a01b5a09eb4dc6cb7e011348f095920dd12e330ba9c0a","up":true},{"one":"776fdbabbca42ecb49cc5451b860305ec3020889b47adaa2add6ea359afe379f","other":"7a46bf03f327b100dc00140c97828a3b607280b6cd57220703be393ae4fdddbc","up":true},{"one":"fe9d68b6f0d0add106d4a9e132308cf5caa867624784d652f071f5d1fe87ff8b","other":"1d531b5c8dd7bbbbf8c2db8f6984d2c9b23d5fee33876379e9d3e62a920e04b9","up":true},{"one":"f84317ef488d8c1495b429b7a1fa21ea53a73e737b3c1aa890c20e38658ab148","other":"9d60b67d731aee2e6ac86d17c9e6a6aa5aaeec6b6803bae5c5fbc02643f7358c","up":true},{"one":"85845413850f5e38cce69548e20179b477bf383dd2b22428358715d9d72421ac","other":"d48642f8008e8aa791ba89c9b3a5b8d2d7aeb7e9078caee8813777679608075c","up":true},{"one":"22b84df73059c92bf59fdad51fe574e027c7cc555dddbf363d745c1c422a89fd","other":"212479d1ab813951780af2bc09a8119dbf62e603c05c0ea26329625036f6708a","up":true},{"one":"0f19fb4d0c6cfe36f7f59c8fe54d2e169687603e1bde0fe6b3a5e864fa51f2f0","other":"089fe22a0bd48d3120cc8171eb9d698532c17f36768dbc25f554f2c53de11ab0","up":true},{"one":"2c3d7e056f87daf540821a31ea8fe9377d00ed3a55ffba67b8bb716cdd9b7303","other":"212479d1ab813951780af2bc09a8119dbf62e603c05c0ea26329625036f6708a","up":true},{"one":"7c992ceefe03187bce1edbf8f80ccd1cab80b3908e625a61fef21ec6d35d04a3","other":"5f8eb6ff0f6ed720eaae86390757833f4b9cdfa52564457ed90c3de260ab6c30","up":true},{"one":"746898d197934d17cd93b958ab20aaa85dba99057495048d2cabc8a996926e05","other":"7c992ceefe03187bce1edbf8f80ccd1cab80b3908e625a61fef21ec6d35d04a3","up":true},{"one":"66f6ff5fbc2526c1db5db491e49138362bef23032c6344ed4ead7dcb58bd1e6c","other":"816609d5b9bdb0c7bad6cbe9400fd506df8662b14ad0e0a61bc7b49482548e39","up":true},{"one":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","other":"6d307f348cc71e8a471ba662c1b5bd9d6e6930c22024f350465cccc7f2208a59","up":true},{"one":"1a16e158f1d35f2573d20b05ba51a2c9cc196547038e3b7be97ddc7598e81257","other":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","up":true},{"one":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":true},{"one":"d6e3d3ffd3858b8284bc9495b2d90b4b1124009a4a7d7394182977a8ae39df55","other":"a93b2360b87425860185535aca8fbf3225e0c141d27690d60144689ae0f28534","up":true},{"one":"a3e87b083776e34d10941832ec078fe9a7b8bae8cefe6fecdd5561549561a7f3","other":"b77832f3f26323f09b37f31d208361d77ca08cec3ff829b9451fa3f00ff61fd0","up":true},{"one":"5f1c751641e1727871c44f0d5779b278871c77f5e05324dc91a8620bed72e627","other":"5258fdc94d78bb45d6634f2672bc2eacdbc896fc5a39784ecc9d8a7dd52cb1e7","up":true},{"one":"74a4ffb0d717a523ec72e07cc5efe3e98d55932d6e0592ef45fe54b378e4e952","other":"746898d197934d17cd93b958ab20aaa85dba99057495048d2cabc8a996926e05","up":true},{"one":"c1c13885e3f543804e62e3b34c0d9762778866e5b8fe9d9df0caa6a64ec41428","other":"d48642f8008e8aa791ba89c9b3a5b8d2d7aeb7e9078caee8813777679608075c","up":true},{"one":"0066352177d6c225842362ab424a632f569980b02ac55a13a71593af94cb8c2d","other":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","up":true},{"one":"20c4c3a3523960ad74538e726e1caaeda987cbc16dcd3beeb63fca6eebd31405","other":"bb9c9f07ee2f8bc2bb0f011309f8314de7c1777a6f683f90506b180ef6f5aab4","up":true},{"one":"1f1580f6bf3df2ce7a50ec53be2511d22149fede99ca6194b611471adabfafaa","other":"1030a5d27a502aeb868e7158373c48af6a85e829ee3a84f4a0b78f9a30bd5a6a","up":true},{"one":"944e8231f9f661435f1f94abfaa17862a005877487df5c5d3a566c4dbe46be3f","other":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","up":true},{"one":"81748a1a38162f9e8613ffa562add1b75893b66199840e74c0774db92bfd7ca0","other":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","up":true},{"one":"795d813cab67300b8c59c78b6b99403c8111920ca47c1e56edfb513bc4999f3f","other":"13165fffc318dd5f182601384e1ac69dd591236a03e88dfd1c637ea21a01b2dd","up":true},{"one":"88a9b808cabfadb31c1a01b5a09eb4dc6cb7e011348f095920dd12e330ba9c0a","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":true},{"one":"cd944ad4c50c4357005ff26ef26369ba3375a1205eec532b6e51b22bf25510bc","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":true},{"one":"1030a5d27a502aeb868e7158373c48af6a85e829ee3a84f4a0b78f9a30bd5a6a","other":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","up":true},{"one":"766b61b773ad34c6cfd30b9c2e94840d151c31ab8cbcf546943eb5821d5a8f36","other":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","up":true},{"one":"78db863d7f60eda6a275cc9f4d1696cb32f5d46de747d278aa17ecd06c791135","other":"7a46bf03f327b100dc00140c97828a3b607280b6cd57220703be393ae4fdddbc","up":true},{"one":"958e075a7f22c092360d325dd0c0449e9442c398b2882009c5c3720cbcd3584f","other":"893773caa8e126430f189186956f3e80b49107ce52777972f5e8c11027c072d6","up":true},{"one":"b77832f3f26323f09b37f31d208361d77ca08cec3ff829b9451fa3f00ff61fd0","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":true},{"one":"e22c1f74c4b480fdd2fbe84ad321a98230efb98fb1cdec4f79e4e50b2b0d5cc4","other":"e884bd666b0215cfe88ee19457c67c747fb1c6815855160f1c243c149b24e923","up":true},{"one":"aff775d47ebe35fbaaad26c627470abb8481558cb5a08c42a44597a98408a919","other":"893773caa8e126430f189186956f3e80b49107ce52777972f5e8c11027c072d6","up":true},{"one":"5958a4ccd5333e8bfed5f18c78d365b201ffc2b9af3afd31748adb8e4f8118bc","other":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","up":true},{"one":"351d1ed5c4012f8870ef2acadc7c0fea2cb697f5688b92e0150c5f670661c617","other":"20c4c3a3523960ad74538e726e1caaeda987cbc16dcd3beeb63fca6eebd31405","up":true},{"one":"b26eff0d882918a40b909b2fc79489a671a9f990cc0dd3d835493bcb52a468ea","other":"a93b2360b87425860185535aca8fbf3225e0c141d27690d60144689ae0f28534","up":true},{"one":"051c8d430b54d843feb325c2b56e36524566d64f48f6c770739748409af44364","other":"07c79aaa7fa759f797b88d8c495c5bfaa12f15289c6a567bbc363fd070227830","up":true},{"one":"893773caa8e126430f189186956f3e80b49107ce52777972f5e8c11027c072d6","other":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","up":true},{"one":"4067997c26042749c16f17169caac66e647b71e3557f7581dbec739d0baa2ef0","other":"d48642f8008e8aa791ba89c9b3a5b8d2d7aeb7e9078caee8813777679608075c","up":true},{"one":"24263ec4b92f390e07193d00e5a032ea2649f560027be02878da656e37d4a1b1","other":"20c4c3a3523960ad74538e726e1caaeda987cbc16dcd3beeb63fca6eebd31405","up":true},{"one":"ac3887308a4c3591293c43f5d5f312874b30e0dc19faa57626fdc6b830d3e433","other":"88a9b808cabfadb31c1a01b5a09eb4dc6cb7e011348f095920dd12e330ba9c0a","up":true},{"one":"459a945c8d6e67437bdfa0e067962b3591dfa0aa7c1177f8327ba1557bbf1569","other":"567206d70bc58993b08719afd9c007c562aad99437599356763b74cd4707083c","up":true},{"one":"b87c0ee719d0656cd325300b756f203310675021f3c508b30542deb1114ec7ac","other":"a93b2360b87425860185535aca8fbf3225e0c141d27690d60144689ae0f28534","up":true},{"one":"1b6168d6a369029690e836d26ffe017e26769ce504c142abf2a4a65774ecb66d","other":"e884bd666b0215cfe88ee19457c67c747fb1c6815855160f1c243c149b24e923","up":true},{"one":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","other":"958e075a7f22c092360d325dd0c0449e9442c398b2882009c5c3720cbcd3584f","up":true},{"one":"6d307f348cc71e8a471ba662c1b5bd9d6e6930c22024f350465cccc7f2208a59","other":"776fdbabbca42ecb49cc5451b860305ec3020889b47adaa2add6ea359afe379f","up":true},{"one":"9a8c60732ff0d0b7403b181cdc5bb9e6db0aa4cf5c7158c40e548fcb76726f87","other":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","up":true},{"one":"4610c06c8f5e1e24a75d9e46a432f4c1a6b9da6c3dbd122d3d3881752d930736","other":"567206d70bc58993b08719afd9c007c562aad99437599356763b74cd4707083c","up":true},{"one":"247dcb2c787abfa845b39cb9d7f9a6d59ea915006f250a4d760cdd8e16b3a5a6","other":"4e6eab6eb3d49b20e2f3dee169d4647ebf26d07044698ff93d51c5fc30fb4ae0","up":true},{"one":"ef40fdb37f2f89ec77833e48b1bd210b2241949b9ef8f6dca6bf6189503abb2c","other":"e22c1f74c4b480fdd2fbe84ad321a98230efb98fb1cdec4f79e4e50b2b0d5cc4","up":true},{"one":"bb9c9f07ee2f8bc2bb0f011309f8314de7c1777a6f683f90506b180ef6f5aab4","other":"1d531b5c8dd7bbbbf8c2db8f6984d2c9b23d5fee33876379e9d3e62a920e04b9","up":true},{"one":"9b240fbf12d9de7975803b09aacdbab94cd978130de62bc89497bb1056e462c0","other":"9835fa9db1ce53ad2a526d36050eefd2c593121df4036c269cd09e1be4746a15","up":true},{"one":"960eb6f745966f47aadb13d488aee1187486c99e6bc78c679a6d9b3d421d8eb0","other":"958e075a7f22c092360d325dd0c0449e9442c398b2882009c5c3720cbcd3584f","up":true},{"one":"160c0401bcd1b38db7b4ae82470244ac9a9923755fc475aa67eb6dedc703c223","other":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","up":true},{"one":"6b1f3a9b65ce3a168fc98dee2b14b1de6912fd02ee2609b047b2c56f6c3b53e9","other":"7c992ceefe03187bce1edbf8f80ccd1cab80b3908e625a61fef21ec6d35d04a3","up":true},{"one":"41f407b92b1462b93400819a6af8815797b9dbc505afaf0162bbaea795205716","other":"45cdaac4c087e6b737507fa29936d09f2fbea14f49ef0ce91c18080456a7459b","up":true},{"one":"1263f18bf8cb4f5a3335f545cdd5d42f1b84da9edf4fa7346572c2bc38c87eb3","other":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","up":true},{"one":"13165fffc318dd5f182601384e1ac69dd591236a03e88dfd1c637ea21a01b2dd","other":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","up":true},{"one":"41f407b92b1462b93400819a6af8815797b9dbc505afaf0162bbaea795205716","other":"459a945c8d6e67437bdfa0e067962b3591dfa0aa7c1177f8327ba1557bbf1569","up":true},{"one":"580a650c2c3e95d35e66264df6f48e28a091bacd20dc2c9828a5b563353d2052","other":"d48642f8008e8aa791ba89c9b3a5b8d2d7aeb7e9078caee8813777679608075c","up":true},{"one":"39b5d09d1227a776f8d65b7239cd03e04499e7d519e5d1ff4c4870a1a949dd91","other":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","up":true},{"one":"6c2fd129c8493cea7634a91aae559ceb64f01cebc45ec59aebd7e709b65941be","other":"776fdbabbca42ecb49cc5451b860305ec3020889b47adaa2add6ea359afe379f","up":true},{"one":"5258fdc94d78bb45d6634f2672bc2eacdbc896fc5a39784ecc9d8a7dd52cb1e7","other":"247dcb2c787abfa845b39cb9d7f9a6d59ea915006f250a4d760cdd8e16b3a5a6","up":true},{"one":"4a2d15b867a620653f099d66839b80c2dd32aec95caf906f0d5c8146192edf62","other":"4610c06c8f5e1e24a75d9e46a432f4c1a6b9da6c3dbd122d3d3881752d930736","up":true},{"one":"f9e4dbb28099af8ca5f28c641a51672f4c6ab54b7037f765a7172a399acb8edc","other":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","up":true},{"one":"74a4ffb0d717a523ec72e07cc5efe3e98d55932d6e0592ef45fe54b378e4e952","other":"776fdbabbca42ecb49cc5451b860305ec3020889b47adaa2add6ea359afe379f","up":true},{"one":"c42f36bf6728b24721ce5ac1272a1058e202a236d276af1265a1eaa675f8fd85","other":"ce12d7b26736d08eaa424a5f972a5a1fd50d05aab330340f3605dbab8007bf28","up":true},{"one":"9265383ecf776ce1da66e99e5f0a9c1af593d8e56ceef21f42aad4be7e34bc45","other":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","up":true},{"one":"11b53fa40926ac84d75d9d698f46b9611b90194857c1397e117931a890447078","other":"1b6168d6a369029690e836d26ffe017e26769ce504c142abf2a4a65774ecb66d","up":true},{"one":"4f7abbc51265dfe22d58f3ac5cde2c05ec56053efe79e3ebb6ece96183dcc17a","other":"4e6eab6eb3d49b20e2f3dee169d4647ebf26d07044698ff93d51c5fc30fb4ae0","up":true},{"one":"60cbf513366da3f6ee288d3572dc3971ae256b02e5492595e63c31a7b1b0d5a8","other":"6421894e25e421d16eccd8858aed3354fce2d04c9d5c46d4005ee1f16944ccfd","up":true},{"one":"869fc21fff150ce9e1bc4512b183559433767cfc1cb7b6baae6d2a1313245ae8","other":"bb9c9f07ee2f8bc2bb0f011309f8314de7c1777a6f683f90506b180ef6f5aab4","up":true},{"one":"a20d591ea15722712e83fcc6c17732f2370034ef870234a0fa0b2f4aea572fd5","other":"adfde69b7633188d7c5a44e51e856d023c946d4cd8fd16bf539e6d1764dd7e04","up":true},{"one":"18b0fcbba2350241088129e8f6a07742857c3edaacd6ef1ec0861cc8e48492ce","other":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","up":true},{"one":"6ad608f1087b8af40359db45e7d78e43c3465fdae5cc01a9a9ab6c149cb6fdf3","other":"6d307f348cc71e8a471ba662c1b5bd9d6e6930c22024f350465cccc7f2208a59","up":true},{"one":"351d1ed5c4012f8870ef2acadc7c0fea2cb697f5688b92e0150c5f670661c617","other":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","up":true},{"one":"45cdaac4c087e6b737507fa29936d09f2fbea14f49ef0ce91c18080456a7459b","other":"4610c06c8f5e1e24a75d9e46a432f4c1a6b9da6c3dbd122d3d3881752d930736","up":true},{"one":"81748a1a38162f9e8613ffa562add1b75893b66199840e74c0774db92bfd7ca0","other":"816609d5b9bdb0c7bad6cbe9400fd506df8662b14ad0e0a61bc7b49482548e39","up":true},{"one":"6421894e25e421d16eccd8858aed3354fce2d04c9d5c46d4005ee1f16944ccfd","other":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","up":true},{"one":"051c8d430b54d843feb325c2b56e36524566d64f48f6c770739748409af44364","other":"0104c3fa3f6b9237565759debed3f1fcebfa75efba7f01e81f06a53274cdf619","up":true},{"one":"f9265e3c23cd931ad51faec142636a6a8c8bd90a23e77f5327861c81cf4dfe17","other":"459a945c8d6e67437bdfa0e067962b3591dfa0aa7c1177f8327ba1557bbf1569","up":true},{"one":"9a8c60732ff0d0b7403b181cdc5bb9e6db0aa4cf5c7158c40e548fcb76726f87","other":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","up":true},{"one":"6b1f3a9b65ce3a168fc98dee2b14b1de6912fd02ee2609b047b2c56f6c3b53e9","other":"7d14fd0b6c554f218249fed7709df51946baf91a3045ed99fb662c32cc97541b","up":true},{"one":"1263f18bf8cb4f5a3335f545cdd5d42f1b84da9edf4fa7346572c2bc38c87eb3","other":"1030a5d27a502aeb868e7158373c48af6a85e829ee3a84f4a0b78f9a30bd5a6a","up":true},{"one":"580a650c2c3e95d35e66264df6f48e28a091bacd20dc2c9828a5b563353d2052","other":"cd944ad4c50c4357005ff26ef26369ba3375a1205eec532b6e51b22bf25510bc","up":true},{"one":"6c2fd129c8493cea7634a91aae559ceb64f01cebc45ec59aebd7e709b65941be","other":"6d307f348cc71e8a471ba662c1b5bd9d6e6930c22024f350465cccc7f2208a59","up":true},{"one":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","other":"e22c1f74c4b480fdd2fbe84ad321a98230efb98fb1cdec4f79e4e50b2b0d5cc4","up":true},{"one":"4a2d15b867a620653f099d66839b80c2dd32aec95caf906f0d5c8146192edf62","other":"746898d197934d17cd93b958ab20aaa85dba99057495048d2cabc8a996926e05","up":true},{"one":"ede2568d60ce7a12b4380818a31d2895fcb9b815cfb5526d28b2a82ff62e0381","other":"b270e0d21a173f4717882b1c6dc942d703fe11cd6adf997b3ecd0944fcea6c7b","up":true},{"one":"f9e4dbb28099af8ca5f28c641a51672f4c6ab54b7037f765a7172a399acb8edc","other":"e22c1f74c4b480fdd2fbe84ad321a98230efb98fb1cdec4f79e4e50b2b0d5cc4","up":true},{"one":"9265383ecf776ce1da66e99e5f0a9c1af593d8e56ceef21f42aad4be7e34bc45","other":"869fc21fff150ce9e1bc4512b183559433767cfc1cb7b6baae6d2a1313245ae8","up":true},{"one":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","other":"1a696462de3d83ff271f30f6d66de968325e37a36877dd67b45d9cdd7e644b5b","up":true},{"one":"816609d5b9bdb0c7bad6cbe9400fd506df8662b14ad0e0a61bc7b49482548e39","other":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","up":true},{"one":"8ff601de9537c9e5d79a3707a312fe893383bef000d00acee3d4552068ef6030","other":"88dc5f31a2b295e8b43bc6e7abaae130061c2d44c49c95bcca3d9dd0a268324c","up":true},{"one":"5f8eb6ff0f6ed720eaae86390757833f4b9cdfa52564457ed90c3de260ab6c30","other":"58207fc8d92e44a8274398d6105b90d763981afba7021b57be98591e7b739272","up":true},{"one":"85845413850f5e38cce69548e20179b477bf383dd2b22428358715d9d72421ac","other":"869fc21fff150ce9e1bc4512b183559433767cfc1cb7b6baae6d2a1313245ae8","up":true},{"one":"57d3361ee59d006e1dba3e1ae9ea624444ee09f70410dff492d624d7e7786d2d","other":"58207fc8d92e44a8274398d6105b90d763981afba7021b57be98591e7b739272","up":true},{"one":"776fdbabbca42ecb49cc5451b860305ec3020889b47adaa2add6ea359afe379f","other":"7d14fd0b6c554f218249fed7709df51946baf91a3045ed99fb662c32cc97541b","up":true},{"one":"30c022df9209b4515213fe3fa67135c05d960afbd76f8455caa9c83ab5e493b6","other":"1b6168d6a369029690e836d26ffe017e26769ce504c142abf2a4a65774ecb66d","up":true},{"one":"746898d197934d17cd93b958ab20aaa85dba99057495048d2cabc8a996926e05","other":"7d14fd0b6c554f218249fed7709df51946baf91a3045ed99fb662c32cc97541b","up":true},{"one":"1d531b5c8dd7bbbbf8c2db8f6984d2c9b23d5fee33876379e9d3e62a920e04b9","other":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","up":true},{"one":"78db863d7f60eda6a275cc9f4d1696cb32f5d46de747d278aa17ecd06c791135","other":"7c992ceefe03187bce1edbf8f80ccd1cab80b3908e625a61fef21ec6d35d04a3","up":true},{"one":"567206d70bc58993b08719afd9c007c562aad99437599356763b74cd4707083c","other":"5f8eb6ff0f6ed720eaae86390757833f4b9cdfa52564457ed90c3de260ab6c30","up":true},{"one":"aff775d47ebe35fbaaad26c627470abb8481558cb5a08c42a44597a98408a919","other":"adfde69b7633188d7c5a44e51e856d023c946d4cd8fd16bf539e6d1764dd7e04","up":true},{"one":"5958a4ccd5333e8bfed5f18c78d365b201ffc2b9af3afd31748adb8e4f8118bc","other":"58207fc8d92e44a8274398d6105b90d763981afba7021b57be98591e7b739272","up":true},{"one":"ac3887308a4c3591293c43f5d5f312874b30e0dc19faa57626fdc6b830d3e433","other":"adfde69b7633188d7c5a44e51e856d023c946d4cd8fd16bf539e6d1764dd7e04","up":true},{"one":"41cd903abdaf445bfbcc3d5f28c10aba473be8a3ddc614c7f124b0ed7f91fd1d","other":"4e6eab6eb3d49b20e2f3dee169d4647ebf26d07044698ff93d51c5fc30fb4ae0","up":true},{"one":"b87c0ee719d0656cd325300b756f203310675021f3c508b30542deb1114ec7ac","other":"a3e87b083776e34d10941832ec078fe9a7b8bae8cefe6fecdd5561549561a7f3","up":true},{"one":"6d307f348cc71e8a471ba662c1b5bd9d6e6930c22024f350465cccc7f2208a59","other":"6c29a1bc0f1025207b4524fadd7c8cd3dd956816fdfc57bb525c4ad3bcca9169","up":true},{"one":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","other":"e22c1f74c4b480fdd2fbe84ad321a98230efb98fb1cdec4f79e4e50b2b0d5cc4","up":true},{"one":"4610c06c8f5e1e24a75d9e46a432f4c1a6b9da6c3dbd122d3d3881752d930736","other":"41f407b92b1462b93400819a6af8815797b9dbc505afaf0162bbaea795205716","up":true},{"one":"ef40fdb37f2f89ec77833e48b1bd210b2241949b9ef8f6dca6bf6189503abb2c","other":"fd54f34d1deead333014c472182bf5cb2db1d1b1ab55caa70f5dc70108622f17","up":true},{"one":"b270e0d21a173f4717882b1c6dc942d703fe11cd6adf997b3ecd0944fcea6c7b","other":"adfde69b7633188d7c5a44e51e856d023c946d4cd8fd16bf539e6d1764dd7e04","up":true},{"one":"bb9c9f07ee2f8bc2bb0f011309f8314de7c1777a6f683f90506b180ef6f5aab4","other":"1f1580f6bf3df2ce7a50ec53be2511d22149fede99ca6194b611471adabfafaa","up":true},{"one":"960eb6f745966f47aadb13d488aee1187486c99e6bc78c679a6d9b3d421d8eb0","other":"9a8c60732ff0d0b7403b181cdc5bb9e6db0aa4cf5c7158c40e548fcb76726f87","up":true},{"one":"0104c3fa3f6b9237565759debed3f1fcebfa75efba7f01e81f06a53274cdf619","other":"d6a203c341b618c47ae53b339563b22c31dfc12d89dea7ba0c33e1812829684c","up":true},{"one":"160c0401bcd1b38db7b4ae82470244ac9a9923755fc475aa67eb6dedc703c223","other":"1030a5d27a502aeb868e7158373c48af6a85e829ee3a84f4a0b78f9a30bd5a6a","up":true},{"one":"b77832f3f26323f09b37f31d208361d77ca08cec3ff829b9451fa3f00ff61fd0","other":"b270e0d21a173f4717882b1c6dc942d703fe11cd6adf997b3ecd0944fcea6c7b","up":true},{"one":"7a46bf03f327b100dc00140c97828a3b607280b6cd57220703be393ae4fdddbc","other":"567206d70bc58993b08719afd9c007c562aad99437599356763b74cd4707083c","up":true},{"one":"a6cad8da6a7949d7624b7ea636fd2d72de6a9b14f899df6263ccb4b3e5351c6a","other":"20c4c3a3523960ad74538e726e1caaeda987cbc16dcd3beeb63fca6eebd31405","up":true},{"one":"016e174a29dc18857107fbebc876689b401c1174964ac6cacb31a4cb847a4d30","other":"fd54f34d1deead333014c472182bf5cb2db1d1b1ab55caa70f5dc70108622f17","up":true},{"one":"ede2568d60ce7a12b4380818a31d2895fcb9b815cfb5526d28b2a82ff62e0381","other":"9835fa9db1ce53ad2a526d36050eefd2c593121df4036c269cd09e1be4746a15","up":true},{"one":"88a9b808cabfadb31c1a01b5a09eb4dc6cb7e011348f095920dd12e330ba9c0a","other":"f9265e3c23cd931ad51faec142636a6a8c8bd90a23e77f5327861c81cf4dfe17","up":true},{"one":"fcf3ccf76c4699752670103e9cb98ac1294339a357876c71673f7e486d02f08b","other":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","up":true},{"one":"a3e87b083776e34d10941832ec078fe9a7b8bae8cefe6fecdd5561549561a7f3","other":"aff775d47ebe35fbaaad26c627470abb8481558cb5a08c42a44597a98408a919","up":true},{"one":"c1c13885e3f543804e62e3b34c0d9762778866e5b8fe9d9df0caa6a64ec41428","other":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","up":true},{"one":"1f1580f6bf3df2ce7a50ec53be2511d22149fede99ca6194b611471adabfafaa","other":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","up":true},{"one":"9835fa9db1ce53ad2a526d36050eefd2c593121df4036c269cd09e1be4746a15","other":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","up":true},{"one":"fe9d68b6f0d0add106d4a9e132308cf5caa867624784d652f071f5d1fe87ff8b","other":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","up":true},{"one":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","other":"e22c1f74c4b480fdd2fbe84ad321a98230efb98fb1cdec4f79e4e50b2b0d5cc4","up":true},{"one":"adfde69b7633188d7c5a44e51e856d023c946d4cd8fd16bf539e6d1764dd7e04","other":"9b240fbf12d9de7975803b09aacdbab94cd978130de62bc89497bb1056e462c0","up":true},{"one":"f9265e3c23cd931ad51faec142636a6a8c8bd90a23e77f5327861c81cf4dfe17","other":"816609d5b9bdb0c7bad6cbe9400fd506df8662b14ad0e0a61bc7b49482548e39","up":true},{"one":"b26eff0d882918a40b909b2fc79489a671a9f990cc0dd3d835493bcb52a468ea","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":true},{"one":"9d60b67d731aee2e6ac86d17c9e6a6aa5aaeec6b6803bae5c5fbc02643f7358c","other":"cb704e26ceff5b613d8211423375fe0ded4d867bc892894a70d71289f1a9c1c1","up":true},{"one":"1030a5d27a502aeb868e7158373c48af6a85e829ee3a84f4a0b78f9a30bd5a6a","other":"1d531b5c8dd7bbbbf8c2db8f6984d2c9b23d5fee33876379e9d3e62a920e04b9","up":true},{"one":"13165fffc318dd5f182601384e1ac69dd591236a03e88dfd1c637ea21a01b2dd","other":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","up":true},{"one":"9835fa9db1ce53ad2a526d36050eefd2c593121df4036c269cd09e1be4746a15","other":"247dcb2c787abfa845b39cb9d7f9a6d59ea915006f250a4d760cdd8e16b3a5a6","up":true},{"one":"d6e3d3ffd3858b8284bc9495b2d90b4b1124009a4a7d7394182977a8ae39df55","other":"f9265e3c23cd931ad51faec142636a6a8c8bd90a23e77f5327861c81cf4dfe17","up":true},{"one":"0104c3fa3f6b9237565759debed3f1fcebfa75efba7f01e81f06a53274cdf619","other":"fd54f34d1deead333014c472182bf5cb2db1d1b1ab55caa70f5dc70108622f17","up":true},{"one":"ce12d7b26736d08eaa424a5f972a5a1fd50d05aab330340f3605dbab8007bf28","other":"cd944ad4c50c4357005ff26ef26369ba3375a1205eec532b6e51b22bf25510bc","up":true},{"one":"7a46bf03f327b100dc00140c97828a3b607280b6cd57220703be393ae4fdddbc","other":"580a650c2c3e95d35e66264df6f48e28a091bacd20dc2c9828a5b563353d2052","up":true},{"one":"adfde69b7633188d7c5a44e51e856d023c946d4cd8fd16bf539e6d1764dd7e04","other":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","up":true},{"one":"6c29a1bc0f1025207b4524fadd7c8cd3dd956816fdfc57bb525c4ad3bcca9169","other":"20c4c3a3523960ad74538e726e1caaeda987cbc16dcd3beeb63fca6eebd31405","up":true},{"one":"9d60b67d731aee2e6ac86d17c9e6a6aa5aaeec6b6803bae5c5fbc02643f7358c","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":true},{"one":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","other":"22b84df73059c92bf59fdad51fe574e027c7cc555dddbf363d745c1c422a89fd","up":true},{"one":"ce12d7b26736d08eaa424a5f972a5a1fd50d05aab330340f3605dbab8007bf28","other":"fb211f405a7dc3a07b6d4f18522c6273ffa4e18325958eea159e33f768eae846","up":true},{"one":"88dc5f31a2b295e8b43bc6e7abaae130061c2d44c49c95bcca3d9dd0a268324c","other":"b270e0d21a173f4717882b1c6dc942d703fe11cd6adf997b3ecd0944fcea6c7b","up":true},{"one":"6c29a1bc0f1025207b4524fadd7c8cd3dd956816fdfc57bb525c4ad3bcca9169","other":"247dcb2c787abfa845b39cb9d7f9a6d59ea915006f250a4d760cdd8e16b3a5a6","up":true},{"one":"089fe22a0bd48d3120cc8171eb9d698532c17f36768dbc25f554f2c53de11ab0","other":"00b98e790fb954975837ff95abb1bdc2673dd8a0ce315986cc0dda5b0cefb05d","up":true},{"one":"1a696462de3d83ff271f30f6d66de968325e37a36877dd67b45d9cdd7e644b5b","other":"1b6168d6a369029690e836d26ffe017e26769ce504c142abf2a4a65774ecb66d","up":true},{"one":"e884bd666b0215cfe88ee19457c67c747fb1c6815855160f1c243c149b24e923","other":"fd54f34d1deead333014c472182bf5cb2db1d1b1ab55caa70f5dc70108622f17","up":true},{"one":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","other":"2742110997dcb690946885e5a4aaa039f72c9f92025e62f1d371922acf5e27a7","up":true},{"one":"8357eeaa65808f09b9a83362cb07508878becee9f843a5e1e70d025167b198ea","other":"816609d5b9bdb0c7bad6cbe9400fd506df8662b14ad0e0a61bc7b49482548e39","up":true},{"one":"a20d591ea15722712e83fcc6c17732f2370034ef870234a0fa0b2f4aea572fd5","other":"85845413850f5e38cce69548e20179b477bf383dd2b22428358715d9d72421ac","up":true},{"one":"089fe22a0bd48d3120cc8171eb9d698532c17f36768dbc25f554f2c53de11ab0","other":"0066352177d6c225842362ab424a632f569980b02ac55a13a71593af94cb8c2d","up":true},{"one":"69baf7ee43b360a3238ad3b175e0ad9c24967dae60c7e91d1ca7ee55167b2bdb","other":"6b1f3a9b65ce3a168fc98dee2b14b1de6912fd02ee2609b047b2c56f6c3b53e9","up":true},{"one":"1a696462de3d83ff271f30f6d66de968325e37a36877dd67b45d9cdd7e644b5b","other":"6421894e25e421d16eccd8858aed3354fce2d04c9d5c46d4005ee1f16944ccfd","up":true},{"one":"869fc21fff150ce9e1bc4512b183559433767cfc1cb7b6baae6d2a1313245ae8","other":"816609d5b9bdb0c7bad6cbe9400fd506df8662b14ad0e0a61bc7b49482548e39","up":true},{"one":"8357eeaa65808f09b9a83362cb07508878becee9f843a5e1e70d025167b198ea","other":"a6cad8da6a7949d7624b7ea636fd2d72de6a9b14f899df6263ccb4b3e5351c6a","up":true},{"one":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","other":"580a650c2c3e95d35e66264df6f48e28a091bacd20dc2c9828a5b563353d2052","up":true},{"one":"85845413850f5e38cce69548e20179b477bf383dd2b22428358715d9d72421ac","other":"8357eeaa65808f09b9a83362cb07508878becee9f843a5e1e70d025167b198ea","up":true},{"one":"57d3361ee59d006e1dba3e1ae9ea624444ee09f70410dff492d624d7e7786d2d","other":"5258fdc94d78bb45d6634f2672bc2eacdbc896fc5a39784ecc9d8a7dd52cb1e7","up":true},{"one":"58207fc8d92e44a8274398d6105b90d763981afba7021b57be98591e7b739272","other":"41f407b92b1462b93400819a6af8815797b9dbc505afaf0162bbaea795205716","up":true},{"one":"74a4ffb0d717a523ec72e07cc5efe3e98d55932d6e0592ef45fe54b378e4e952","other":"766b61b773ad34c6cfd30b9c2e94840d151c31ab8cbcf546943eb5821d5a8f36","up":true},{"one":"69baf7ee43b360a3238ad3b175e0ad9c24967dae60c7e91d1ca7ee55167b2bdb","other":"6421894e25e421d16eccd8858aed3354fce2d04c9d5c46d4005ee1f16944ccfd","up":true},{"one":"9265383ecf776ce1da66e99e5f0a9c1af593d8e56ceef21f42aad4be7e34bc45","other":"960eb6f745966f47aadb13d488aee1187486c99e6bc78c679a6d9b3d421d8eb0","up":true},{"one":"81748a1a38162f9e8613ffa562add1b75893b66199840e74c0774db92bfd7ca0","other":"8357eeaa65808f09b9a83362cb07508878becee9f843a5e1e70d025167b198ea","up":true},{"one":"fb211f405a7dc3a07b6d4f18522c6273ffa4e18325958eea159e33f768eae846","other":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","up":true},{"one":"212479d1ab813951780af2bc09a8119dbf62e603c05c0ea26329625036f6708a","other":"c6edfd613f216f48a1ea05be84faa5b9a69dd97c6e5d72e0bd339ea62c1b19e1","up":true},{"one":"cd944ad4c50c4357005ff26ef26369ba3375a1205eec532b6e51b22bf25510bc","other":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","up":true},{"one":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","other":"57d3361ee59d006e1dba3e1ae9ea624444ee09f70410dff492d624d7e7786d2d","up":true},{"one":"6b7da636e8eccac658c0abf6aa8ddd74be6d4cf9c5e0333bd28d9a6d1fe43ab3","other":"6ad608f1087b8af40359db45e7d78e43c3465fdae5cc01a9a9ab6c149cb6fdf3","up":true},{"one":"fcf3ccf76c4699752670103e9cb98ac1294339a357876c71673f7e486d02f08b","other":"e22c1f74c4b480fdd2fbe84ad321a98230efb98fb1cdec4f79e4e50b2b0d5cc4","up":true},{"one":"2742110997dcb690946885e5a4aaa039f72c9f92025e62f1d371922acf5e27a7","other":"24263ec4b92f390e07193d00e5a032ea2649f560027be02878da656e37d4a1b1","up":true},{"one":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","other":"e8d0bd04fa5b56fcd50cb4b1d550855c21192f1294f2954401348c8a28b455ab","up":true},{"one":"8ff601de9537c9e5d79a3707a312fe893383bef000d00acee3d4552068ef6030","other":"869fc21fff150ce9e1bc4512b183559433767cfc1cb7b6baae6d2a1313245ae8","up":true},{"one":"f9265e3c23cd931ad51faec142636a6a8c8bd90a23e77f5327861c81cf4dfe17","other":"8357eeaa65808f09b9a83362cb07508878becee9f843a5e1e70d025167b198ea","up":true},{"one":"6421894e25e421d16eccd8858aed3354fce2d04c9d5c46d4005ee1f16944ccfd","other":"9835fa9db1ce53ad2a526d36050eefd2c593121df4036c269cd09e1be4746a15","up":true},{"one":"58207fc8d92e44a8274398d6105b90d763981afba7021b57be98591e7b739272","other":"6421894e25e421d16eccd8858aed3354fce2d04c9d5c46d4005ee1f16944ccfd","up":true},{"one":"5f8eb6ff0f6ed720eaae86390757833f4b9cdfa52564457ed90c3de260ab6c30","other":"6b7da636e8eccac658c0abf6aa8ddd74be6d4cf9c5e0333bd28d9a6d1fe43ab3","up":true},{"one":"1030a5d27a502aeb868e7158373c48af6a85e829ee3a84f4a0b78f9a30bd5a6a","other":"766b61b773ad34c6cfd30b9c2e94840d151c31ab8cbcf546943eb5821d5a8f36","up":true},{"one":"a3e87b083776e34d10941832ec078fe9a7b8bae8cefe6fecdd5561549561a7f3","other":"adfde69b7633188d7c5a44e51e856d023c946d4cd8fd16bf539e6d1764dd7e04","up":true},{"one":"051c8d430b54d843feb325c2b56e36524566d64f48f6c770739748409af44364","other":"a6cad8da6a7949d7624b7ea636fd2d72de6a9b14f899df6263ccb4b3e5351c6a","up":true},{"one":"2c3d7e056f87daf540821a31ea8fe9377d00ed3a55ffba67b8bb716cdd9b7303","other":"24263ec4b92f390e07193d00e5a032ea2649f560027be02878da656e37d4a1b1","up":true},{"one":"c1c13885e3f543804e62e3b34c0d9762778866e5b8fe9d9df0caa6a64ec41428","other":"cd944ad4c50c4357005ff26ef26369ba3375a1205eec532b6e51b22bf25510bc","up":true},{"one":"1f1580f6bf3df2ce7a50ec53be2511d22149fede99ca6194b611471adabfafaa","other":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","up":true},{"one":"fe9d68b6f0d0add106d4a9e132308cf5caa867624784d652f071f5d1fe87ff8b","other":"e22c1f74c4b480fdd2fbe84ad321a98230efb98fb1cdec4f79e4e50b2b0d5cc4","up":true},{"one":"b77832f3f26323f09b37f31d208361d77ca08cec3ff829b9451fa3f00ff61fd0","other":"fd54f34d1deead333014c472182bf5cb2db1d1b1ab55caa70f5dc70108622f17","up":true},{"one":"351d1ed5c4012f8870ef2acadc7c0fea2cb697f5688b92e0150c5f670661c617","other":"30c022df9209b4515213fe3fa67135c05d960afbd76f8455caa9c83ab5e493b6","up":true},{"one":"0561b40886edc84497731c7b64acdabb63a22abe573847b78f74887afd86d71e","other":"78db863d7f60eda6a275cc9f4d1696cb32f5d46de747d278aa17ecd06c791135","up":true},{"one":"6c2fd129c8493cea7634a91aae559ceb64f01cebc45ec59aebd7e709b65941be","other":"6c29a1bc0f1025207b4524fadd7c8cd3dd956816fdfc57bb525c4ad3bcca9169","up":true},{"one":"13165fffc318dd5f182601384e1ac69dd591236a03e88dfd1c637ea21a01b2dd","other":"1030a5d27a502aeb868e7158373c48af6a85e829ee3a84f4a0b78f9a30bd5a6a","up":true},{"one":"d6e3d3ffd3858b8284bc9495b2d90b4b1124009a4a7d7394182977a8ae39df55","other":"fd54f34d1deead333014c472182bf5cb2db1d1b1ab55caa70f5dc70108622f17","up":true},{"one":"a93b2360b87425860185535aca8fbf3225e0c141d27690d60144689ae0f28534","other":"adfde69b7633188d7c5a44e51e856d023c946d4cd8fd16bf539e6d1764dd7e04","up":true},{"one":"0ef0bf53ddadd2c942f21e7e09d5a902d166f13920aabb2aa6652b70c423f575","other":"07c79aaa7fa759f797b88d8c495c5bfaa12f15289c6a567bbc363fd070227830","up":true},{"one":"b26eff0d882918a40b909b2fc79489a671a9f990cc0dd3d835493bcb52a468ea","other":"f9265e3c23cd931ad51faec142636a6a8c8bd90a23e77f5327861c81cf4dfe17","up":true},{"one":"212479d1ab813951780af2bc09a8119dbf62e603c05c0ea26329625036f6708a","other":"24263ec4b92f390e07193d00e5a032ea2649f560027be02878da656e37d4a1b1","up":true},{"one":"d6a203c341b618c47ae53b339563b22c31dfc12d89dea7ba0c33e1812829684c","other":"88a9b808cabfadb31c1a01b5a09eb4dc6cb7e011348f095920dd12e330ba9c0a","up":true},{"one":"1d531b5c8dd7bbbbf8c2db8f6984d2c9b23d5fee33876379e9d3e62a920e04b9","other":"1a696462de3d83ff271f30f6d66de968325e37a36877dd67b45d9cdd7e644b5b","up":true},{"one":"78db863d7f60eda6a275cc9f4d1696cb32f5d46de747d278aa17ecd06c791135","other":"7d14fd0b6c554f218249fed7709df51946baf91a3045ed99fb662c32cc97541b","up":true},{"one":"aff775d47ebe35fbaaad26c627470abb8481558cb5a08c42a44597a98408a919","other":"a6cad8da6a7949d7624b7ea636fd2d72de6a9b14f899df6263ccb4b3e5351c6a","up":true},{"one":"e22c1f74c4b480fdd2fbe84ad321a98230efb98fb1cdec4f79e4e50b2b0d5cc4","other":"f9265e3c23cd931ad51faec142636a6a8c8bd90a23e77f5327861c81cf4dfe17","up":true},{"one":"5958a4ccd5333e8bfed5f18c78d365b201ffc2b9af3afd31748adb8e4f8118bc","other":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","up":true},{"one":"ac3887308a4c3591293c43f5d5f312874b30e0dc19faa57626fdc6b830d3e433","other":"a6cad8da6a7949d7624b7ea636fd2d72de6a9b14f899df6263ccb4b3e5351c6a","up":true},{"one":"7c992ceefe03187bce1edbf8f80ccd1cab80b3908e625a61fef21ec6d35d04a3","other":"5958a4ccd5333e8bfed5f18c78d365b201ffc2b9af3afd31748adb8e4f8118bc","up":true},{"one":"6d307f348cc71e8a471ba662c1b5bd9d6e6930c22024f350465cccc7f2208a59","other":"6b1f3a9b65ce3a168fc98dee2b14b1de6912fd02ee2609b047b2c56f6c3b53e9","up":true},{"one":"4e6eab6eb3d49b20e2f3dee169d4647ebf26d07044698ff93d51c5fc30fb4ae0","other":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","up":true},{"one":"212479d1ab813951780af2bc09a8119dbf62e603c05c0ea26329625036f6708a","other":"a20d591ea15722712e83fcc6c17732f2370034ef870234a0fa0b2f4aea572fd5","up":true},{"one":"0f19fb4d0c6cfe36f7f59c8fe54d2e169687603e1bde0fe6b3a5e864fa51f2f0","other":"0104c3fa3f6b9237565759debed3f1fcebfa75efba7f01e81f06a53274cdf619","up":true},{"one":"66f6ff5fbc2526c1db5db491e49138362bef23032c6344ed4ead7dcb58bd1e6c","other":"869fc21fff150ce9e1bc4512b183559433767cfc1cb7b6baae6d2a1313245ae8","up":true},{"one":"0f19fb4d0c6cfe36f7f59c8fe54d2e169687603e1bde0fe6b3a5e864fa51f2f0","other":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","up":true},{"one":"58207fc8d92e44a8274398d6105b90d763981afba7021b57be98591e7b739272","other":"5f1c751641e1727871c44f0d5779b278871c77f5e05324dc91a8620bed72e627","up":true},{"one":"66f6ff5fbc2526c1db5db491e49138362bef23032c6344ed4ead7dcb58bd1e6c","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":true},{"one":"22b84df73059c92bf59fdad51fe574e027c7cc555dddbf363d745c1c422a89fd","other":"247dcb2c787abfa845b39cb9d7f9a6d59ea915006f250a4d760cdd8e16b3a5a6","up":true},{"one":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","other":"9d60b67d731aee2e6ac86d17c9e6a6aa5aaeec6b6803bae5c5fbc02643f7358c","up":true},{"one":"4610c06c8f5e1e24a75d9e46a432f4c1a6b9da6c3dbd122d3d3881752d930736","other":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","up":true},{"one":"bb9c9f07ee2f8bc2bb0f011309f8314de7c1777a6f683f90506b180ef6f5aab4","other":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","up":true},{"one":"00b98e790fb954975837ff95abb1bdc2673dd8a0ce315986cc0dda5b0cefb05d","other":"567206d70bc58993b08719afd9c007c562aad99437599356763b74cd4707083c","up":true},{"one":"a6cad8da6a7949d7624b7ea636fd2d72de6a9b14f899df6263ccb4b3e5351c6a","other":"247dcb2c787abfa845b39cb9d7f9a6d59ea915006f250a4d760cdd8e16b3a5a6","up":true},{"one":"795d813cab67300b8c59c78b6b99403c8111920ca47c1e56edfb513bc4999f3f","other":"78db863d7f60eda6a275cc9f4d1696cb32f5d46de747d278aa17ecd06c791135","up":true},{"one":"22b84df73059c92bf59fdad51fe574e027c7cc555dddbf363d745c1c422a89fd","other":"24263ec4b92f390e07193d00e5a032ea2649f560027be02878da656e37d4a1b1","up":true},{"one":"766b61b773ad34c6cfd30b9c2e94840d151c31ab8cbcf546943eb5821d5a8f36","other":"78db863d7f60eda6a275cc9f4d1696cb32f5d46de747d278aa17ecd06c791135","up":true},{"one":"a6cad8da6a7949d7624b7ea636fd2d72de6a9b14f899df6263ccb4b3e5351c6a","other":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","up":true},{"one":"00b98e790fb954975837ff95abb1bdc2673dd8a0ce315986cc0dda5b0cefb05d","other":"5258fdc94d78bb45d6634f2672bc2eacdbc896fc5a39784ecc9d8a7dd52cb1e7","up":true},{"one":"41f407b92b1462b93400819a6af8815797b9dbc505afaf0162bbaea795205716","other":"9d60b67d731aee2e6ac86d17c9e6a6aa5aaeec6b6803bae5c5fbc02643f7358c","up":true},{"one":"795d813cab67300b8c59c78b6b99403c8111920ca47c1e56edfb513bc4999f3f","other":"6b1f3a9b65ce3a168fc98dee2b14b1de6912fd02ee2609b047b2c56f6c3b53e9","up":true},{"one":"4067997c26042749c16f17169caac66e647b71e3557f7581dbec739d0baa2ef0","other":"4e6eab6eb3d49b20e2f3dee169d4647ebf26d07044698ff93d51c5fc30fb4ae0","up":true},{"one":"766b61b773ad34c6cfd30b9c2e94840d151c31ab8cbcf546943eb5821d5a8f36","other":"6b1f3a9b65ce3a168fc98dee2b14b1de6912fd02ee2609b047b2c56f6c3b53e9","up":true},{"one":"247dcb2c787abfa845b39cb9d7f9a6d59ea915006f250a4d760cdd8e16b3a5a6","other":"2c3d7e056f87daf540821a31ea8fe9377d00ed3a55ffba67b8bb716cdd9b7303","up":true},{"one":"4067997c26042749c16f17169caac66e647b71e3557f7581dbec739d0baa2ef0","other":"4f7abbc51265dfe22d58f3ac5cde2c05ec56053efe79e3ebb6ece96183dcc17a","up":true},{"one":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","other":"adfde69b7633188d7c5a44e51e856d023c946d4cd8fd16bf539e6d1764dd7e04","up":true},{"one":"9b240fbf12d9de7975803b09aacdbab94cd978130de62bc89497bb1056e462c0","other":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","up":true},{"one":"4f7abbc51265dfe22d58f3ac5cde2c05ec56053efe79e3ebb6ece96183dcc17a","other":"41cd903abdaf445bfbcc3d5f28c10aba473be8a3ddc614c7f124b0ed7f91fd1d","up":true},{"one":"07c79aaa7fa759f797b88d8c495c5bfaa12f15289c6a567bbc363fd070227830","other":"016e174a29dc18857107fbebc876689b401c1174964ac6cacb31a4cb847a4d30","up":true},{"one":"9b240fbf12d9de7975803b09aacdbab94cd978130de62bc89497bb1056e462c0","other":"d48642f8008e8aa791ba89c9b3a5b8d2d7aeb7e9078caee8813777679608075c","up":true},{"one":"5f1c751641e1727871c44f0d5779b278871c77f5e05324dc91a8620bed72e627","other":"41cd903abdaf445bfbcc3d5f28c10aba473be8a3ddc614c7f124b0ed7f91fd1d","up":true},{"one":"e8d0bd04fa5b56fcd50cb4b1d550855c21192f1294f2954401348c8a28b455ab","other":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","up":true},{"one":"6ad608f1087b8af40359db45e7d78e43c3465fdae5cc01a9a9ab6c149cb6fdf3","other":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","up":true},{"one":"5f1c751641e1727871c44f0d5779b278871c77f5e05324dc91a8620bed72e627","other":"6d307f348cc71e8a471ba662c1b5bd9d6e6930c22024f350465cccc7f2208a59","up":true},{"one":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","other":"b270e0d21a173f4717882b1c6dc942d703fe11cd6adf997b3ecd0944fcea6c7b","up":true},{"one":"30c022df9209b4515213fe3fa67135c05d960afbd76f8455caa9c83ab5e493b6","other":"1a16e158f1d35f2573d20b05ba51a2c9cc196547038e3b7be97ddc7598e81257","up":true},{"one":"07c79aaa7fa759f797b88d8c495c5bfaa12f15289c6a567bbc363fd070227830","other":"0d90040b9d0cd742149654262198aa8dc4fc96cfdf9b84cfe833277c3e23831f","up":true},{"one":"41cd903abdaf445bfbcc3d5f28c10aba473be8a3ddc614c7f124b0ed7f91fd1d","other":"5258fdc94d78bb45d6634f2672bc2eacdbc896fc5a39784ecc9d8a7dd52cb1e7","up":true},{"one":"6ad608f1087b8af40359db45e7d78e43c3465fdae5cc01a9a9ab6c149cb6fdf3","other":"6421894e25e421d16eccd8858aed3354fce2d04c9d5c46d4005ee1f16944ccfd","up":true},{"one":"0066352177d6c225842362ab424a632f569980b02ac55a13a71593af94cb8c2d","other":"1b6168d6a369029690e836d26ffe017e26769ce504c142abf2a4a65774ecb66d","up":true},{"one":"351d1ed5c4012f8870ef2acadc7c0fea2cb697f5688b92e0150c5f670661c617","other":"5258fdc94d78bb45d6634f2672bc2eacdbc896fc5a39784ecc9d8a7dd52cb1e7","up":true},{"one":"30c022df9209b4515213fe3fa67135c05d960afbd76f8455caa9c83ab5e493b6","other":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","up":true},{"one":"4a2d15b867a620653f099d66839b80c2dd32aec95caf906f0d5c8146192edf62","other":"74a4ffb0d717a523ec72e07cc5efe3e98d55932d6e0592ef45fe54b378e4e952","up":true},{"one":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","other":"cd944ad4c50c4357005ff26ef26369ba3375a1205eec532b6e51b22bf25510bc","up":true},{"one":"a3e87b083776e34d10941832ec078fe9a7b8bae8cefe6fecdd5561549561a7f3","other":"a20d591ea15722712e83fcc6c17732f2370034ef870234a0fa0b2f4aea572fd5","up":true},{"one":"f9e4dbb28099af8ca5f28c641a51672f4c6ab54b7037f765a7172a399acb8edc","other":"fe9d68b6f0d0add106d4a9e132308cf5caa867624784d652f071f5d1fe87ff8b","up":true},{"one":"b270e0d21a173f4717882b1c6dc942d703fe11cd6adf997b3ecd0944fcea6c7b","other":"d48642f8008e8aa791ba89c9b3a5b8d2d7aeb7e9078caee8813777679608075c","up":true},{"one":"1030a5d27a502aeb868e7158373c48af6a85e829ee3a84f4a0b78f9a30bd5a6a","other":"2c3d7e056f87daf540821a31ea8fe9377d00ed3a55ffba67b8bb716cdd9b7303","up":true},{"one":"893773caa8e126430f189186956f3e80b49107ce52777972f5e8c11027c072d6","other":"960eb6f745966f47aadb13d488aee1187486c99e6bc78c679a6d9b3d421d8eb0","up":true},{"one":"41cd903abdaf445bfbcc3d5f28c10aba473be8a3ddc614c7f124b0ed7f91fd1d","other":"6ad608f1087b8af40359db45e7d78e43c3465fdae5cc01a9a9ab6c149cb6fdf3","up":true},{"one":"1f1580f6bf3df2ce7a50ec53be2511d22149fede99ca6194b611471adabfafaa","other":"1a696462de3d83ff271f30f6d66de968325e37a36877dd67b45d9cdd7e644b5b","up":true},{"one":"d6e3d3ffd3858b8284bc9495b2d90b4b1124009a4a7d7394182977a8ae39df55","other":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","up":true},{"one":"fb211f405a7dc3a07b6d4f18522c6273ffa4e18325958eea159e33f768eae846","other":"ede2568d60ce7a12b4380818a31d2895fcb9b815cfb5526d28b2a82ff62e0381","up":true},{"one":"816609d5b9bdb0c7bad6cbe9400fd506df8662b14ad0e0a61bc7b49482548e39","other":"88dc5f31a2b295e8b43bc6e7abaae130061c2d44c49c95bcca3d9dd0a268324c","up":true},{"one":"567206d70bc58993b08719afd9c007c562aad99437599356763b74cd4707083c","other":"5258fdc94d78bb45d6634f2672bc2eacdbc896fc5a39784ecc9d8a7dd52cb1e7","up":true},{"one":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","other":"b26eff0d882918a40b909b2fc79489a671a9f990cc0dd3d835493bcb52a468ea","up":false},{"one":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","other":"20c4c3a3523960ad74538e726e1caaeda987cbc16dcd3beeb63fca6eebd31405","up":false},{"one":"0066352177d6c225842362ab424a632f569980b02ac55a13a71593af94cb8c2d","other":"1a16e158f1d35f2573d20b05ba51a2c9cc196547038e3b7be97ddc7598e81257","up":true},{"one":"8ff601de9537c9e5d79a3707a312fe893383bef000d00acee3d4552068ef6030","other":"d48642f8008e8aa791ba89c9b3a5b8d2d7aeb7e9078caee8813777679608075c","up":true},{"one":"85845413850f5e38cce69548e20179b477bf383dd2b22428358715d9d72421ac","other":"b77832f3f26323f09b37f31d208361d77ca08cec3ff829b9451fa3f00ff61fd0","up":true},{"one":"60cbf513366da3f6ee288d3572dc3971ae256b02e5492595e63c31a7b1b0d5a8","other":"66f6ff5fbc2526c1db5db491e49138362bef23032c6344ed4ead7dcb58bd1e6c","up":true},{"one":"11b53fa40926ac84d75d9d698f46b9611b90194857c1397e117931a890447078","other":"07c79aaa7fa759f797b88d8c495c5bfaa12f15289c6a567bbc363fd070227830","up":true},{"one":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","other":"1a16e158f1d35f2573d20b05ba51a2c9cc196547038e3b7be97ddc7598e81257","up":true},{"one":"60cbf513366da3f6ee288d3572dc3971ae256b02e5492595e63c31a7b1b0d5a8","other":"6b1f3a9b65ce3a168fc98dee2b14b1de6912fd02ee2609b047b2c56f6c3b53e9","up":true},{"one":"88dc5f31a2b295e8b43bc6e7abaae130061c2d44c49c95bcca3d9dd0a268324c","other":"adfde69b7633188d7c5a44e51e856d023c946d4cd8fd16bf539e6d1764dd7e04","up":true},{"one":"11b53fa40926ac84d75d9d698f46b9611b90194857c1397e117931a890447078","other":"0066352177d6c225842362ab424a632f569980b02ac55a13a71593af94cb8c2d","up":true},{"one":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","other":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","up":true},{"one":"7d14fd0b6c554f218249fed7709df51946baf91a3045ed99fb662c32cc97541b","other":"5958a4ccd5333e8bfed5f18c78d365b201ffc2b9af3afd31748adb8e4f8118bc","up":true},{"one":"960eb6f745966f47aadb13d488aee1187486c99e6bc78c679a6d9b3d421d8eb0","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":true},{"one":"960eb6f745966f47aadb13d488aee1187486c99e6bc78c679a6d9b3d421d8eb0","other":"fd54f34d1deead333014c472182bf5cb2db1d1b1ab55caa70f5dc70108622f17","up":true},{"one":"7d14fd0b6c554f218249fed7709df51946baf91a3045ed99fb662c32cc97541b","other":"58207fc8d92e44a8274398d6105b90d763981afba7021b57be98591e7b739272","up":true},{"one":"cb704e26ceff5b613d8211423375fe0ded4d867bc892894a70d71289f1a9c1c1","other":"cd944ad4c50c4357005ff26ef26369ba3375a1205eec532b6e51b22bf25510bc","up":true},{"one":"f84317ef488d8c1495b429b7a1fa21ea53a73e737b3c1aa890c20e38658ab148","other":"bb9c9f07ee2f8bc2bb0f011309f8314de7c1777a6f683f90506b180ef6f5aab4","up":true},{"one":"f84317ef488d8c1495b429b7a1fa21ea53a73e737b3c1aa890c20e38658ab148","other":"9a8c60732ff0d0b7403b181cdc5bb9e6db0aa4cf5c7158c40e548fcb76726f87","up":true},{"one":"7c992ceefe03187bce1edbf8f80ccd1cab80b3908e625a61fef21ec6d35d04a3","other":"58207fc8d92e44a8274398d6105b90d763981afba7021b57be98591e7b739272","up":true},{"one":"11b53fa40926ac84d75d9d698f46b9611b90194857c1397e117931a890447078","other":"1263f18bf8cb4f5a3335f545cdd5d42f1b84da9edf4fa7346572c2bc38c87eb3","up":true},{"one":"60cbf513366da3f6ee288d3572dc3971ae256b02e5492595e63c31a7b1b0d5a8","other":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","up":false},{"one":"a20d591ea15722712e83fcc6c17732f2370034ef870234a0fa0b2f4aea572fd5","other":"ac3887308a4c3591293c43f5d5f312874b30e0dc19faa57626fdc6b830d3e433","up":true},{"one":"9d60b67d731aee2e6ac86d17c9e6a6aa5aaeec6b6803bae5c5fbc02643f7358c","other":"8357eeaa65808f09b9a83362cb07508878becee9f843a5e1e70d025167b198ea","up":true},{"one":"160c0401bcd1b38db7b4ae82470244ac9a9923755fc475aa67eb6dedc703c223","other":"1d531b5c8dd7bbbbf8c2db8f6984d2c9b23d5fee33876379e9d3e62a920e04b9","up":true},{"one":"ede2568d60ce7a12b4380818a31d2895fcb9b815cfb5526d28b2a82ff62e0381","other":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","up":false},{"one":"7c992ceefe03187bce1edbf8f80ccd1cab80b3908e625a61fef21ec6d35d04a3","other":"580a650c2c3e95d35e66264df6f48e28a091bacd20dc2c9828a5b563353d2052","up":true},{"one":"9835fa9db1ce53ad2a526d36050eefd2c593121df4036c269cd09e1be4746a15","other":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","up":true},{"one":"1f1580f6bf3df2ce7a50ec53be2511d22149fede99ca6194b611471adabfafaa","other":"18b0fcbba2350241088129e8f6a07742857c3edaacd6ef1ec0861cc8e48492ce","up":true},{"one":"9d60b67d731aee2e6ac86d17c9e6a6aa5aaeec6b6803bae5c5fbc02643f7358c","other":"bb9c9f07ee2f8bc2bb0f011309f8314de7c1777a6f683f90506b180ef6f5aab4","up":true},{"one":"960eb6f745966f47aadb13d488aee1187486c99e6bc78c679a6d9b3d421d8eb0","other":"fcf3ccf76c4699752670103e9cb98ac1294339a357876c71673f7e486d02f08b","up":true},{"one":"adfde69b7633188d7c5a44e51e856d023c946d4cd8fd16bf539e6d1764dd7e04","other":"d48642f8008e8aa791ba89c9b3a5b8d2d7aeb7e9078caee8813777679608075c","up":false},{"one":"160c0401bcd1b38db7b4ae82470244ac9a9923755fc475aa67eb6dedc703c223","other":"4e6eab6eb3d49b20e2f3dee169d4647ebf26d07044698ff93d51c5fc30fb4ae0","up":true},{"one":"ede2568d60ce7a12b4380818a31d2895fcb9b815cfb5526d28b2a82ff62e0381","other":"d6a203c341b618c47ae53b339563b22c31dfc12d89dea7ba0c33e1812829684c","up":true},{"one":"fcf3ccf76c4699752670103e9cb98ac1294339a357876c71673f7e486d02f08b","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":false},{"one":"893773caa8e126430f189186956f3e80b49107ce52777972f5e8c11027c072d6","other":"8ff601de9537c9e5d79a3707a312fe893383bef000d00acee3d4552068ef6030","up":true},{"one":"6c29a1bc0f1025207b4524fadd7c8cd3dd956816fdfc57bb525c4ad3bcca9169","other":"1a16e158f1d35f2573d20b05ba51a2c9cc196547038e3b7be97ddc7598e81257","up":true},{"one":"13165fffc318dd5f182601384e1ac69dd591236a03e88dfd1c637ea21a01b2dd","other":"1263f18bf8cb4f5a3335f545cdd5d42f1b84da9edf4fa7346572c2bc38c87eb3","up":true},{"one":"0104c3fa3f6b9237565759debed3f1fcebfa75efba7f01e81f06a53274cdf619","other":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","up":true},{"one":"f9265e3c23cd931ad51faec142636a6a8c8bd90a23e77f5327861c81cf4dfe17","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":true},{"one":"9835fa9db1ce53ad2a526d36050eefd2c593121df4036c269cd09e1be4746a15","other":"0d90040b9d0cd742149654262198aa8dc4fc96cfdf9b84cfe833277c3e23831f","up":true},{"one":"0ef0bf53ddadd2c942f21e7e09d5a902d166f13920aabb2aa6652b70c423f575","other":"0f19fb4d0c6cfe36f7f59c8fe54d2e169687603e1bde0fe6b3a5e864fa51f2f0","up":true},{"one":"1030a5d27a502aeb868e7158373c48af6a85e829ee3a84f4a0b78f9a30bd5a6a","other":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","up":true},{"one":"944e8231f9f661435f1f94abfaa17862a005877487df5c5d3a566c4dbe46be3f","other":"960eb6f745966f47aadb13d488aee1187486c99e6bc78c679a6d9b3d421d8eb0","up":true},{"one":"8357eeaa65808f09b9a83362cb07508878becee9f843a5e1e70d025167b198ea","other":"adfde69b7633188d7c5a44e51e856d023c946d4cd8fd16bf539e6d1764dd7e04","up":true},{"one":"7a46bf03f327b100dc00140c97828a3b607280b6cd57220703be393ae4fdddbc","other":"5258fdc94d78bb45d6634f2672bc2eacdbc896fc5a39784ecc9d8a7dd52cb1e7","up":true},{"one":"a3e87b083776e34d10941832ec078fe9a7b8bae8cefe6fecdd5561549561a7f3","other":"a6cad8da6a7949d7624b7ea636fd2d72de6a9b14f899df6263ccb4b3e5351c6a","up":false},{"one":"0104c3fa3f6b9237565759debed3f1fcebfa75efba7f01e81f06a53274cdf619","other":"30c022df9209b4515213fe3fa67135c05d960afbd76f8455caa9c83ab5e493b6","up":true},{"one":"ce12d7b26736d08eaa424a5f972a5a1fd50d05aab330340f3605dbab8007bf28","other":"bb9c9f07ee2f8bc2bb0f011309f8314de7c1777a6f683f90506b180ef6f5aab4","up":true},{"one":"adfde69b7633188d7c5a44e51e856d023c946d4cd8fd16bf539e6d1764dd7e04","other":"d6a203c341b618c47ae53b339563b22c31dfc12d89dea7ba0c33e1812829684c","up":true},{"one":"0ef0bf53ddadd2c942f21e7e09d5a902d166f13920aabb2aa6652b70c423f575","other":"00b98e790fb954975837ff95abb1bdc2673dd8a0ce315986cc0dda5b0cefb05d","up":true},{"one":"ede2568d60ce7a12b4380818a31d2895fcb9b815cfb5526d28b2a82ff62e0381","other":"d48642f8008e8aa791ba89c9b3a5b8d2d7aeb7e9078caee8813777679608075c","up":false},{"one":"e884bd666b0215cfe88ee19457c67c747fb1c6815855160f1c243c149b24e923","other":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","up":true},{"one":"7a46bf03f327b100dc00140c97828a3b607280b6cd57220703be393ae4fdddbc","other":"5f1c751641e1727871c44f0d5779b278871c77f5e05324dc91a8620bed72e627","up":true},{"one":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","other":"6421894e25e421d16eccd8858aed3354fce2d04c9d5c46d4005ee1f16944ccfd","up":true},{"one":"0104c3fa3f6b9237565759debed3f1fcebfa75efba7f01e81f06a53274cdf619","other":"24263ec4b92f390e07193d00e5a032ea2649f560027be02878da656e37d4a1b1","up":true},{"one":"016e174a29dc18857107fbebc876689b401c1174964ac6cacb31a4cb847a4d30","other":"e884bd666b0215cfe88ee19457c67c747fb1c6815855160f1c243c149b24e923","up":true},{"one":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","other":"30c022df9209b4515213fe3fa67135c05d960afbd76f8455caa9c83ab5e493b6","up":true},{"one":"089fe22a0bd48d3120cc8171eb9d698532c17f36768dbc25f554f2c53de11ab0","other":"c6edfd613f216f48a1ea05be84faa5b9a69dd97c6e5d72e0bd339ea62c1b19e1","up":true},{"one":"c6edfd613f216f48a1ea05be84faa5b9a69dd97c6e5d72e0bd339ea62c1b19e1","other":"c1c13885e3f543804e62e3b34c0d9762778866e5b8fe9d9df0caa6a64ec41428","up":true},{"one":"1a696462de3d83ff271f30f6d66de968325e37a36877dd67b45d9cdd7e644b5b","other":"6b1f3a9b65ce3a168fc98dee2b14b1de6912fd02ee2609b047b2c56f6c3b53e9","up":true},{"one":"089fe22a0bd48d3120cc8171eb9d698532c17f36768dbc25f554f2c53de11ab0","other":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","up":true},{"one":"8357eeaa65808f09b9a83362cb07508878becee9f843a5e1e70d025167b198ea","other":"b26eff0d882918a40b909b2fc79489a671a9f990cc0dd3d835493bcb52a468ea","up":true},{"one":"2742110997dcb690946885e5a4aaa039f72c9f92025e62f1d371922acf5e27a7","other":"22b84df73059c92bf59fdad51fe574e027c7cc555dddbf363d745c1c422a89fd","up":true},{"one":"adfde69b7633188d7c5a44e51e856d023c946d4cd8fd16bf539e6d1764dd7e04","other":"fd54f34d1deead333014c472182bf5cb2db1d1b1ab55caa70f5dc70108622f17","up":false},{"one":"6c29a1bc0f1025207b4524fadd7c8cd3dd956816fdfc57bb525c4ad3bcca9169","other":"1b6168d6a369029690e836d26ffe017e26769ce504c142abf2a4a65774ecb66d","up":true},{"one":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","other":"66f6ff5fbc2526c1db5db491e49138362bef23032c6344ed4ead7dcb58bd1e6c","up":true},{"one":"69baf7ee43b360a3238ad3b175e0ad9c24967dae60c7e91d1ca7ee55167b2bdb","other":"567206d70bc58993b08719afd9c007c562aad99437599356763b74cd4707083c","up":true},{"one":"24263ec4b92f390e07193d00e5a032ea2649f560027be02878da656e37d4a1b1","other":"d6a203c341b618c47ae53b339563b22c31dfc12d89dea7ba0c33e1812829684c","up":true},{"one":"6c29a1bc0f1025207b4524fadd7c8cd3dd956816fdfc57bb525c4ad3bcca9169","other":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","up":true},{"one":"1b6168d6a369029690e836d26ffe017e26769ce504c142abf2a4a65774ecb66d","other":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","up":true},{"one":"1a696462de3d83ff271f30f6d66de968325e37a36877dd67b45d9cdd7e644b5b","other":"6b7da636e8eccac658c0abf6aa8ddd74be6d4cf9c5e0333bd28d9a6d1fe43ab3","up":true},{"one":"18b0fcbba2350241088129e8f6a07742857c3edaacd6ef1ec0861cc8e48492ce","other":"1a16e158f1d35f2573d20b05ba51a2c9cc196547038e3b7be97ddc7598e81257","up":true},{"one":"69baf7ee43b360a3238ad3b175e0ad9c24967dae60c7e91d1ca7ee55167b2bdb","other":"580a650c2c3e95d35e66264df6f48e28a091bacd20dc2c9828a5b563353d2052","up":true},{"one":"74a4ffb0d717a523ec72e07cc5efe3e98d55932d6e0592ef45fe54b378e4e952","other":"fb211f405a7dc3a07b6d4f18522c6273ffa4e18325958eea159e33f768eae846","up":true},{"one":"24263ec4b92f390e07193d00e5a032ea2649f560027be02878da656e37d4a1b1","other":"e8d0bd04fa5b56fcd50cb4b1d550855c21192f1294f2954401348c8a28b455ab","up":true},{"one":"ce12d7b26736d08eaa424a5f972a5a1fd50d05aab330340f3605dbab8007bf28","other":"893773caa8e126430f189186956f3e80b49107ce52777972f5e8c11027c072d6","up":true},{"one":"2742110997dcb690946885e5a4aaa039f72c9f92025e62f1d371922acf5e27a7","other":"6421894e25e421d16eccd8858aed3354fce2d04c9d5c46d4005ee1f16944ccfd","up":true},{"one":"869fc21fff150ce9e1bc4512b183559433767cfc1cb7b6baae6d2a1313245ae8","other":"8357eeaa65808f09b9a83362cb07508878becee9f843a5e1e70d025167b198ea","up":false},{"one":"18b0fcbba2350241088129e8f6a07742857c3edaacd6ef1ec0861cc8e48492ce","other":"1d531b5c8dd7bbbbf8c2db8f6984d2c9b23d5fee33876379e9d3e62a920e04b9","up":true},{"one":"4f7abbc51265dfe22d58f3ac5cde2c05ec56053efe79e3ebb6ece96183dcc17a","other":"43d797cb7fced78dd7141ef452ea367d7484d5c0fec2431caebe1317c4aa6340","up":true},{"one":"1b6168d6a369029690e836d26ffe017e26769ce504c142abf2a4a65774ecb66d","other":"1030a5d27a502aeb868e7158373c48af6a85e829ee3a84f4a0b78f9a30bd5a6a","up":true},{"one":"051c8d430b54d843feb325c2b56e36524566d64f48f6c770739748409af44364","other":"bb9c9f07ee2f8bc2bb0f011309f8314de7c1777a6f683f90506b180ef6f5aab4","up":false},{"one":"66f6ff5fbc2526c1db5db491e49138362bef23032c6344ed4ead7dcb58bd1e6c","other":"5f8eb6ff0f6ed720eaae86390757833f4b9cdfa52564457ed90c3de260ab6c30","up":true},{"one":"1263f18bf8cb4f5a3335f545cdd5d42f1b84da9edf4fa7346572c2bc38c87eb3","other":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","up":true},{"one":"0f19fb4d0c6cfe36f7f59c8fe54d2e169687603e1bde0fe6b3a5e864fa51f2f0","other":"2c3d7e056f87daf540821a31ea8fe9377d00ed3a55ffba67b8bb716cdd9b7303","up":true},{"one":"74a4ffb0d717a523ec72e07cc5efe3e98d55932d6e0592ef45fe54b378e4e952","other":"f9e4dbb28099af8ca5f28c641a51672f4c6ab54b7037f765a7172a399acb8edc","up":true},{"one":"776fdbabbca42ecb49cc5451b860305ec3020889b47adaa2add6ea359afe379f","other":"766b61b773ad34c6cfd30b9c2e94840d151c31ab8cbcf546943eb5821d5a8f36","up":true},{"one":"6421894e25e421d16eccd8858aed3354fce2d04c9d5c46d4005ee1f16944ccfd","other":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","up":false},{"one":"1a16e158f1d35f2573d20b05ba51a2c9cc196547038e3b7be97ddc7598e81257","other":"1f1580f6bf3df2ce7a50ec53be2511d22149fede99ca6194b611471adabfafaa","up":false},{"one":"22b84df73059c92bf59fdad51fe574e027c7cc555dddbf363d745c1c422a89fd","other":"cd944ad4c50c4357005ff26ef26369ba3375a1205eec532b6e51b22bf25510bc","up":true},{"one":"81748a1a38162f9e8613ffa562add1b75893b66199840e74c0774db92bfd7ca0","other":"b77832f3f26323f09b37f31d208361d77ca08cec3ff829b9451fa3f00ff61fd0","up":false},{"one":"2c3d7e056f87daf540821a31ea8fe9377d00ed3a55ffba67b8bb716cdd9b7303","other":"c6edfd613f216f48a1ea05be84faa5b9a69dd97c6e5d72e0bd339ea62c1b19e1","up":true},{"one":"1263f18bf8cb4f5a3335f545cdd5d42f1b84da9edf4fa7346572c2bc38c87eb3","other":"cd944ad4c50c4357005ff26ef26369ba3375a1205eec532b6e51b22bf25510bc","up":true},{"one":"11b53fa40926ac84d75d9d698f46b9611b90194857c1397e117931a890447078","other":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","up":false},{"one":"051c8d430b54d843feb325c2b56e36524566d64f48f6c770739748409af44364","other":"adfde69b7633188d7c5a44e51e856d023c946d4cd8fd16bf539e6d1764dd7e04","up":false},{"one":"776fdbabbca42ecb49cc5451b860305ec3020889b47adaa2add6ea359afe379f","other":"d48642f8008e8aa791ba89c9b3a5b8d2d7aeb7e9078caee8813777679608075c","up":true},{"one":"22b84df73059c92bf59fdad51fe574e027c7cc555dddbf363d745c1c422a89fd","other":"e22c1f74c4b480fdd2fbe84ad321a98230efb98fb1cdec4f79e4e50b2b0d5cc4","up":false},{"one":"051c8d430b54d843feb325c2b56e36524566d64f48f6c770739748409af44364","other":"8357eeaa65808f09b9a83362cb07508878becee9f843a5e1e70d025167b198ea","up":false},{"one":"2c3d7e056f87daf540821a31ea8fe9377d00ed3a55ffba67b8bb716cdd9b7303","other":"e22c1f74c4b480fdd2fbe84ad321a98230efb98fb1cdec4f79e4e50b2b0d5cc4","up":false},{"one":"776fdbabbca42ecb49cc5451b860305ec3020889b47adaa2add6ea359afe379f","other":"c42f36bf6728b24721ce5ac1272a1058e202a236d276af1265a1eaa675f8fd85","up":true},{"one":"e22c1f74c4b480fdd2fbe84ad321a98230efb98fb1cdec4f79e4e50b2b0d5cc4","other":"ede2568d60ce7a12b4380818a31d2895fcb9b815cfb5526d28b2a82ff62e0381","up":false},{"one":"f84317ef488d8c1495b429b7a1fa21ea53a73e737b3c1aa890c20e38658ab148","other":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","up":true},{"one":"22b84df73059c92bf59fdad51fe574e027c7cc555dddbf363d745c1c422a89fd","other":"9a8c60732ff0d0b7403b181cdc5bb9e6db0aa4cf5c7158c40e548fcb76726f87","up":false},{"one":"2c3d7e056f87daf540821a31ea8fe9377d00ed3a55ffba67b8bb716cdd9b7303","other":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","up":false},{"one":"18b0fcbba2350241088129e8f6a07742857c3edaacd6ef1ec0861cc8e48492ce","other":"5f8eb6ff0f6ed720eaae86390757833f4b9cdfa52564457ed90c3de260ab6c30","up":true},{"one":"5958a4ccd5333e8bfed5f18c78d365b201ffc2b9af3afd31748adb8e4f8118bc","other":"1d531b5c8dd7bbbbf8c2db8f6984d2c9b23d5fee33876379e9d3e62a920e04b9","up":true},{"one":"66f6ff5fbc2526c1db5db491e49138362bef23032c6344ed4ead7dcb58bd1e6c","other":"5958a4ccd5333e8bfed5f18c78d365b201ffc2b9af3afd31748adb8e4f8118bc","up":true},{"one":"78db863d7f60eda6a275cc9f4d1696cb32f5d46de747d278aa17ecd06c791135","other":"4610c06c8f5e1e24a75d9e46a432f4c1a6b9da6c3dbd122d3d3881752d930736","up":true},{"one":"81748a1a38162f9e8613ffa562add1b75893b66199840e74c0774db92bfd7ca0","other":"b270e0d21a173f4717882b1c6dc942d703fe11cd6adf997b3ecd0944fcea6c7b","up":true},{"one":"ac3887308a4c3591293c43f5d5f312874b30e0dc19faa57626fdc6b830d3e433","other":"a3e87b083776e34d10941832ec078fe9a7b8bae8cefe6fecdd5561549561a7f3","up":true},{"one":"6421894e25e421d16eccd8858aed3354fce2d04c9d5c46d4005ee1f16944ccfd","other":"776fdbabbca42ecb49cc5451b860305ec3020889b47adaa2add6ea359afe379f","up":false},{"one":"c42f36bf6728b24721ce5ac1272a1058e202a236d276af1265a1eaa675f8fd85","other":"c1c13885e3f543804e62e3b34c0d9762778866e5b8fe9d9df0caa6a64ec41428","up":false},{"one":"b87c0ee719d0656cd325300b756f203310675021f3c508b30542deb1114ec7ac","other":"b270e0d21a173f4717882b1c6dc942d703fe11cd6adf997b3ecd0944fcea6c7b","up":true},{"one":"4f7abbc51265dfe22d58f3ac5cde2c05ec56053efe79e3ebb6ece96183dcc17a","other":"a6cad8da6a7949d7624b7ea636fd2d72de6a9b14f899df6263ccb4b3e5351c6a","up":true},{"one":"1263f18bf8cb4f5a3335f545cdd5d42f1b84da9edf4fa7346572c2bc38c87eb3","other":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","up":true},{"one":"1d531b5c8dd7bbbbf8c2db8f6984d2c9b23d5fee33876379e9d3e62a920e04b9","other":"1a16e158f1d35f2573d20b05ba51a2c9cc196547038e3b7be97ddc7598e81257","up":false},{"one":"5958a4ccd5333e8bfed5f18c78d365b201ffc2b9af3afd31748adb8e4f8118bc","other":"089fe22a0bd48d3120cc8171eb9d698532c17f36768dbc25f554f2c53de11ab0","up":true},{"one":"78db863d7f60eda6a275cc9f4d1696cb32f5d46de747d278aa17ecd06c791135","other":"459a945c8d6e67437bdfa0e067962b3591dfa0aa7c1177f8327ba1557bbf1569","up":false},{"one":"4610c06c8f5e1e24a75d9e46a432f4c1a6b9da6c3dbd122d3d3881752d930736","other":"1030a5d27a502aeb868e7158373c48af6a85e829ee3a84f4a0b78f9a30bd5a6a","up":true},{"one":"1d531b5c8dd7bbbbf8c2db8f6984d2c9b23d5fee33876379e9d3e62a920e04b9","other":"1b6168d6a369029690e836d26ffe017e26769ce504c142abf2a4a65774ecb66d","up":true},{"one":"960eb6f745966f47aadb13d488aee1187486c99e6bc78c679a6d9b3d421d8eb0","other":"9835fa9db1ce53ad2a526d36050eefd2c593121df4036c269cd09e1be4746a15","up":true},{"one":"ef40fdb37f2f89ec77833e48b1bd210b2241949b9ef8f6dca6bf6189503abb2c","other":"e884bd666b0215cfe88ee19457c67c747fb1c6815855160f1c243c149b24e923","up":false},{"one":"160c0401bcd1b38db7b4ae82470244ac9a9923755fc475aa67eb6dedc703c223","other":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","up":false},{"one":"bb9c9f07ee2f8bc2bb0f011309f8314de7c1777a6f683f90506b180ef6f5aab4","other":"b270e0d21a173f4717882b1c6dc942d703fe11cd6adf997b3ecd0944fcea6c7b","up":true},{"one":"351d1ed5c4012f8870ef2acadc7c0fea2cb697f5688b92e0150c5f670661c617","other":"39b5d09d1227a776f8d65b7239cd03e04499e7d519e5d1ff4c4870a1a949dd91","up":true},{"one":"6d307f348cc71e8a471ba662c1b5bd9d6e6930c22024f350465cccc7f2208a59","other":"6b7da636e8eccac658c0abf6aa8ddd74be6d4cf9c5e0333bd28d9a6d1fe43ab3","up":false},{"one":"b77832f3f26323f09b37f31d208361d77ca08cec3ff829b9451fa3f00ff61fd0","other":"8357eeaa65808f09b9a83362cb07508878becee9f843a5e1e70d025167b198ea","up":false},{"one":"fe9d68b6f0d0add106d4a9e132308cf5caa867624784d652f071f5d1fe87ff8b","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":true},{"one":"9d60b67d731aee2e6ac86d17c9e6a6aa5aaeec6b6803bae5c5fbc02643f7358c","other":"9265383ecf776ce1da66e99e5f0a9c1af593d8e56ceef21f42aad4be7e34bc45","up":true},{"one":"b87c0ee719d0656cd325300b756f203310675021f3c508b30542deb1114ec7ac","other":"e22c1f74c4b480fdd2fbe84ad321a98230efb98fb1cdec4f79e4e50b2b0d5cc4","up":false},{"one":"1b6168d6a369029690e836d26ffe017e26769ce504c142abf2a4a65774ecb66d","other":"18b0fcbba2350241088129e8f6a07742857c3edaacd6ef1ec0861cc8e48492ce","up":false},{"one":"ede2568d60ce7a12b4380818a31d2895fcb9b815cfb5526d28b2a82ff62e0381","other":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","up":true},{"one":"43d797cb7fced78dd7141ef452ea367d7484d5c0fec2431caebe1317c4aa6340","other":"41cd903abdaf445bfbcc3d5f28c10aba473be8a3ddc614c7f124b0ed7f91fd1d","up":true},{"one":"766b61b773ad34c6cfd30b9c2e94840d151c31ab8cbcf546943eb5821d5a8f36","other":"746898d197934d17cd93b958ab20aaa85dba99057495048d2cabc8a996926e05","up":true},{"one":"a6cad8da6a7949d7624b7ea636fd2d72de6a9b14f899df6263ccb4b3e5351c6a","other":"a93b2360b87425860185535aca8fbf3225e0c141d27690d60144689ae0f28534","up":false},{"one":"5958a4ccd5333e8bfed5f18c78d365b201ffc2b9af3afd31748adb8e4f8118bc","other":"00b98e790fb954975837ff95abb1bdc2673dd8a0ce315986cc0dda5b0cefb05d","up":false},{"one":"b87c0ee719d0656cd325300b756f203310675021f3c508b30542deb1114ec7ac","other":"e8d0bd04fa5b56fcd50cb4b1d550855c21192f1294f2954401348c8a28b455ab","up":false},{"one":"6d307f348cc71e8a471ba662c1b5bd9d6e6930c22024f350465cccc7f2208a59","other":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","up":false},{"one":"5f1c751641e1727871c44f0d5779b278871c77f5e05324dc91a8620bed72e627","other":"43d797cb7fced78dd7141ef452ea367d7484d5c0fec2431caebe1317c4aa6340","up":false},{"one":"a3e87b083776e34d10941832ec078fe9a7b8bae8cefe6fecdd5561549561a7f3","other":"960eb6f745966f47aadb13d488aee1187486c99e6bc78c679a6d9b3d421d8eb0","up":false},{"one":"c1c13885e3f543804e62e3b34c0d9762778866e5b8fe9d9df0caa6a64ec41428","other":"ce12d7b26736d08eaa424a5f972a5a1fd50d05aab330340f3605dbab8007bf28","up":false},{"one":"1f1580f6bf3df2ce7a50ec53be2511d22149fede99ca6194b611471adabfafaa","other":"1b6168d6a369029690e836d26ffe017e26769ce504c142abf2a4a65774ecb66d","up":false},{"one":"a6cad8da6a7949d7624b7ea636fd2d72de6a9b14f899df6263ccb4b3e5351c6a","other":"c1c13885e3f543804e62e3b34c0d9762778866e5b8fe9d9df0caa6a64ec41428","up":false},{"one":"13165fffc318dd5f182601384e1ac69dd591236a03e88dfd1c637ea21a01b2dd","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":false},{"one":"6ad608f1087b8af40359db45e7d78e43c3465fdae5cc01a9a9ab6c149cb6fdf3","other":"6b1f3a9b65ce3a168fc98dee2b14b1de6912fd02ee2609b047b2c56f6c3b53e9","up":true},{"one":"b77832f3f26323f09b37f31d208361d77ca08cec3ff829b9451fa3f00ff61fd0","other":"816609d5b9bdb0c7bad6cbe9400fd506df8662b14ad0e0a61bc7b49482548e39","up":false},{"one":"9b240fbf12d9de7975803b09aacdbab94cd978130de62bc89497bb1056e462c0","other":"d6a203c341b618c47ae53b339563b22c31dfc12d89dea7ba0c33e1812829684c","up":true},{"one":"089fe22a0bd48d3120cc8171eb9d698532c17f36768dbc25f554f2c53de11ab0","other":"0ef0bf53ddadd2c942f21e7e09d5a902d166f13920aabb2aa6652b70c423f575","up":true},{"one":"39b5d09d1227a776f8d65b7239cd03e04499e7d519e5d1ff4c4870a1a949dd91","other":"f9265e3c23cd931ad51faec142636a6a8c8bd90a23e77f5327861c81cf4dfe17","up":true},{"one":"1a696462de3d83ff271f30f6d66de968325e37a36877dd67b45d9cdd7e644b5b","other":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","up":false},{"one":"5f1c751641e1727871c44f0d5779b278871c77f5e05324dc91a8620bed72e627","other":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","up":true},{"one":"41cd903abdaf445bfbcc3d5f28c10aba473be8a3ddc614c7f124b0ed7f91fd1d","other":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","up":true},{"one":"6ad608f1087b8af40359db45e7d78e43c3465fdae5cc01a9a9ab6c149cb6fdf3","other":"69baf7ee43b360a3238ad3b175e0ad9c24967dae60c7e91d1ca7ee55167b2bdb","up":true},{"one":"39b5d09d1227a776f8d65b7239cd03e04499e7d519e5d1ff4c4870a1a949dd91","other":"e8d0bd04fa5b56fcd50cb4b1d550855c21192f1294f2954401348c8a28b455ab","up":true},{"one":"41cd903abdaf445bfbcc3d5f28c10aba473be8a3ddc614c7f124b0ed7f91fd1d","other":"795d813cab67300b8c59c78b6b99403c8111920ca47c1e56edfb513bc4999f3f","up":true},{"one":"b270e0d21a173f4717882b1c6dc942d703fe11cd6adf997b3ecd0944fcea6c7b","other":"e8d0bd04fa5b56fcd50cb4b1d550855c21192f1294f2954401348c8a28b455ab","up":true},{"one":"60cbf513366da3f6ee288d3572dc3971ae256b02e5492595e63c31a7b1b0d5a8","other":"5f1c751641e1727871c44f0d5779b278871c77f5e05324dc91a8620bed72e627","up":false},{"one":"c42f36bf6728b24721ce5ac1272a1058e202a236d276af1265a1eaa675f8fd85","other":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","up":false},{"one":"a20d591ea15722712e83fcc6c17732f2370034ef870234a0fa0b2f4aea572fd5","other":"a6cad8da6a7949d7624b7ea636fd2d72de6a9b14f899df6263ccb4b3e5351c6a","up":false},{"one":"81748a1a38162f9e8613ffa562add1b75893b66199840e74c0774db92bfd7ca0","other":"893773caa8e126430f189186956f3e80b49107ce52777972f5e8c11027c072d6","up":false},{"one":"00b98e790fb954975837ff95abb1bdc2673dd8a0ce315986cc0dda5b0cefb05d","other":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","up":false},{"one":"212479d1ab813951780af2bc09a8119dbf62e603c05c0ea26329625036f6708a","other":"fb211f405a7dc3a07b6d4f18522c6273ffa4e18325958eea159e33f768eae846","up":false},{"one":"00b98e790fb954975837ff95abb1bdc2673dd8a0ce315986cc0dda5b0cefb05d","other":"30c022df9209b4515213fe3fa67135c05d960afbd76f8455caa9c83ab5e493b6","up":false},{"one":"41f407b92b1462b93400819a6af8815797b9dbc505afaf0162bbaea795205716","other":"4067997c26042749c16f17169caac66e647b71e3557f7581dbec739d0baa2ef0","up":true},{"one":"58207fc8d92e44a8274398d6105b90d763981afba7021b57be98591e7b739272","other":"5258fdc94d78bb45d6634f2672bc2eacdbc896fc5a39784ecc9d8a7dd52cb1e7","up":false},{"one":"9a8c60732ff0d0b7403b181cdc5bb9e6db0aa4cf5c7158c40e548fcb76726f87","other":"9835fa9db1ce53ad2a526d36050eefd2c593121df4036c269cd09e1be4746a15","up":true},{"one":"5f8eb6ff0f6ed720eaae86390757833f4b9cdfa52564457ed90c3de260ab6c30","other":"4610c06c8f5e1e24a75d9e46a432f4c1a6b9da6c3dbd122d3d3881752d930736","up":true},{"one":"1263f18bf8cb4f5a3335f545cdd5d42f1b84da9edf4fa7346572c2bc38c87eb3","other":"351d1ed5c4012f8870ef2acadc7c0fea2cb697f5688b92e0150c5f670661c617","up":false},{"one":"2c3d7e056f87daf540821a31ea8fe9377d00ed3a55ffba67b8bb716cdd9b7303","other":"d48642f8008e8aa791ba89c9b3a5b8d2d7aeb7e9078caee8813777679608075c","up":false},{"one":"5958a4ccd5333e8bfed5f18c78d365b201ffc2b9af3afd31748adb8e4f8118bc","other":"4a2d15b867a620653f099d66839b80c2dd32aec95caf906f0d5c8146192edf62","up":false},{"one":"6d307f348cc71e8a471ba662c1b5bd9d6e6930c22024f350465cccc7f2208a59","other":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","up":false},{"one":"41f407b92b1462b93400819a6af8815797b9dbc505afaf0162bbaea795205716","other":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","up":true},{"one":"ce12d7b26736d08eaa424a5f972a5a1fd50d05aab330340f3605dbab8007bf28","other":"85845413850f5e38cce69548e20179b477bf383dd2b22428358715d9d72421ac","up":false},{"one":"089fe22a0bd48d3120cc8171eb9d698532c17f36768dbc25f554f2c53de11ab0","other":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","up":false},{"one":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","other":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","up":false},{"one":"00b98e790fb954975837ff95abb1bdc2673dd8a0ce315986cc0dda5b0cefb05d","other":"2c3d7e056f87daf540821a31ea8fe9377d00ed3a55ffba67b8bb716cdd9b7303","up":false},{"one":"58207fc8d92e44a8274398d6105b90d763981afba7021b57be98591e7b739272","other":"6c2fd129c8493cea7634a91aae559ceb64f01cebc45ec59aebd7e709b65941be","up":true},{"one":"1030a5d27a502aeb868e7158373c48af6a85e829ee3a84f4a0b78f9a30bd5a6a","other":"11b53fa40926ac84d75d9d698f46b9611b90194857c1397e117931a890447078","up":false},{"one":"43d797cb7fced78dd7141ef452ea367d7484d5c0fec2431caebe1317c4aa6340","other":"459a945c8d6e67437bdfa0e067962b3591dfa0aa7c1177f8327ba1557bbf1569","up":false},{"one":"5f8eb6ff0f6ed720eaae86390757833f4b9cdfa52564457ed90c3de260ab6c30","other":"459a945c8d6e67437bdfa0e067962b3591dfa0aa7c1177f8327ba1557bbf1569","up":true},{"one":"a3e87b083776e34d10941832ec078fe9a7b8bae8cefe6fecdd5561549561a7f3","other":"958e075a7f22c092360d325dd0c0449e9442c398b2882009c5c3720cbcd3584f","up":false},{"one":"41f407b92b1462b93400819a6af8815797b9dbc505afaf0162bbaea795205716","other":"66f6ff5fbc2526c1db5db491e49138362bef23032c6344ed4ead7dcb58bd1e6c","up":true},{"one":"13165fffc318dd5f182601384e1ac69dd591236a03e88dfd1c637ea21a01b2dd","other":"e22c1f74c4b480fdd2fbe84ad321a98230efb98fb1cdec4f79e4e50b2b0d5cc4","up":false},{"one":"1a696462de3d83ff271f30f6d66de968325e37a36877dd67b45d9cdd7e644b5b","other":"30c022df9209b4515213fe3fa67135c05d960afbd76f8455caa9c83ab5e493b6","up":false},{"one":"39b5d09d1227a776f8d65b7239cd03e04499e7d519e5d1ff4c4870a1a949dd91","other":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","up":true},{"one":"212479d1ab813951780af2bc09a8119dbf62e603c05c0ea26329625036f6708a","other":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","up":false},{"one":"4a2d15b867a620653f099d66839b80c2dd32aec95caf906f0d5c8146192edf62","other":"41f407b92b1462b93400819a6af8815797b9dbc505afaf0162bbaea795205716","up":true},{"one":"f9e4dbb28099af8ca5f28c641a51672f4c6ab54b7037f765a7172a399acb8edc","other":"f9265e3c23cd931ad51faec142636a6a8c8bd90a23e77f5327861c81cf4dfe17","up":true},{"one":"4f7abbc51265dfe22d58f3ac5cde2c05ec56053efe79e3ebb6ece96183dcc17a","other":"766b61b773ad34c6cfd30b9c2e94840d151c31ab8cbcf546943eb5821d5a8f36","up":false},{"one":"9265383ecf776ce1da66e99e5f0a9c1af593d8e56ceef21f42aad4be7e34bc45","other":"944e8231f9f661435f1f94abfaa17862a005877487df5c5d3a566c4dbe46be3f","up":true},{"one":"0ef0bf53ddadd2c942f21e7e09d5a902d166f13920aabb2aa6652b70c423f575","other":"39b5d09d1227a776f8d65b7239cd03e04499e7d519e5d1ff4c4870a1a949dd91","up":true},{"one":"816609d5b9bdb0c7bad6cbe9400fd506df8662b14ad0e0a61bc7b49482548e39","other":"b26eff0d882918a40b909b2fc79489a671a9f990cc0dd3d835493bcb52a468ea","up":false},{"one":"5f1c751641e1727871c44f0d5779b278871c77f5e05324dc91a8620bed72e627","other":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","up":false},{"one":"c6edfd613f216f48a1ea05be84faa5b9a69dd97c6e5d72e0bd339ea62c1b19e1","other":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","up":false},{"one":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","other":"30c022df9209b4515213fe3fa67135c05d960afbd76f8455caa9c83ab5e493b6","up":false},{"one":"fb211f405a7dc3a07b6d4f18522c6273ffa4e18325958eea159e33f768eae846","other":"f9265e3c23cd931ad51faec142636a6a8c8bd90a23e77f5327861c81cf4dfe17","up":true},{"one":"7d14fd0b6c554f218249fed7709df51946baf91a3045ed99fb662c32cc97541b","other":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","up":false},{"one":"d6a203c341b618c47ae53b339563b22c31dfc12d89dea7ba0c33e1812829684c","other":"9dc8a0545acfdd12cb985532b9625d8860b8c87aa871f07f9c91f1599860831e","up":true},{"one":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","other":"5258fdc94d78bb45d6634f2672bc2eacdbc896fc5a39784ecc9d8a7dd52cb1e7","up":false},{"one":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","other":"5958a4ccd5333e8bfed5f18c78d365b201ffc2b9af3afd31748adb8e4f8118bc","up":false},{"one":"d6a203c341b618c47ae53b339563b22c31dfc12d89dea7ba0c33e1812829684c","other":"9a8c60732ff0d0b7403b181cdc5bb9e6db0aa4cf5c7158c40e548fcb76726f87","up":false},{"one":"4a2d15b867a620653f099d66839b80c2dd32aec95caf906f0d5c8146192edf62","other":"4f7abbc51265dfe22d58f3ac5cde2c05ec56053efe79e3ebb6ece96183dcc17a","up":false},{"one":"7d14fd0b6c554f218249fed7709df51946baf91a3045ed99fb662c32cc97541b","other":"795d813cab67300b8c59c78b6b99403c8111920ca47c1e56edfb513bc4999f3f","up":false},{"one":"5f8eb6ff0f6ed720eaae86390757833f4b9cdfa52564457ed90c3de260ab6c30","other":"5f1c751641e1727871c44f0d5779b278871c77f5e05324dc91a8620bed72e627","up":false},{"one":"2c3d7e056f87daf540821a31ea8fe9377d00ed3a55ffba67b8bb716cdd9b7303","other":"2742110997dcb690946885e5a4aaa039f72c9f92025e62f1d371922acf5e27a7","up":true},{"one":"0561b40886edc84497731c7b64acdabb63a22abe573847b78f74887afd86d71e","other":"62d5162001253b3964e6772d35bc31aa372a42e87ce450d48dbb84f5d3c9e8f2","up":false},{"one":"5958a4ccd5333e8bfed5f18c78d365b201ffc2b9af3afd31748adb8e4f8118bc","other":"459a945c8d6e67437bdfa0e067962b3591dfa0aa7c1177f8327ba1557bbf1569","up":false},{"one":"9835fa9db1ce53ad2a526d36050eefd2c593121df4036c269cd09e1be4746a15","other":"212479d1ab813951780af2bc09a8119dbf62e603c05c0ea26329625036f6708a","up":false},{"one":"cb704e26ceff5b613d8211423375fe0ded4d867bc892894a70d71289f1a9c1c1","other":"ce12d7b26736d08eaa424a5f972a5a1fd50d05aab330340f3605dbab8007bf28","up":true},{"one":"4f7abbc51265dfe22d58f3ac5cde2c05ec56053efe79e3ebb6ece96183dcc17a","other":"459a945c8d6e67437bdfa0e067962b3591dfa0aa7c1177f8327ba1557bbf1569","up":false},{"one":"4067997c26042749c16f17169caac66e647b71e3557f7581dbec739d0baa2ef0","other":"459a945c8d6e67437bdfa0e067962b3591dfa0aa7c1177f8327ba1557bbf1569","up":false},{"one":"016e174a29dc18857107fbebc876689b401c1174964ac6cacb31a4cb847a4d30","other":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","up":false},{"one":"00b98e790fb954975837ff95abb1bdc2673dd8a0ce315986cc0dda5b0cefb05d","other":"39b5d09d1227a776f8d65b7239cd03e04499e7d519e5d1ff4c4870a1a949dd91","up":false},{"one":"ce12d7b26736d08eaa424a5f972a5a1fd50d05aab330340f3605dbab8007bf28","other":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","up":false},{"one":"ede2568d60ce7a12b4380818a31d2895fcb9b815cfb5526d28b2a82ff62e0381","other":"ef40fdb37f2f89ec77833e48b1bd210b2241949b9ef8f6dca6bf6189503abb2c","up":false},{"one":"016e174a29dc18857107fbebc876689b401c1174964ac6cacb31a4cb847a4d30","other":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","up":false},{"one":"41f407b92b1462b93400819a6af8815797b9dbc505afaf0162bbaea795205716","other":"41cd903abdaf445bfbcc3d5f28c10aba473be8a3ddc614c7f124b0ed7f91fd1d","up":false},{"one":"57d3361ee59d006e1dba3e1ae9ea624444ee09f70410dff492d624d7e7786d2d","other":"5f1c751641e1727871c44f0d5779b278871c77f5e05324dc91a8620bed72e627","up":true},{"one":"567206d70bc58993b08719afd9c007c562aad99437599356763b74cd4707083c","other":"5f1c751641e1727871c44f0d5779b278871c77f5e05324dc91a8620bed72e627","up":false},{"one":"7c992ceefe03187bce1edbf8f80ccd1cab80b3908e625a61fef21ec6d35d04a3","other":"795d813cab67300b8c59c78b6b99403c8111920ca47c1e56edfb513bc4999f3f","up":false},{"one":"9a8c60732ff0d0b7403b181cdc5bb9e6db0aa4cf5c7158c40e548fcb76726f87","other":"9b240fbf12d9de7975803b09aacdbab94cd978130de62bc89497bb1056e462c0","up":false},{"one":"4a2d15b867a620653f099d66839b80c2dd32aec95caf906f0d5c8146192edf62","other":"4e6eab6eb3d49b20e2f3dee169d4647ebf26d07044698ff93d51c5fc30fb4ae0","up":false},{"one":"958e075a7f22c092360d325dd0c0449e9442c398b2882009c5c3720cbcd3584f","other":"9265383ecf776ce1da66e99e5f0a9c1af593d8e56ceef21f42aad4be7e34bc45","up":false},{"one":"0ef0bf53ddadd2c942f21e7e09d5a902d166f13920aabb2aa6652b70c423f575","other":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","up":false},{"one":"9b240fbf12d9de7975803b09aacdbab94cd978130de62bc89497bb1056e462c0","other":"8357eeaa65808f09b9a83362cb07508878becee9f843a5e1e70d025167b198ea","up":false},{"one":"c1c13885e3f543804e62e3b34c0d9762778866e5b8fe9d9df0caa6a64ec41428","other":"f7cd1cc174a21f190a715dad84857f563908db960f2a5943ef015caf6220a8d3","up":false},{"one":"0561b40886edc84497731c7b64acdabb63a22abe573847b78f74887afd86d71e","other":"0d90040b9d0cd742149654262198aa8dc4fc96cfdf9b84cfe833277c3e23831f","up":false},{"one":"2742110997dcb690946885e5a4aaa039f72c9f92025e62f1d371922acf5e27a7","other":"1b6168d6a369029690e836d26ffe017e26769ce504c142abf2a4a65774ecb66d","up":false},{"one":"567206d70bc58993b08719afd9c007c562aad99437599356763b74cd4707083c","other":"30c022df9209b4515213fe3fa67135c05d960afbd76f8455caa9c83ab5e493b6","up":false},{"one":"9835fa9db1ce53ad2a526d36050eefd2c593121df4036c269cd09e1be4746a15","other":"816609d5b9bdb0c7bad6cbe9400fd506df8662b14ad0e0a61bc7b49482548e39","up":false},{"one":"13165fffc318dd5f182601384e1ac69dd591236a03e88dfd1c637ea21a01b2dd","other":"0ef0bf53ddadd2c942f21e7e09d5a902d166f13920aabb2aa6652b70c423f575","up":false},{"one":"9b240fbf12d9de7975803b09aacdbab94cd978130de62bc89497bb1056e462c0","other":"893773caa8e126430f189186956f3e80b49107ce52777972f5e8c11027c072d6","up":false},{"one":"4f7abbc51265dfe22d58f3ac5cde2c05ec56053efe79e3ebb6ece96183dcc17a","other":"5258fdc94d78bb45d6634f2672bc2eacdbc896fc5a39784ecc9d8a7dd52cb1e7","up":false},{"one":"7a46bf03f327b100dc00140c97828a3b607280b6cd57220703be393ae4fdddbc","other":"795d813cab67300b8c59c78b6b99403c8111920ca47c1e56edfb513bc4999f3f","up":false},{"one":"1f1580f6bf3df2ce7a50ec53be2511d22149fede99ca6194b611471adabfafaa","other":"74a4ffb0d717a523ec72e07cc5efe3e98d55932d6e0592ef45fe54b378e4e952","up":false},{"one":"2742110997dcb690946885e5a4aaa039f72c9f92025e62f1d371922acf5e27a7","other":"1a16e158f1d35f2573d20b05ba51a2c9cc196547038e3b7be97ddc7598e81257","up":false},{"one":"016e174a29dc18857107fbebc876689b401c1174964ac6cacb31a4cb847a4d30","other":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","up":false},{"one":"00b98e790fb954975837ff95abb1bdc2673dd8a0ce315986cc0dda5b0cefb05d","other":"160c0401bcd1b38db7b4ae82470244ac9a9923755fc475aa67eb6dedc703c223","up":false},{"one":"1f1580f6bf3df2ce7a50ec53be2511d22149fede99ca6194b611471adabfafaa","other":"746898d197934d17cd93b958ab20aaa85dba99057495048d2cabc8a996926e05","up":false},{"one":"ce12d7b26736d08eaa424a5f972a5a1fd50d05aab330340f3605dbab8007bf28","other":"d48642f8008e8aa791ba89c9b3a5b8d2d7aeb7e9078caee8813777679608075c","up":false},{"one":"c1c13885e3f543804e62e3b34c0d9762778866e5b8fe9d9df0caa6a64ec41428","other":"fb211f405a7dc3a07b6d4f18522c6273ffa4e18325958eea159e33f768eae846","up":false},{"one":"85845413850f5e38cce69548e20179b477bf383dd2b22428358715d9d72421ac","other":"b26eff0d882918a40b909b2fc79489a671a9f990cc0dd3d835493bcb52a468ea","up":false},{"one":"9265383ecf776ce1da66e99e5f0a9c1af593d8e56ceef21f42aad4be7e34bc45","other":"9b240fbf12d9de7975803b09aacdbab94cd978130de62bc89497bb1056e462c0","up":false},{"one":"816609d5b9bdb0c7bad6cbe9400fd506df8662b14ad0e0a61bc7b49482548e39","other":"bb9c9f07ee2f8bc2bb0f011309f8314de7c1777a6f683f90506b180ef6f5aab4","up":false},{"one":"fb211f405a7dc3a07b6d4f18522c6273ffa4e18325958eea159e33f768eae846","other":"fe9d68b6f0d0add106d4a9e132308cf5caa867624784d652f071f5d1fe87ff8b","up":false},{"one":"fb211f405a7dc3a07b6d4f18522c6273ffa4e18325958eea159e33f768eae846","other":"fcf3ccf76c4699752670103e9cb98ac1294339a357876c71673f7e486d02f08b","up":false},{"one":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","other":"567206d70bc58993b08719afd9c007c562aad99437599356763b74cd4707083c","up":false},{"one":"11b53fa40926ac84d75d9d698f46b9611b90194857c1397e117931a890447078","other":"171f8d32719c1c3a330cdec42e47f0a5876bb2cc1cac8f13579f9cd19887c531","up":false},{"one":"816609d5b9bdb0c7bad6cbe9400fd506df8662b14ad0e0a61bc7b49482548e39","other":"a3e87b083776e34d10941832ec078fe9a7b8bae8cefe6fecdd5561549561a7f3","up":false},{"one":"f84317ef488d8c1495b429b7a1fa21ea53a73e737b3c1aa890c20e38658ab148","other":"e22c1f74c4b480fdd2fbe84ad321a98230efb98fb1cdec4f79e4e50b2b0d5cc4","up":false},{"one":"66f6ff5fbc2526c1db5db491e49138362bef23032c6344ed4ead7dcb58bd1e6c","other":"5f1c751641e1727871c44f0d5779b278871c77f5e05324dc91a8620bed72e627","up":false},{"one":"41f407b92b1462b93400819a6af8815797b9dbc505afaf0162bbaea795205716","other":"43d797cb7fced78dd7141ef452ea367d7484d5c0fec2431caebe1317c4aa6340","up":false},{"one":"f84317ef488d8c1495b429b7a1fa21ea53a73e737b3c1aa890c20e38658ab148","other":"fd54f34d1deead333014c472182bf5cb2db1d1b1ab55caa70f5dc70108622f17","up":false},{"one":"016e174a29dc18857107fbebc876689b401c1174964ac6cacb31a4cb847a4d30","other":"0f19fb4d0c6cfe36f7f59c8fe54d2e169687603e1bde0fe6b3a5e864fa51f2f0","up":false},{"one":"b87c0ee719d0656cd325300b756f203310675021f3c508b30542deb1114ec7ac","other":"869fc21fff150ce9e1bc4512b183559433767cfc1cb7b6baae6d2a1313245ae8","up":false},{"one":"81748a1a38162f9e8613ffa562add1b75893b66199840e74c0774db92bfd7ca0","other":"88dc5f31a2b295e8b43bc6e7abaae130061c2d44c49c95bcca3d9dd0a268324c","up":false},{"one":"b87c0ee719d0656cd325300b756f203310675021f3c508b30542deb1114ec7ac","other":"85845413850f5e38cce69548e20179b477bf383dd2b22428358715d9d72421ac","up":false},{"one":"ef40fdb37f2f89ec77833e48b1bd210b2241949b9ef8f6dca6bf6189503abb2c","other":"efdee7c9bab75e85302e3cac95ca2434af63ccb731ed24e0bda419e7a471cdde","up":false},{"one":"6421894e25e421d16eccd8858aed3354fce2d04c9d5c46d4005ee1f16944ccfd","other":"74a4ffb0d717a523ec72e07cc5efe3e98d55932d6e0592ef45fe54b378e4e952","up":false},{"one":"9265383ecf776ce1da66e99e5f0a9c1af593d8e56ceef21f42aad4be7e34bc45","other":"2c3d7e056f87daf540821a31ea8fe9377d00ed3a55ffba67b8bb716cdd9b7303","up":false},{"one":"fb211f405a7dc3a07b6d4f18522c6273ffa4e18325958eea159e33f768eae846","other":"fd54f34d1deead333014c472182bf5cb2db1d1b1ab55caa70f5dc70108622f17","up":false},{"one":"11b53fa40926ac84d75d9d698f46b9611b90194857c1397e117931a890447078","other":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","up":false},{"one":"18b0fcbba2350241088129e8f6a07742857c3edaacd6ef1ec0861cc8e48492ce","other":"30c022df9209b4515213fe3fa67135c05d960afbd76f8455caa9c83ab5e493b6","up":false},{"one":"f84317ef488d8c1495b429b7a1fa21ea53a73e737b3c1aa890c20e38658ab148","other":"fe9d68b6f0d0add106d4a9e132308cf5caa867624784d652f071f5d1fe87ff8b","up":false},{"one":"1263f18bf8cb4f5a3335f545cdd5d42f1b84da9edf4fa7346572c2bc38c87eb3","other":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","up":false},{"one":"6421894e25e421d16eccd8858aed3354fce2d04c9d5c46d4005ee1f16944ccfd","other":"7a46bf03f327b100dc00140c97828a3b607280b6cd57220703be393ae4fdddbc","up":false},{"one":"160c0401bcd1b38db7b4ae82470244ac9a9923755fc475aa67eb6dedc703c223","other":"13165fffc318dd5f182601384e1ac69dd591236a03e88dfd1c637ea21a01b2dd","up":false},{"one":"b87c0ee719d0656cd325300b756f203310675021f3c508b30542deb1114ec7ac","other":"81748a1a38162f9e8613ffa562add1b75893b66199840e74c0774db92bfd7ca0","up":false},{"one":"6d307f348cc71e8a471ba662c1b5bd9d6e6930c22024f350465cccc7f2208a59","other":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","up":false},{"one":"1263f18bf8cb4f5a3335f545cdd5d42f1b84da9edf4fa7346572c2bc38c87eb3","other":"30c022df9209b4515213fe3fa67135c05d960afbd76f8455caa9c83ab5e493b6","up":false},{"one":"160c0401bcd1b38db7b4ae82470244ac9a9923755fc475aa67eb6dedc703c223","other":"12df4927ff743d4dd9dfb0a7d13d39012b172f6a35921aaef1547d5d02ad38fe","up":false},{"one":"5f1c751641e1727871c44f0d5779b278871c77f5e05324dc91a8620bed72e627","other":"580a650c2c3e95d35e66264df6f48e28a091bacd20dc2c9828a5b563353d2052","up":false},{"one":"cb704e26ceff5b613d8211423375fe0ded4d867bc892894a70d71289f1a9c1c1","other":"d48642f8008e8aa791ba89c9b3a5b8d2d7aeb7e9078caee8813777679608075c","up":false},{"one":"8ff601de9537c9e5d79a3707a312fe893383bef000d00acee3d4552068ef6030","other":"cd944ad4c50c4357005ff26ef26369ba3375a1205eec532b6e51b22bf25510bc","up":false},{"one":"8ff601de9537c9e5d79a3707a312fe893383bef000d00acee3d4552068ef6030","other":"f84317ef488d8c1495b429b7a1fa21ea53a73e737b3c1aa890c20e38658ab148","up":false},{"one":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","other":"0d90040b9d0cd742149654262198aa8dc4fc96cfdf9b84cfe833277c3e23831f","up":false},{"one":"1263f18bf8cb4f5a3335f545cdd5d42f1b84da9edf4fa7346572c2bc38c87eb3","other":"089fe22a0bd48d3120cc8171eb9d698532c17f36768dbc25f554f2c53de11ab0","up":false},{"one":"43d797cb7fced78dd7141ef452ea367d7484d5c0fec2431caebe1317c4aa6340","other":"4a2d15b867a620653f099d66839b80c2dd32aec95caf906f0d5c8146192edf62","up":false},{"one":"4a2d15b867a620653f099d66839b80c2dd32aec95caf906f0d5c8146192edf62","other":"4067997c26042749c16f17169caac66e647b71e3557f7581dbec739d0baa2ef0","up":false},{"one":"00b98e790fb954975837ff95abb1bdc2673dd8a0ce315986cc0dda5b0cefb05d","other":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","up":false},{"one":"165d670ad20922226e1f3275abd153b6fe8d9ffe3e45e6c1fcae3f5a3d2ec35a","other":"0f19fb4d0c6cfe36f7f59c8fe54d2e169687603e1bde0fe6b3a5e864fa51f2f0","up":false},{"one":"8ff601de9537c9e5d79a3707a312fe893383bef000d00acee3d4552068ef6030","other":"b77832f3f26323f09b37f31d208361d77ca08cec3ff829b9451fa3f00ff61fd0","up":false},{"one":"160c0401bcd1b38db7b4ae82470244ac9a9923755fc475aa67eb6dedc703c223","other":"24263ec4b92f390e07193d00e5a032ea2649f560027be02878da656e37d4a1b1","up":false},{"one":"11b53fa40926ac84d75d9d698f46b9611b90194857c1397e117931a890447078","other":"13165fffc318dd5f182601384e1ac69dd591236a03e88dfd1c637ea21a01b2dd","up":false},{"one":"18b0fcbba2350241088129e8f6a07742857c3edaacd6ef1ec0861cc8e48492ce","other":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","up":false},{"one":"f84317ef488d8c1495b429b7a1fa21ea53a73e737b3c1aa890c20e38658ab148","other":"f9265e3c23cd931ad51faec142636a6a8c8bd90a23e77f5327861c81cf4dfe17","up":false},{"one":"9265383ecf776ce1da66e99e5f0a9c1af593d8e56ceef21f42aad4be7e34bc45","other":"3547871df541d0734c374e4c42f610f235f0c923d8b2bdc88c17fadb2c8e25ea","up":false},{"one":"9d60b67d731aee2e6ac86d17c9e6a6aa5aaeec6b6803bae5c5fbc02643f7358c","other":"9a8c60732ff0d0b7403b181cdc5bb9e6db0aa4cf5c7158c40e548fcb76726f87","up":false},{"one":"41cd903abdaf445bfbcc3d5f28c10aba473be8a3ddc614c7f124b0ed7f91fd1d","other":"459a945c8d6e67437bdfa0e067962b3591dfa0aa7c1177f8327ba1557bbf1569","up":false},{"one":"cb704e26ceff5b613d8211423375fe0ded4d867bc892894a70d71289f1a9c1c1","other":"d24a8781af0c8ff19eca66388e308569bcce48144e559dc4c543f5a2ab0fe7c3","up":false},{"one":"9265383ecf776ce1da66e99e5f0a9c1af593d8e56ceef21f42aad4be7e34bc45","other":"b77832f3f26323f09b37f31d208361d77ca08cec3ff829b9451fa3f00ff61fd0","up":false},{"one":"8ff601de9537c9e5d79a3707a312fe893383bef000d00acee3d4552068ef6030","other":"b270e0d21a173f4717882b1c6dc942d703fe11cd6adf997b3ecd0944fcea6c7b","up":false},{"one":"9d60b67d731aee2e6ac86d17c9e6a6aa5aaeec6b6803bae5c5fbc02643f7358c","other":"9835fa9db1ce53ad2a526d36050eefd2c593121df4036c269cd09e1be4746a15","up":false},{"one":"8ff601de9537c9e5d79a3707a312fe893383bef000d00acee3d4552068ef6030","other":"958e075a7f22c092360d325dd0c0449e9442c398b2882009c5c3720cbcd3584f","up":false},{"one":"5f1c751641e1727871c44f0d5779b278871c77f5e05324dc91a8620bed72e627","other":"5958a4ccd5333e8bfed5f18c78d365b201ffc2b9af3afd31748adb8e4f8118bc","up":false},{"one":"9d60b67d731aee2e6ac86d17c9e6a6aa5aaeec6b6803bae5c5fbc02643f7358c","other":"9b240fbf12d9de7975803b09aacdbab94cd978130de62bc89497bb1056e462c0","up":false},{"one":"9265383ecf776ce1da66e99e5f0a9c1af593d8e56ceef21f42aad4be7e34bc45","other":"b270e0d21a173f4717882b1c6dc942d703fe11cd6adf997b3ecd0944fcea6c7b","up":false},{"one":"8ff601de9537c9e5d79a3707a312fe893383bef000d00acee3d4552068ef6030","other":"9b240fbf12d9de7975803b09aacdbab94cd978130de62bc89497bb1056e462c0","up":false},{"one":"41cd903abdaf445bfbcc3d5f28c10aba473be8a3ddc614c7f124b0ed7f91fd1d","other":"4610c06c8f5e1e24a75d9e46a432f4c1a6b9da6c3dbd122d3d3881752d930736","up":false}]} \ No newline at end of file diff --git a/swarm/pss/testdata/snapshot_16.json b/swarm/pss/testdata/snapshot_16.json deleted file mode 100644 index d35df9fc9b55..000000000000 --- a/swarm/pss/testdata/snapshot_16.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes":[{"node":{"config":{"id":"88621610b5fdbf3e584724e2268a24dc41562da5cdba92357b97edc42d79ec4e","private_key":"2c268d9cf0ca43f4b0ad80f8980f4fe019e0294819f881d505e02382b472b98b","name":"node01","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"d9b0b57a237a8386cf6cf611779d8df3f5b0f9c01719e97230c3b23bf0ab04ae","private_key":"69ac59cce230e49f10c769fc8f2b717bdadc5ffa5dcf7fae19d8cb15315fa177","name":"node02","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c553ca20b4f7f1b1e3b71b261829c27eb1cfe6710aa0999f655a42670e6d8813","private_key":"b793f9ace49ecce16c0c86b49495093f7f4c5fa0003675c9eb6efa802c8daafe","name":"node03","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c651a0c924fc01e9d6cd30aba6864cd93631d5d4e21ceeda7db1922f105c5ca8","private_key":"9fecef44b474621ce2ddd57cf67df319bd0e13c27f0f6e9d060c34ef813675ea","name":"node04","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"4b6e068e84fecde2bb6a48698233f0c2ae5e66b15a8bad2e0b5d499c31d48b98","private_key":"c8f4336f88c90242744e9c04fba1a55027d9ad4295b7a2b0ad99e8dae12463d3","name":"node05","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"6629ec867229b28221a6118eb0e097861eb784b624578f55c3811cdf5026c1c7","private_key":"0ca52c3e4781fc413a13448abaae042dae52e2bab5772c52440bc4b2c6a5bda3","name":"node06","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"9cd2b6b17d2954c0a0d4a340707c71215a08c3c27b5b5b294e96ad20263ba600","private_key":"447c97a6c211160f0fb7990812c637bd346003a484cf05c3d92816007ab3745a","name":"node07","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c0f23df25018045ddc4035268a9e7468f386ecad306b9ed9053cf66e2f1ab0a2","private_key":"8fa6ac35409a6df21ee32da8c36f61cd007a3515d23bb350351c263c241b851a","name":"node08","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"69d329853a0624dba12949e6be7db093143976d9a5d514b80d72a9bf7fbc568c","private_key":"e4143bd79f4a55f463b623afc397fe0166a144f45c41fd8b58f816e208212819","name":"node09","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"d33e4e6af5ba13ae448a16a4a8183ab1050fa3f983cdbf03dec5c21a5e9dd146","private_key":"6a2a32adb2b5cbc66adf29d5da1123b6e345e73ed42916e9535df3058801cb92","name":"node10","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"8c92cb1fcda5b67a4b928c200e2e5e324599c997202d9ea98f703239ee3fc3b1","private_key":"ae486490233b1b16e6a35461a3b90573f59362b9bbf0b8c46b65c715b0506bd7","name":"node11","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"5989492b2703336b42f59b2be1653a0bce76e801ef262121f627a476581af5e1","private_key":"cbf9da4b4f44f44c0bcf69bb2134e33ddfdda7742d7f5609db74bdf4fb683cfb","name":"node12","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c7a2bf651f59149716235fa7bfb4cee835aa4bdd402ccab8c52959aa74dc6af4","private_key":"180ee0110d87d330dd416568e300cacfd4d52f8c08875ada1a6d5b4a11142b1d","name":"node13","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"6e8c4a5d6e54dda7c633ff469e8f935935250f4d0034c9058048dc643a4f811e","private_key":"d7a364b77d55a53453e94e10d235edf4ceb248653247bded6c1df31fc88ecbca","name":"node14","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1ffdd4692bd4fd272ac6b5c2e039354869df92c0d4247abedcd8a8932b057574","private_key":"7eaec2f68f3b0e562a5438324aa30b9c22746af6e8139b05b21e89103f508c5a","name":"node15","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"55ce5ea5772c31de27b337f1e2cf767663c03a61b2f47b71921def1cfc3270ea","private_key":"b130fa04ab1f60d0713b9260156457f9139e819cb2f70bd87a2ea409f135f881","name":"node16","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}}],"conns":[{"one":"55ce5ea5772c31de27b337f1e2cf767663c03a61b2f47b71921def1cfc3270ea","other":"88621610b5fdbf3e584724e2268a24dc41562da5cdba92357b97edc42d79ec4e","up":true},{"one":"88621610b5fdbf3e584724e2268a24dc41562da5cdba92357b97edc42d79ec4e","other":"d9b0b57a237a8386cf6cf611779d8df3f5b0f9c01719e97230c3b23bf0ab04ae","up":true},{"one":"d9b0b57a237a8386cf6cf611779d8df3f5b0f9c01719e97230c3b23bf0ab04ae","other":"c553ca20b4f7f1b1e3b71b261829c27eb1cfe6710aa0999f655a42670e6d8813","up":true},{"one":"c553ca20b4f7f1b1e3b71b261829c27eb1cfe6710aa0999f655a42670e6d8813","other":"c651a0c924fc01e9d6cd30aba6864cd93631d5d4e21ceeda7db1922f105c5ca8","up":true},{"one":"c651a0c924fc01e9d6cd30aba6864cd93631d5d4e21ceeda7db1922f105c5ca8","other":"4b6e068e84fecde2bb6a48698233f0c2ae5e66b15a8bad2e0b5d499c31d48b98","up":true},{"one":"4b6e068e84fecde2bb6a48698233f0c2ae5e66b15a8bad2e0b5d499c31d48b98","other":"6629ec867229b28221a6118eb0e097861eb784b624578f55c3811cdf5026c1c7","up":true},{"one":"6629ec867229b28221a6118eb0e097861eb784b624578f55c3811cdf5026c1c7","other":"9cd2b6b17d2954c0a0d4a340707c71215a08c3c27b5b5b294e96ad20263ba600","up":true},{"one":"9cd2b6b17d2954c0a0d4a340707c71215a08c3c27b5b5b294e96ad20263ba600","other":"c0f23df25018045ddc4035268a9e7468f386ecad306b9ed9053cf66e2f1ab0a2","up":true},{"one":"c0f23df25018045ddc4035268a9e7468f386ecad306b9ed9053cf66e2f1ab0a2","other":"69d329853a0624dba12949e6be7db093143976d9a5d514b80d72a9bf7fbc568c","up":true},{"one":"69d329853a0624dba12949e6be7db093143976d9a5d514b80d72a9bf7fbc568c","other":"d33e4e6af5ba13ae448a16a4a8183ab1050fa3f983cdbf03dec5c21a5e9dd146","up":true},{"one":"d33e4e6af5ba13ae448a16a4a8183ab1050fa3f983cdbf03dec5c21a5e9dd146","other":"8c92cb1fcda5b67a4b928c200e2e5e324599c997202d9ea98f703239ee3fc3b1","up":true},{"one":"8c92cb1fcda5b67a4b928c200e2e5e324599c997202d9ea98f703239ee3fc3b1","other":"5989492b2703336b42f59b2be1653a0bce76e801ef262121f627a476581af5e1","up":true},{"one":"5989492b2703336b42f59b2be1653a0bce76e801ef262121f627a476581af5e1","other":"c7a2bf651f59149716235fa7bfb4cee835aa4bdd402ccab8c52959aa74dc6af4","up":true},{"one":"c7a2bf651f59149716235fa7bfb4cee835aa4bdd402ccab8c52959aa74dc6af4","other":"6e8c4a5d6e54dda7c633ff469e8f935935250f4d0034c9058048dc643a4f811e","up":true},{"one":"6e8c4a5d6e54dda7c633ff469e8f935935250f4d0034c9058048dc643a4f811e","other":"1ffdd4692bd4fd272ac6b5c2e039354869df92c0d4247abedcd8a8932b057574","up":true},{"one":"1ffdd4692bd4fd272ac6b5c2e039354869df92c0d4247abedcd8a8932b057574","other":"55ce5ea5772c31de27b337f1e2cf767663c03a61b2f47b71921def1cfc3270ea","up":true},{"one":"88621610b5fdbf3e584724e2268a24dc41562da5cdba92357b97edc42d79ec4e","other":"8c92cb1fcda5b67a4b928c200e2e5e324599c997202d9ea98f703239ee3fc3b1","up":true},{"one":"c651a0c924fc01e9d6cd30aba6864cd93631d5d4e21ceeda7db1922f105c5ca8","other":"d9b0b57a237a8386cf6cf611779d8df3f5b0f9c01719e97230c3b23bf0ab04ae","up":true},{"one":"4b6e068e84fecde2bb6a48698233f0c2ae5e66b15a8bad2e0b5d499c31d48b98","other":"6e8c4a5d6e54dda7c633ff469e8f935935250f4d0034c9058048dc643a4f811e","up":true},{"one":"9cd2b6b17d2954c0a0d4a340707c71215a08c3c27b5b5b294e96ad20263ba600","other":"8c92cb1fcda5b67a4b928c200e2e5e324599c997202d9ea98f703239ee3fc3b1","up":true},{"one":"6629ec867229b28221a6118eb0e097861eb784b624578f55c3811cdf5026c1c7","other":"6e8c4a5d6e54dda7c633ff469e8f935935250f4d0034c9058048dc643a4f811e","up":true},{"one":"6e8c4a5d6e54dda7c633ff469e8f935935250f4d0034c9058048dc643a4f811e","other":"5989492b2703336b42f59b2be1653a0bce76e801ef262121f627a476581af5e1","up":true},{"one":"69d329853a0624dba12949e6be7db093143976d9a5d514b80d72a9bf7fbc568c","other":"6e8c4a5d6e54dda7c633ff469e8f935935250f4d0034c9058048dc643a4f811e","up":true},{"one":"d33e4e6af5ba13ae448a16a4a8183ab1050fa3f983cdbf03dec5c21a5e9dd146","other":"6e8c4a5d6e54dda7c633ff469e8f935935250f4d0034c9058048dc643a4f811e","up":true},{"one":"c7a2bf651f59149716235fa7bfb4cee835aa4bdd402ccab8c52959aa74dc6af4","other":"d33e4e6af5ba13ae448a16a4a8183ab1050fa3f983cdbf03dec5c21a5e9dd146","up":true},{"one":"55ce5ea5772c31de27b337f1e2cf767663c03a61b2f47b71921def1cfc3270ea","other":"6e8c4a5d6e54dda7c633ff469e8f935935250f4d0034c9058048dc643a4f811e","up":true},{"one":"5989492b2703336b42f59b2be1653a0bce76e801ef262121f627a476581af5e1","other":"1ffdd4692bd4fd272ac6b5c2e039354869df92c0d4247abedcd8a8932b057574","up":true},{"one":"c0f23df25018045ddc4035268a9e7468f386ecad306b9ed9053cf66e2f1ab0a2","other":"d33e4e6af5ba13ae448a16a4a8183ab1050fa3f983cdbf03dec5c21a5e9dd146","up":true},{"one":"1ffdd4692bd4fd272ac6b5c2e039354869df92c0d4247abedcd8a8932b057574","other":"4b6e068e84fecde2bb6a48698233f0c2ae5e66b15a8bad2e0b5d499c31d48b98","up":true},{"one":"c553ca20b4f7f1b1e3b71b261829c27eb1cfe6710aa0999f655a42670e6d8813","other":"c7a2bf651f59149716235fa7bfb4cee835aa4bdd402ccab8c52959aa74dc6af4","up":true},{"one":"d9b0b57a237a8386cf6cf611779d8df3f5b0f9c01719e97230c3b23bf0ab04ae","other":"c7a2bf651f59149716235fa7bfb4cee835aa4bdd402ccab8c52959aa74dc6af4","up":true},{"one":"88621610b5fdbf3e584724e2268a24dc41562da5cdba92357b97edc42d79ec4e","other":"9cd2b6b17d2954c0a0d4a340707c71215a08c3c27b5b5b294e96ad20263ba600","up":true},{"one":"d9b0b57a237a8386cf6cf611779d8df3f5b0f9c01719e97230c3b23bf0ab04ae","other":"c0f23df25018045ddc4035268a9e7468f386ecad306b9ed9053cf66e2f1ab0a2","up":true},{"one":"c553ca20b4f7f1b1e3b71b261829c27eb1cfe6710aa0999f655a42670e6d8813","other":"5989492b2703336b42f59b2be1653a0bce76e801ef262121f627a476581af5e1","up":true},{"one":"c651a0c924fc01e9d6cd30aba6864cd93631d5d4e21ceeda7db1922f105c5ca8","other":"c7a2bf651f59149716235fa7bfb4cee835aa4bdd402ccab8c52959aa74dc6af4","up":true},{"one":"4b6e068e84fecde2bb6a48698233f0c2ae5e66b15a8bad2e0b5d499c31d48b98","other":"55ce5ea5772c31de27b337f1e2cf767663c03a61b2f47b71921def1cfc3270ea","up":true},{"one":"6629ec867229b28221a6118eb0e097861eb784b624578f55c3811cdf5026c1c7","other":"69d329853a0624dba12949e6be7db093143976d9a5d514b80d72a9bf7fbc568c","up":true},{"one":"9cd2b6b17d2954c0a0d4a340707c71215a08c3c27b5b5b294e96ad20263ba600","other":"c553ca20b4f7f1b1e3b71b261829c27eb1cfe6710aa0999f655a42670e6d8813","up":true},{"one":"c0f23df25018045ddc4035268a9e7468f386ecad306b9ed9053cf66e2f1ab0a2","other":"c7a2bf651f59149716235fa7bfb4cee835aa4bdd402ccab8c52959aa74dc6af4","up":true},{"one":"1ffdd4692bd4fd272ac6b5c2e039354869df92c0d4247abedcd8a8932b057574","other":"6629ec867229b28221a6118eb0e097861eb784b624578f55c3811cdf5026c1c7","up":true},{"one":"55ce5ea5772c31de27b337f1e2cf767663c03a61b2f47b71921def1cfc3270ea","other":"5989492b2703336b42f59b2be1653a0bce76e801ef262121f627a476581af5e1","up":true},{"one":"d33e4e6af5ba13ae448a16a4a8183ab1050fa3f983cdbf03dec5c21a5e9dd146","other":"d9b0b57a237a8386cf6cf611779d8df3f5b0f9c01719e97230c3b23bf0ab04ae","up":true},{"one":"5989492b2703336b42f59b2be1653a0bce76e801ef262121f627a476581af5e1","other":"4b6e068e84fecde2bb6a48698233f0c2ae5e66b15a8bad2e0b5d499c31d48b98","up":true},{"one":"c7a2bf651f59149716235fa7bfb4cee835aa4bdd402ccab8c52959aa74dc6af4","other":"9cd2b6b17d2954c0a0d4a340707c71215a08c3c27b5b5b294e96ad20263ba600","up":true},{"one":"69d329853a0624dba12949e6be7db093143976d9a5d514b80d72a9bf7fbc568c","other":"5989492b2703336b42f59b2be1653a0bce76e801ef262121f627a476581af5e1","up":true},{"one":"d9b0b57a237a8386cf6cf611779d8df3f5b0f9c01719e97230c3b23bf0ab04ae","other":"55ce5ea5772c31de27b337f1e2cf767663c03a61b2f47b71921def1cfc3270ea","up":true},{"one":"c553ca20b4f7f1b1e3b71b261829c27eb1cfe6710aa0999f655a42670e6d8813","other":"d33e4e6af5ba13ae448a16a4a8183ab1050fa3f983cdbf03dec5c21a5e9dd146","up":true},{"one":"c651a0c924fc01e9d6cd30aba6864cd93631d5d4e21ceeda7db1922f105c5ca8","other":"8c92cb1fcda5b67a4b928c200e2e5e324599c997202d9ea98f703239ee3fc3b1","up":true},{"one":"c0f23df25018045ddc4035268a9e7468f386ecad306b9ed9053cf66e2f1ab0a2","other":"c651a0c924fc01e9d6cd30aba6864cd93631d5d4e21ceeda7db1922f105c5ca8","up":true},{"one":"69d329853a0624dba12949e6be7db093143976d9a5d514b80d72a9bf7fbc568c","other":"1ffdd4692bd4fd272ac6b5c2e039354869df92c0d4247abedcd8a8932b057574","up":true},{"one":"d33e4e6af5ba13ae448a16a4a8183ab1050fa3f983cdbf03dec5c21a5e9dd146","other":"c651a0c924fc01e9d6cd30aba6864cd93631d5d4e21ceeda7db1922f105c5ca8","up":true},{"one":"1ffdd4692bd4fd272ac6b5c2e039354869df92c0d4247abedcd8a8932b057574","other":"9cd2b6b17d2954c0a0d4a340707c71215a08c3c27b5b5b294e96ad20263ba600","up":true},{"one":"c0f23df25018045ddc4035268a9e7468f386ecad306b9ed9053cf66e2f1ab0a2","other":"c553ca20b4f7f1b1e3b71b261829c27eb1cfe6710aa0999f655a42670e6d8813","up":true}]} \ No newline at end of file diff --git a/swarm/pss/testdata/snapshot_2.json b/swarm/pss/testdata/snapshot_2.json deleted file mode 100644 index b01ce303802a..000000000000 --- a/swarm/pss/testdata/snapshot_2.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes":[{"node":{"config":{"id":"73d6ad4a75069dced660fa4cb98143ee5573df7cb15d9a295acf1655e9683384","private_key":"e567b7d9c554e5102cdc99b6523bace02dbb8951415c8816d82ba2d2e97fa23b","name":"node01","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"6e8da86abb894ab35044c8c455147225df96cab498da067a118f1fb9a417f9e3","private_key":"c7526db70acd02f36d3b201ef3e1d85e38c52bee6931453213dbc5edec4d0976","name":"node02","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}}],"conns":[{"one":"73d6ad4a75069dced660fa4cb98143ee5573df7cb15d9a295acf1655e9683384","other":"6e8da86abb894ab35044c8c455147225df96cab498da067a118f1fb9a417f9e3","up":true}]} \ No newline at end of file diff --git a/swarm/pss/testdata/snapshot_256.json b/swarm/pss/testdata/snapshot_256.json deleted file mode 100644 index 4397a7ea7d51..000000000000 --- a/swarm/pss/testdata/snapshot_256.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes":[{"node":{"config":{"id":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","private_key":"c4e98d074abce07e849be2810e5522bdacf2489125ed7577e02b4809f9619700","name":"node01","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"0f81caba7d2f752fc670829f74565114bcf10c5b97d7f93252b467d0eee278b3","private_key":"d607b8638fa33ef417d88597579e00a2f93e76881193a8692b57d03b27a60ba7","name":"node02","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","private_key":"c4501aa97316685b3e707bd881c1d805e96430723fa88ffc60e1703485eff5b2","name":"node03","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1d942c85568f640497c46a9bd5a9825bc6fb05571203a3f0f14a3032602d81fc","private_key":"f1fceda785676921048d116f40e4d331ab32873f4b0343fddbd372c836808f2a","name":"node04","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"52881f1fe0b8e9044a3bc035edbbd6343b9c41d49c0f2c2ca799c2e764489b91","private_key":"a5baeb4f2e35eedde63d573bbec157e61f0c0ba6ecc7b6cb6a42759bbc165e5b","name":"node05","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","private_key":"1b7e31744236baa89eaadd57ec870b5415111685fda883490b5a0e1dbc321210","name":"node06","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","private_key":"344f9ff7d68ca6a04e3141646044db06e5761248c64a01bb6cc2813fb7745ae4","name":"node07","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"0592048ae4e6a3000f36e8e1c0e1328c26c0d4bb6369d6cd867a08d847dacb9e","private_key":"869c03072d8108be18544c579cca53ffe17682e3898f44baff9cc37507fc62ed","name":"node08","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"03f5f1be5c1ae35469333a5b08622372f65ef8f233746afde56d2647c5273630","private_key":"1fe668699f54728124bbf993215de07682f072d145dd8acc428f04abd4a46f08","name":"node09","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"0ea201515867c6793cbe48350f209e69acd3829b9b16b6016d965d84fb6379c5","private_key":"a3af1294d727198fb3282659a35ae5b27f4b23b28d76e528809b68a6263fd673","name":"node10","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","private_key":"f7ad4635d864376fa4536bf23df764278d30805556cbf13e794e0ebacf389172","name":"node11","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"14c81fb2fb8fa5a6b47288356ab486012046d09959b9ef4814a09dc3facb4853","private_key":"237183f9e7834a03859c43be2a49bb8e4f1c9c7a5c334958f74d4ed36dfbd5a7","name":"node12","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","private_key":"7f7c361b6989c77419ac69848c9a4b06255aeb0f6115454566cde4ca544af085","name":"node13","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"a485db6ec1929aaf695a84294f52905aa602584954e9ef71b4e18f7b847da31d","private_key":"00133d03c85e03bcd54ddefc03d9060009ea78ee5ba1f6f335111f47ab8f433f","name":"node14","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"ed1348b4037004b61bd4d6f9be1648112f835943a3b997b91ac931301311e007","private_key":"ee3f0e3cca3720aa0727efa92a11ea9252c7fe72fe64c8814de2accbb91d049d","name":"node15","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"4a828523731caf47f9aa2dfa20f8a8895e9a6562b3a034574639a229a733b4a6","private_key":"470d12a7459f9fa9f1adf18e94b60340c5c442eadb1329fa871073e64d6bd7aa","name":"node16","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"b45d1aee940e5d2c71db22e2afbe908d5967e437fbb5bd051edea86ea4ac9d30","private_key":"3450c9c68339a76f1f75fb1f770914dac1cfc5e0cb23d6fd703480beb3ddfd4a","name":"node17","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"3af3bb6c4343a19e3c0f9a89697cd70a3c79c64b1e40afa59b188c5553eed567","private_key":"1df10df7cb050098e9713c3773a47962a6cfea944b53b12fd84563c98dc46e7a","name":"node18","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"cb69f673a54a75de300d1d1ab989caa04579504cbcaf78f03bc192c9578ec5a0","private_key":"e699fe1e14d46a5a72d5696e397df71ac0d17abf136dc84d850b658e56881c75","name":"node19","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"237459ba37141f18b3cd40f5ec9cdaf6789fb87aa4511d489fdd98a960ef2883","private_key":"0974ab51554372614954b68ffb0fdde4a82efdc0bf4e6f802dbc3728cb4b5e47","name":"node20","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"15f6bb0640c3ac8ae8ecf10d0d46cc739bb6f86b0c0cf706103aad9f3785219b","private_key":"14ce7db5594270c24ed48fd5881d6f02b9cc6267731612c5117e4d273d3920ca","name":"node21","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c4844b8046d1c47f699ee1c232d945829105d45af26fba722ce4d0a937c99631","private_key":"32fa52ca7b60e6953e97635659e4a5153688636594be7f560fc5f8468fdf7022","name":"node22","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","private_key":"d28563bbfb6db23928a66e837cdfa794230537066eeb93063ebeff3f531b12ca","name":"node23","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","private_key":"137533f5c2f9257d2a60c22d205407f3cc14c52ec60fc34666b05dcb935178c7","name":"node24","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","private_key":"1dbab5393fba98797db2a32f7d2ee6ad019bbd9e26ee051313e566bb1e21a1b4","name":"node25","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"d6f37555e9f35a3ef3b8b4d5c18cf0702ed99c1c8b5b0a8554deddcbe542b88c","private_key":"87a7b7028ee1140af69055fb641d23473c44f238544e3cc23e2909a959d1e091","name":"node26","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"dc3ec6529cec19709403e473d4546ba07f7aaaaec5be7cb0618246a4c0458944","private_key":"102d3a405cf636abf7d0b4e4a1fc0a698dc0d4033c288762ce9cc975b91db032","name":"node27","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"7471be56a2f617783d5a1ae4390794ad24f453259b7c1f8f65be8f71c8bf8bf7","private_key":"d8ecd7e813dbc7683f773cf38cd0e344ee9b4e308f12f557b6642eda2ef88ea1","name":"node28","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"290fca7596740129fd1a3075bbf56885d8c83381f981adb0fb283799375a3bd5","private_key":"225f43e03ffb8a97b760538a5cb9cddb61e7a387a3e56e82160300ed8c53e073","name":"node29","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","private_key":"75acc8059053d23505c4513dbfd60777918db43c9713b3577c36836f066e31af","name":"node30","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","private_key":"d6bd6c72597f6ec178becb6cdf6520d7de3f1278f77aa42095d287f45513b1f2","name":"node31","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"f4eee71a143722ea8288279876bf5687c1ed65eede0129e987e53f5ec76d8543","private_key":"b329bd7d93dbc0fef82737292076fb91e323da4f34d22a5ee6fe311018203992","name":"node32","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"b310299bef4f4e84a1ecc960143e4b1bbe98cd4eb8352458c477569ca9994c20","private_key":"f62b2e6c0ace2c204b4efdf62d3a9e4e41740aaa2a7aee72aa67272b08f65388","name":"node33","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"edca23c0fccfd1ec66f4d39bb129462776b9091c2337f6afbfe8737a0ebc0d7d","private_key":"5083c0504c95867fc5a924311192eeb35e80105fb25720516a8af8053516b87d","name":"node34","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","private_key":"52454a2b364cf029dbd0bd0f6880fd3c4a3eea2ef593277ca45c363888b025dc","name":"node35","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c15d13aff7f5694fb8fc2a2154846ac703064133feb632e28830777bbfc7c016","private_key":"822f8bae08da0af5c00b04adc4653e9b425648c538f482224cc7406ed46694b4","name":"node36","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"7de7eb0b73f3b02b998e592e0fc51b26c0c3ae6695d41897874d2a3d8ce3554b","private_key":"1e41a399b1b77f559bce8b0db22cabd26152fde5eab7c91576e4a2e00cbf3061","name":"node37","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"7854fb9f0d2f016653994b9e23033f27e21a2ef186a352c8d068b75ea20beec2","private_key":"f39163a2c0a70f6eb02436a58c6029082e040ee88271fb27deb0e9c61af2a409","name":"node38","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"e67d72c8c72bbc7bec1d4e936d65adbeab75d1b6cafdf2c39e38bfc723cceebf","private_key":"30358622050808cb05e6c497e4ab00bc0baa126282c0f0bd38a279f15161ebfa","name":"node39","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"ecd2b502d4e9bf77369c62d1142a9fb4ab1071e751cc6971e9e381e953b0fc12","private_key":"e96ccd329e3ce59cca1c371e0d97e891c8755d8285c414d227fe2d8dde438ada","name":"node40","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"4a670fe61711307f7781b3b5a8014fc0e63866efe356a25e61b2e768a7389980","private_key":"a8af10b8118821f3f7a1c456f857e4ddf50526337a38eddaffe15bbbb383ac32","name":"node41","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","private_key":"aea855db0ccd147bccfb6969c37e5ceb12623a95cde33c7725d51418c2a58e56","name":"node42","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"265d7fc553fb8dc016d7c6ccc31fc372912a11e9e8da0e5822e371a91d82e857","private_key":"14f5b342ec3c67c89537fc4dbaa64da24b8d7d02242eff9642b680d1923a000a","name":"node43","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"bf5ac6f9b651895c8d75ab3ae7994f91981dbbcdce25a7cd718fc996da96946d","private_key":"b56fd7d35ab0d1f76d30c898f6794ff5399b2d6982c4d5afadacaf72aa535bd4","name":"node44","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"4cf655b03778bcb41c32664f13168dc32968354468c8aa699de52b43a2e0dc7d","private_key":"59db345b1123f497ab8804ffed255e26dc028f68d9010314f9eebee243ee74cb","name":"node45","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"df5ea72c0c257bbea9b43901a1e406bf6055a307d083367e7f1d944e65c2b3cd","private_key":"87b0f18d2c52b3f0f6934c09421248a6d0457eab81b8dbd93840877ba7c25006","name":"node46","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"fed15b57e749c5f8f5fdb382eb95abe2ce0dee184c817e557122207f3729b05c","private_key":"86f6a5532361ee4f4511ef6ced21fe2e8d4e12b10d61b8d16f6805d5d6ff869c","name":"node47","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","private_key":"604cbd4183a23b452f0c9add6181effac6084e4411051850ed0bc4f1ae9a33d8","name":"node48","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","private_key":"ada9db22cbb971526fb9a1e050a039ae3bc1b898086642c093f9d2fd1b4a2e30","name":"node49","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","private_key":"c3e1cd6edffbca6121b114763c7cc8d8fce9b4444747af55b56cb111f3803b5e","name":"node50","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"36485615576c68702382f614054494cc4a40b90fd66db86b9d86a6a131dcc6ac","private_key":"e74e4c545f1d04c35176065ee8e6610e9847ba30aa0b4ff9df3d574a9cd04bc5","name":"node51","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","private_key":"d44b65dad6f0fc7d3868207b4d13aa646925f53eef21981d7898ca8eef1f47f3","name":"node52","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"baf32cface953bd22141970b691567ed375f06043fe31de354598fb4230fb0a3","private_key":"5c0b688fe7738cf3e06e8f932ca1986d88f6c00a9f705d50335632433ad7d52a","name":"node53","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"72acf53ab7dbda90ea70e78d001fe21e7ab88e4054ff0453212e33a26fbbf7f1","private_key":"8d78762191955f66c6881143b2fbf367eb02b6182eb49c5f11d22381f8e34152","name":"node54","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"13d8107c669cc27f6cbc64cef6b2bd11f72db297f2857c2de7323b92632b8231","private_key":"296788d95df4ddf6af02ab317a50c417d36515453b5cebdae7c71f55a657c7f9","name":"node55","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","private_key":"b229e6aab9cd866a0aecbc7358eeb2986cdf2ff1bcd25ae6d3dc33ad282a03ee","name":"node56","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"88da9dccda89c111dde40318fd1e5239e77b670f5a1b88a3e9320b7350885e31","private_key":"016f837cc16dfa3e8ed3519599b40bf8ee03872dbe01509e3928f5459be708d9","name":"node57","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"8163eebe696123817a6d998d7781612931df6fa2fd0293c0d0dd395d031c53f6","private_key":"fb9bd6643165414424261aa1dfdab87ad7b83e52d9bfcbaa69e1bb116f84b19a","name":"node58","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"730749922ce3dcd8e249b988dbfb950c15473a2f6fd5cfe2a8126995fb1dc186","private_key":"c761dd84c3923763ca0acfc07e4939b146d57bd6b42efd6c5e8e5c0b63dc1518","name":"node59","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"2f2284537629514837d5c2296d2871d8c4c147719f0ddc28ef7c629efd44dd61","private_key":"4503d268231fe7b4f60290da3c7f7f67bc51e123b65b7fb48f8ababc2eccbf6a","name":"node60","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","private_key":"c643d7712e7aeffa425505349e837da25e2cae551f446e56a96e5b2df48f2bfe","name":"node61","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","private_key":"cc7a5a2b92089562a184b024a3782da10d925002ca1dac6c95d902ed4df95998","name":"node62","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","private_key":"31d2f152a7b173892132dcb790d697cb779cc886a67355b5907e803ec734a1e0","name":"node63","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"fb93417c597050f8deeb3098475f6cf0d213926332b59c25a538c1b4e6af89c0","private_key":"f0045a14d36d5d17d8859c51edce3fd7afd083b6722cf6a3668dbd4f1db69e17","name":"node64","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","private_key":"f00404704ec4c556b0ee4cbbc1b8fec0b741d8a587b0baffa9a0ce79648f6560","name":"node65","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"f9243ad864859edd351592d24abdf5c9c4e37f4ffe2ae1201c45f26576ee5725","private_key":"5998a146afa95186e2ecf2d1daa4376812bbcbbf22809ba0807dc5e34e5d1e9c","name":"node66","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"e8392bdce92a7dcfa5e3196a6e1d4e5dc4814c72e104d3e2aa1f1ab99e2a47a6","private_key":"542904a26056c4d86aafe32a8dbbb30ef4a31b36c81563f47a8ce22145e5da4d","name":"node67","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","private_key":"4feddb131b7fb4c20144641fd72951bf356b4ec190a8a8cb322f2d0819aaf317","name":"node68","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"f4e0d91ca58ed616918bf268565c2edb4176293e851b8d29cbbda55bdc608534","private_key":"1fa8a1ef0703e81a32dd0faf6d25447a183ecb90d6994f45921c3db0e8dc3d06","name":"node69","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"4019ce1dc667d90a85021b5fbf49ad77c1de7fd3443a40a17bdfaf5175efa1e1","private_key":"dffd7a3c7cb1c3a0d129522290be15481d609d4d268daa13364cb16e13213398","name":"node70","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"5110c7e54a18df6963919866e7f6c0c3a483045d5ac20fc0dfa5e1926681be7f","private_key":"c5ab7f8e52d35ba6f3aae971d7215e9dd234f82a3331ca904c2b6b526d2a59db","name":"node71","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"d68f504755b48f8d41570169dd3544ef1caea2ab1bb334f8d8c5fb27fe816f01","private_key":"07c9b7896ed7f5c9763b72ab6631797941cd615c3ecd431de30169fdbe89cc2a","name":"node72","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","private_key":"da65f19428056c9c512efa6c1e97a409861ab28956b2c9ed0b8a72bba67c010e","name":"node73","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","private_key":"3ea7d8647b5a7f04bdae56288940cc9dc8289c49fcc4a1a79e9d3fd9a6ceab2a","name":"node74","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"5fab772fff883d04cf7e4713266fb1e98fa1746e12faeb04e87bcb37b8b61187","private_key":"86dd972691e02a4562fde8ab7cfa9ceb75201e42e506cf2d51c16d9d474bad2d","name":"node75","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"0f5e34b25f6851d0f3b572c64420de0aec01754bea0854bd7e9910b4b1cc66c3","private_key":"cb578a8fabe87c91b214032286af35c973b3f27880dd80fcd1efb929d1dd4756","name":"node76","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"d3d254a3024a7aac01e97a5f0aff13756741867e599bc94c068f0d1d19ca8176","private_key":"d4921582200b69793cc182b16d32031dd9559865007b31b1011d35512379ae8a","name":"node77","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","private_key":"286143c4711912830a900f8a45b11baa1bef2b6b96e9a918484cb95aafd2164f","name":"node78","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"92325e52babbf2b8ae3c37beec7e440d11efed2d103befa2c4a969315a2eab56","private_key":"48d763e65d8c5b6a83609844cf203d410e1afa134af6eb6ed22a42bbfd55ccb9","name":"node79","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c0d1ac430a1466a1a28cb1aa3d29573ccedb13641ac19e6e615f2a96e8f0950b","private_key":"98a9e2aaeb4c53a781d4150faa14cc0ff5f066e7be28098cbc25a0c379be18a4","name":"node80","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"46c5a266dd46041a62f21946290aad736d2298277bbbb16b1efb4490d9d31fa8","private_key":"97df41163f6dfeb248b196fff24b95edb55e2b6c48c551480226459fde0ce62a","name":"node81","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"275c93677aefd92ab38f9e5b5b4d29f90e809ccaf77b088102071aba26421f3e","private_key":"887a82d3553c5107cd8d6220f2ac36ca2d6d499b8c82b765a17bd6295e9861d4","name":"node82","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"9294e5b39a1cd4230400e033da1ac1f79a4025adab41ce8c0e0e47dda2418cb5","private_key":"b067efa25d200683b69e2fa485be6ac0a6bbc34a18796bc0b0e794d4dadf83b1","name":"node83","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","private_key":"f2b10950651367c628d1675c7a07962c0af4062b2c3c8154075cf61f9635cd67","name":"node84","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"2e9f7d37106b59d6f411dfa0392325d1fa279fe45c18a101e32ece6716e88946","private_key":"55f59b8404f62f76682812c128cde0b37da46140d69661f3de90af132daa04c8","name":"node85","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","private_key":"bc5bbbdd85ae3b09a493f0b43542448acf2976654a249f32bf92eff0d414866c","name":"node86","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","private_key":"f342c7683da21156a1f53ce673f78414f580cdd86eda879596d087439e4475f9","name":"node87","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"985c14910d922bbd1556c30620e3ab72170da6553e658dd3806d773a734a1c93","private_key":"f9550b9a11e9aece642e0e863b82852f4c5c8cb6044144627d678ce3988cda39","name":"node88","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"6ea59b849a9754c022219146efc27fa6561e5aca8338a111847259695198267b","private_key":"111dc027f4345175a5a141eff93a8d04d82ec6d67a15d0ed0c53ae7fe954868c","name":"node89","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"211a516fb95d093eb43f4716edeb1e6f76ed79d2aeee3393740e244970187840","private_key":"30caccc4af13ac896656ca1add341299bb51773c44f91f3105a1564abbe84f5b","name":"node90","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"a749372cccb7001a480b9416c41305bd1d3bc417e15669ae587d4375f2bbcbf7","private_key":"ca298279e19e3d8437a361debb136898e8bf05c7946dd3ffdef8d267f1c79049","name":"node91","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","private_key":"d513af6f8087302defbc40f0f03c63b144c07a636b7c5c970e962d445232a6f9","name":"node92","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"b79fd26ce97e98e7432029dea6bf11e48ea9e8f2385dec5c60be0cbd51bbef54","private_key":"e5808b0004f64868225ce7362aa1b0d787d6c8bafbf5d08aadf4a804bfa4519a","name":"node93","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"18f9292fbe6e94ec3df4d0a896c71ed2b1771437e42d8475a7aae63c7992834d","private_key":"68c381bcacc6c4396824a929cd7124a13b8032185de88ce2cc3cb75badc6a5d5","name":"node94","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"06aa2f43956f978abe7b8f8f78a5a8cd59decb66ec54b7c81ae50c3752798c5e","private_key":"0d0d4c6da032e6ceb496ec71883cff52efa41a9383d2b83b747d1bc6f8269a17","name":"node95","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"2279d612454ff034032a5acf5a039477a11479b9b9a93edc26b61312e8d9b156","private_key":"53c79eca1a5cc6b186db543273722c0168564d7cecceb76d1433330101f8e62a","name":"node96","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"e3c95a82f373da4522533252549c1d68b6f7621a887feb66199cfd4b7a35b6fc","private_key":"cd3e3585e9c28d16a0a5c11e8efd66671e8cac68915eb6b7bae228e10e867fd8","name":"node97","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"bfeca898b1755a001eb0bcf8f5ad5713a550d0597f99ef7967ea4e7add8d3e81","private_key":"60f513f00eb15da948e892d69bec82991bfb0747c9d5879c6c26c2a5a6095365","name":"node98","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"f5dd00d94488f3387d64ccc2115cd91e5073c06de36d195a5c1a95db23cd5c68","private_key":"94b0c348b1ec7288d57eb195f114f38da5a6fc3f604e8f1ed76135ef26f50c6d","name":"node99","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","private_key":"a6e6ca4e3f494adc69bb1aaa01dfc3dd625d9923fa4c979ec80ee221b83a589b","name":"node100","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","private_key":"2c11691d22adc7bee004f61ad67b543d9ca22c7c65125427e310fc5c8784091f","name":"node101","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"458a31b8fdbc42f5e80f01118c9dc041740212c09ddfb4b137c9e7e93ae9ec51","private_key":"1ebabfc78e1b9d17e6fe38508bc354ab54be2a8bf57483b0afe7dc3530533e0f","name":"node102","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"e4c3fa0700516da5650f794067eb1c90ba7eaab2b65019bf34552c26f8c1d9a6","private_key":"9b22a93b1dd1ac5ad34771c2cf183292f9ca7133b4ed8a1d0fceb889d6017170","name":"node103","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"3f3e66f7c8fc7daae8a7a84a9c90f1ea64faa43930f36fc8f11e2d7b751f8ee9","private_key":"b4d7978eed053b73224b969ad03abd7503081764e86f7815f8b650b7ba9a34b0","name":"node104","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"167306fb0e24ddd775c2ac0722103ce5db98bb0ef60159b91f584475d34cb5bf","private_key":"18bbd14788534b3f7490b55c2243e84c1ed1d158bd769a47fdc2d8550098ba97","name":"node105","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"77ec3a73ce1d597d307e87ea8d026baae2a7df4d288a657f240eaaaae7fdb456","private_key":"ff55b25abe67052213c916a734949cb6a98d1ad2f240183bfa1fe694cfa0937a","name":"node106","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c3f356325eb1e61f2cfd8d80d085fb144c6887e1d710c9d133109b84b823425d","private_key":"406c029264f74f39f2ad851342bc311d4800fe07db744f69557ad9e3e5899aeb","name":"node107","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"7294223c0ab27851df8c25418acf09a28efa95fc238252af8a68390db07b77e4","private_key":"9f9be5e82bec360e52170374b35e26ab30480bf5effa10bc43527c191d1efc84","name":"node108","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"9390ef61753fcf9dae5e585a85650287fe1f792538721a2cf767adc44c5e7ce0","private_key":"01c46dd80b68bfbc5916277ab36142ff3033b126df71354bc7b21993be4f27b4","name":"node109","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"9c0c7b6da33520440b181a5fcc30aa8a7576ec9c6f64acef0c9525d05a2ee393","private_key":"d4ea8e3d466c3fadc709252e0b35240e831250311a3023363aaa2de0d4068efe","name":"node110","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"43afd835930eac91c0c8947001dd60d8da9112c19cf3f1356589b58b48962166","private_key":"ff786dd6bdf5a54dfd71c73ab93427d94008f2854eec2a87c96223aeaf5a2357","name":"node111","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"b60deba7c8676305b67af41d2de866bddd72edc0667d427e51fc3f2e91412b45","private_key":"81d2fd2b16f53fcb3749b3c8575a00ddf39ee32f760cc7e8365c046e364ec849","name":"node112","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"670d1c31407b4f34bb955031e9a3ab7980273155080b459a082687d53a3b42a6","private_key":"5017533627afd71f2684b9ef264ac79ff826f1cbbfedd788d969d9ae1bb87b20","name":"node113","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"ad36b802e9b01727e516879c0055e3c46b810845794f7772ad85e8062111b167","private_key":"92a91e558f70fffacaea7b5c86540ae940da57dcb660d8c3e6eb5c7b38f015f0","name":"node114","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"b4c7555a38d1d4e3bd1d01bcc47a84efd6128e8cc0fd803362da8b341f984a4d","private_key":"91f18de340c4916711d69fea368a4248eca8a13910d576e24ff9125fbccae3a3","name":"node115","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"ed6570e63fc0e2e2d6290645c4339b70a0f1c14064d286b4e0ba143607bd70c8","private_key":"f1e5638e4912ca0ccd446a27531942b75039807eea70118fad6880f5b1ad5ee7","name":"node116","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"29fd4473f1b6c37f8268d73dd5f683a0f7eb64bd793dab2c450c10d8ab0b666d","private_key":"83bcea8f8c409f9470bca240d19fb29d9fea6cf94435ebbdf0a8faf5f1cb5ca0","name":"node117","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"74062d9dd73e26066fd44e4f6c696f271c9dc6860bdc5ba3fcf447f455c8cafa","private_key":"33c3e295cfb9706d4d5e081ec3c220d8c35415d7c256de99511e76474e8c906b","name":"node118","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"7ffed78e11c71eccfdddf6a8e5e367831b26a6b11e4d3b60c5b9fc50f4bcdf33","private_key":"af0dcfc78e50f0893cd504a3567a8515538bd85a1d3eb72809dd503690d0d274","name":"node119","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","private_key":"8c806f6fed9bc74fb07341b080bc3067c953b5d6093ef5779221924d4ead4bb8","name":"node120","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"67831469d7f9b779df4318cda9d4310c53fdd66332af3740e654233ceb3fbcbc","private_key":"a1a11eedc4fe78f42dd23b093d9fbcbf1643899eca875296b0374ec0e8ab619c","name":"node121","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","private_key":"15d8362248798e68c5eda882717ee691573e5477b7f0444222fabaaf1a025a8a","name":"node122","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c7230d8d56b65a66ab721a8268ed436af78210aae7d068cc2b8afe39b6e61c81","private_key":"c1d6cf53ce48953f5b6bb0e0b644aaebd16b84a3910894f93c157140c88988a5","name":"node123","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"ae715a40e9bf50b4eb954c6b87efbf1a6e7abc3da6d84055e4591aadf5bad5c8","private_key":"a67b1d8e2abb33c866d215b81af3a23fe0657a9155a8e17754bc0028dcf87852","name":"node124","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"f1fcfc064446a432ac7a21efc386fa3d053d0ca4540fd4ddbbb253ec11f9ce94","private_key":"b9da682c3a119f650ebbaccf2974166f3162ce600afd50152f95e9be3f688bd4","name":"node125","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"7d45f1e91bcd683d870422b2f591c76f6ab07ff4b079f35c280ce46f7fc364e1","private_key":"a6ca067c4be7a67d6c5b14fe7e0b62a964d844462a6b26a981cb73ffcbb48e46","name":"node126","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","private_key":"08926af18a3a13a1bf786aa6946ab5bde52c531026a8561524925f1d9f0d665c","name":"node127","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"32dd86f3f5c3abe55d6b1fe352e9d1d0cc63d61cfa60b8e70b2ee9db2e28e1aa","private_key":"793a4b0ec03ee3d4c1cc8fc8084366fc20e5852ecc3aa96ba2882babd7b8ff37","name":"node128","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"487edf32027381256a864bc2e9bca175f7216cc1234731bd4dc90d2cbad6975d","private_key":"9d2fd418a2966f748dd746ca5b5f0c3a82496a0a6274355c059d5f48be6870b8","name":"node129","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"d6d2d35b3819e03b39d7146c4dd1b53fb7b70681c8860464650d5eb2d09dda2c","private_key":"7c7cfd0cdb3cede7dc5d152c6f5a8d89941656a3e9e560cf993a319c9012f074","name":"node130","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"9fee945c89d6bb59e77a24183c2855f4eece0b5d3266f750ea95483eae1e1871","private_key":"457954e43019a3f3e510a0f818996c28e372410ba50490b5042068ff63f3e17d","name":"node131","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","private_key":"d45a6d15ff3a2073f6d31d9df7fe6778cac0ca1d62aeacec44341aef19924624","name":"node132","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","private_key":"c1e5c2bc35a1030f8bd3ffd9099376d32ca3029eff92b5c79055ae2454a6fd6a","name":"node133","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":false}},{"node":{"config":{"id":"571638637156bdaf92d9c715647a1cf64e91877c2d998658b7c6cd27522271bd","private_key":"89da1a80c7122d19de9b63637b1f1675ee7e009a5ecf1f6c51cb29b2a09cc908","name":"node134","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"31edba65e539a9a3c7107a131c4fb4b5a8e2c2d4e2db7874a0116c173961e27c","private_key":"e5596012d345aff602e83361bc5fb1f7e3feee7b23782a8c7f0a1c7933ab928c","name":"node135","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"adfc03118406bc5af4e3f3c6c89c3448c3fe4f9df20b15bcca9bbacf58ee5457","private_key":"91af9b7387bad90d696b549270c0302fbe3805efb01f311e801b317217b92cca","name":"node136","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"79abd33d150fdbcbce6f55f3d101608b80e03ede7e4627bc06ba5c1b001538a4","private_key":"af83c717380c5132acd3357ec3e29daaacfc4e4a65fd1f5b14479b78e5fb01f6","name":"node137","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"aa504681c991c69015c7ef0469902c44fc4e900356b181aa252a1280ffbd2e3b","private_key":"2645348387e283c7f69d634a71ee38a65d4fc6928ecc383fb25cea4525fcdad9","name":"node138","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","private_key":"bc25724231b7bb54d17219feff13e1fe8e0486cace91c0cc6f3731a986f4a8ae","name":"node139","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","private_key":"1b90feec9d475fc13f1394c4b39d837fbd09f4c329ef5747d988b17b84967ccc","name":"node140","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"a6720426faa1ea7e2d55c1c5ad05ba2e8c534d120ebb5e1375cf769d73191708","private_key":"392b881dddc671e72fc89ef71b340f19840650943cae22682d6cb6f97570c1ac","name":"node141","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","private_key":"f6541fa1eb8508dbcfde0259a988d0564c192cd25b2051e1299c32cad9ceb149","name":"node142","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c99c6a2a2fe7f4613c8d918404c956eae0a00fce544c9eeba5da640e5e958874","private_key":"59014852ab4f95ef336b10bb7c05d22e54eb0ea453d0f1c56638852ffac3aab0","name":"node143","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"af358f626738d2e5d17060abbaf24cb0124a48c5edb8f3baeda30bea7b403dfa","private_key":"45d467a8320183e6e9f9fb0219c71ee08f43352c83c0b2d84da4ae4241b0173c","name":"node144","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1b7250dae1f2b07504b381d28084b751a26ac791a1ed842ea5746826ab85ba22","private_key":"3c564ae34741afc14a8ea217a734d5a8bc6d8dfcce3f4943acff14036edbf1c0","name":"node145","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","private_key":"43f552096880ddd297dd590b83f738fa13826e6120ec3d6311ac565b78a252c7","name":"node146","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","private_key":"177c7e7e8e870ff8b4b606ee3bc6f94d6fa57fd6deabefdabb250776939ef9f9","name":"node147","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"abfabc2964ce2f7b8b3702e897a8cc83fb0c009d5af9670b889db582e57ef7c1","private_key":"26c019de3883a787fafed3839e768c71553ec8fe6cf607725f0e2acc80711cee","name":"node148","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c63e796efdc44712403199c11c3e5ff28e5d6a4d9ecdf3e95620fbd6afb1bc2d","private_key":"943fd9910b9fa6cdb47ec68ec64b2faecd9a56de487fba1b7773a6dd54f94664","name":"node149","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","private_key":"f75e19af5d5e340e6a07d85042608f0500511042eb2e1ccbae8fe8569b9cbc8c","name":"node150","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","private_key":"e400c8293b9474c5aa84b48e37e1f435a53c910af98fd4e1b23ff9bd670bb51f","name":"node151","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","private_key":"f5a6a565c7c14cba96a75712373743d09ba804a9b8332a667492617dd5211abb","name":"node152","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"7dc63c8146ed092aa4c9539abd112dc3a703631e364a62fd74b192eee5778123","private_key":"4f592401cb57bcdc263bb96492ca66258130460b5fcb1a1cfafdedef3cee99f7","name":"node153","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","private_key":"97486d2d37010143a830ed6a0be6528a5611fc42962ae80e44a12c9b3399502c","name":"node154","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"604c6bfab4f5d16d5b786318cf8f9e0197fc18e5d8a8e6f4fd345e653d2abe17","private_key":"d93efde40fe67c2f412577aefbc6af1e876bde81b53c22ef6da4a5a23c8c13ad","name":"node155","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1a02108d9149739be6ba85ddd1e8a3961adeec38f77fcef95ab57f8f2f39f4bb","private_key":"eaab112f5381b5b84ac9920fede88f2e04b725398e37a0b1b003442c281e32a1","name":"node156","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"34fce2b8208c6c8ed005943b3f2cb9f8923739ce262e2040f7ffc048093ef910","private_key":"f79356978056456eb8f10bcf3c06b107a4afb4d5a8c2fc9380011a7420d59c81","name":"node157","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"4f90f53754ee50357491dbab7c3e6748b40133e3339920b32f4c3457f4258477","private_key":"f0c66841f0adf1a9af04982fcce0c38c5012595de449dd0a8ea97ba06d5e43e9","name":"node158","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"52619a523050aa8caa32448e59721a76dc3321dc92cfcb1b05f1cec5b79d424a","private_key":"cc124633e255f1c6ba0beb16fb978958ed79be0e0f6ebd7968ef82b6d439fcda","name":"node159","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"66100e4f9d103d935164f39ea3f0332708f3825ebcf31ac6f8c9714c75a50860","private_key":"223b2c119cccb5059319a4a0305aec9c1bace6e731215f699cbd4d9a056ec777","name":"node160","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"4fd6a4b99b1ba76775594673421c8eea175c114519c8554798c2c364175bbaae","private_key":"158d4468a6b4e7413f8b1d4112ef2b2c562e6dd26101b022ece2fa57801e6b03","name":"node161","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","private_key":"9b9249eb2418f61bc1e6a582b28e46c0d25eecf549e98414e2ca5ab1d3f5b1f7","name":"node162","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"3e85d2d3d6750f84500fcf523743e0c242943e69188cb83114adad1787e6c089","private_key":"92f63cfcb2341c43aee585f9b965979e49d307de204dcc09b8e869f4e67640b4","name":"node163","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"4d447ea80bc126e57b9fc91203a310c5f4b242df5ad70d5087d16697a626e601","private_key":"81e7d25a3c5700b592d3ea4cd85440f914f919e65f0a6e55c99ba619a519c70a","name":"node164","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"388d46d6ea3fd92aa959b04d597ff1096d6133f171181c8fb54e52935b62d33c","private_key":"09ab58931e7729bf23cdcb2772b3869d1aacece96070df5790f8343b70ef5e3b","name":"node165","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"3345e03880c377fc1fe7b6205011ce3e32525cefee5e03161105f1512893f9fb","private_key":"c330a5f7858f47d6653c52c88207fd10c6c87d8e77c87b9be95165f094918210","name":"node166","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","private_key":"7bbb910a655225eccc1fa6ab5abd3696725591448d5b628656692c1170f1f095","name":"node167","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"043f24ce87fbd01f6f663f323ac6c156810a67b12efbec2f219ae9a882a4bd16","private_key":"9a0adb03a36d31dc716a280bc0b0ccdb5a891c5242f507aed5f6c370ea6bca05","name":"node168","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"f78a54ba4b3fa75cdb542bdcd6da0d49986a5f7e7939959a51b3b48a87605654","private_key":"c9271845738bb80612a262956270b72e5152311e2e80fe21b3c7238f394911a7","name":"node169","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"9c01d016aaf11f9e7b048ba10cea3c351d9ab5c8c66e8605f78c06772708985c","private_key":"1710672c4d27d5363877ec9be4202445a3404d3e16ff221611f0c2d82c34af1c","name":"node170","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","private_key":"fe3ddcd5732357d3e602b35b79a21afd716fadbe7b569e44eb014dd4b944ad49","name":"node171","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","private_key":"85d961e31726c6d75913c901b5db7e115dd67338d9d89584e0c73df8f673a01c","name":"node172","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":false}},{"node":{"config":{"id":"d8222a2c177f0cd17baae28327af5c13f2fb551dd01298d17855ce7f6f4f6189","private_key":"ded7d4f338d0e906b79482b6a79c0a2224820bac1893e3ee083a66eaaebea363","name":"node173","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"353875d4ae2b9c5cc6cb14059bdfb9540647100c7c3334bc60db2ca2b4ddf3f5","private_key":"f2b75f511327f380d86989fed2d67129f32bb0aeafb3aba0250c8e9e5b581d16","name":"node174","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"89ee628583b6e0345ceb312c2e2cbf5ac410eb00531be903a4b94b60b639993b","private_key":"a9bc40abaa967e683f994c9d053b90fa4daa9602fc810cd974a8f6a3629dbd28","name":"node175","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"95e0529227286d442cdeeed428614f99c19f9223426e6f6cefbef2d9d47ce96f","private_key":"fa312311679e17f36872e8c75b78bd3b730d7423c613f5d7cbcaf653d847419e","name":"node176","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","private_key":"3532d20f2d6b03b910ef2d4dc968b71d89bd0fa3c9a758fd355ec3f8c9b6b62b","name":"node177","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"14418557af44e33fc8d46369426c3e2fc91622cb08779b3fefce706969ec2163","private_key":"d491504fcc40f961febbbf2089616ac2a2b7cc79e5dc9c01b632ab9d226bca86","name":"node178","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"004e812995bc04f22993466df9889ca1b650bba242cf80bb070ce9564e7bedd8","private_key":"9653af0c5c528f1079ce38ba8e44273a7eed8efe91036d0219e08121fc62ca06","name":"node179","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1d5fc65b4121cccaff0d2a853c6bbff0c10b4ec28cfc5ef5cbea8bc28bc4cbec","private_key":"09d927a912f0daefdca0fdb594feea4e25c384ac07efb663ea46ba893d0f32f9","name":"node180","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","private_key":"b5c4da646e3485b765e530ff19f36c4753764dda9d4aa308fb8d5c3d52d9b04f","name":"node181","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"86128689b03706f9a86baf4753154afbb0c535f84089039ef5610d82fb969a6e","private_key":"cf80dfc2e5888e69aa7c570430d76fb7a11991c8f8bd4c8dec3e2303085624ad","name":"node182","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"245495250d38095106022d3fc905eb7eb6f54adb2cb0bacdda0b305974eaa50a","private_key":"33a68fc227f5f745114937915a4678fbcd985ff8c589698f241091a32fd901b2","name":"node183","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"a80b39cf280fa80b323370cfc37e8e3ff38de47d5de445c500f36c8cf2390b53","private_key":"ff80b3d224dd48711a8b71840a9762289dbcac4d27cb6c24878fb4dd01b7c55f","name":"node184","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"86f71d23eb4efeeed4679be7a3d0f58f98b83d23b3e0a8b73f60f1535b51f25c","private_key":"9b8f03ec5acc438bfb8ef1d604066899d9108b46efa0136298d820aad5752cf6","name":"node185","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"123fe1d442c7dc905a7206e712fbd9cea640295a6823b39b1945629ffa89e895","private_key":"b3496c97ca4de82c4133936c457c24ece46c36d35193ff6a5cd269701841cfd4","name":"node186","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"be0ab3ddc656568900d8e1f42394cc942464596b7565db40f6c5a0c24ee942f2","private_key":"65f6b15bed8bfb72144314f9aa2a7364bcb356e60cdb0212a7d04a3eb9e2d3af","name":"node187","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"99fba7334f47bfcf21331d6179bfb2eee2ecebd1ea23fd829c542844b10ed974","private_key":"fe832e4f1c7485f3906f1807544431a825ca6ff8ea89d3d87a14aba92ba4d995","name":"node188","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","private_key":"50f4adfdd5287853b06a93a0214c09ee109edc00698de65c0c0523e10e7d828b","name":"node189","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"47f95708475c9d61ff2016e7707c4a3796f6c87313bd1d2bf91aa620113ba5f8","private_key":"e40b799e72dc611e2a8f64d5b8c7ca95535499887a0eb36f5f4f3690097e0ce0","name":"node190","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"def4f0b988942a8699f6b500cc2531c7dea065e3dc5bdcbeb26bca8f00e1d3a6","private_key":"11535d8be8b31e5bf636e9671c7be140d596984e31eb44adeb0002976fa05b97","name":"node191","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c4632e164170d35e91fce46f660265f73795f1d840effce5ab2aae18c73003d7","private_key":"9870e0dfa67e07bbfca9e8eb065c1085adc82bf75d9d75fab9909f3681b654ed","name":"node192","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","private_key":"01d68cdd16950c65e5781ece848e201a6ebbd5097f74dadfd7a5fb007bbcc11d","name":"node193","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","private_key":"7d7d75371a52b4d22411fc3dec135a945466d9fcce8615dbb959ebaf62bcebac","name":"node194","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"7d941474f90981a03c95af4cf68be163d01a317e202ac0db82e25f46fc3db8aa","private_key":"cdcb88f25a626c1434db6d9ee8ae5934f466813535a8b9425572a888ade1bd98","name":"node195","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"2af02e62ff3e40be25d22c2f745a505b895b3804a80663cb53db4edd5365c568","private_key":"70186f9ea20bd1f26270c4a3cc72d7fd6997f6ff1e81fa580ac396cfb8a53d96","name":"node196","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"6544c4a403d9809697420ff9840ea9de0c9ba6491de28dac288db6257f2dc218","private_key":"23e586de5f3e3d888e4b5afccbc7bbe9bc569233c4133cf8b4b6a6f722bcbcf4","name":"node197","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"21682cd736edfb8b451dfd9c55eb9ea3b7cf2e35cad32537f1f4800644fbf9a7","private_key":"fa2860804ef40cd74e911bae08fd20f7fef5ee4d34f163dd456f566b899f18fd","name":"node198","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","private_key":"1552e3359f865f955336c9e44aa94278481ebc3fba1bbac62a7e6c95d3348d6b","name":"node199","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"5fa8a5a29a47694653a5e55fd9b76e9c4f717ecc9b7088a6ec7ee273b291d49e","private_key":"da57c89729140a0f359ddf902197cddcb6b13a00c226d1a8a0975bda02f3a495","name":"node200","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"5f056f9593be46d3eba4dc8d9e0b93262cd3f00e007c13c4c011d53e7c199628","private_key":"a71ad1e471863026826e723cb60ef8221c29ef9c115f59ec22dccfdbf13724cb","name":"node201","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","private_key":"66b7f7a02087e6dda466b0fb6d05311ce95e179b4baf6cb7dd1fc1052f066367","name":"node202","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","private_key":"f4efac8f64908b896f893bb4b4113b00734dfcbf9aa87245f1aa8e9f65b644ce","name":"node203","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"ceee9ac63786ad68d884f5d018248ae1b7b60f0ad434776d29bd4dfcdc0f04c1","private_key":"f15da5ee626bdec9e7b303afaff488d87aff1815668f878ea6b1f270c6300ec2","name":"node204","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"3a4a300de1009d694bebce2362ca429086ade2f551989d55ba459a68efea15af","private_key":"ec037812b58da37d27db08df00018a39dc06d096116acf7f56921452cf7cfc0b","name":"node205","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"99aaaa7c98113955ecc0163943317f6ec08ae755e96c25c9f79ca1f529c60587","private_key":"9a037c145472a92a10b90fce8fe35501e93af7b73b026641d66a53ccfd3930dd","name":"node206","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"99dbf361b67d6c370fd4f201b059e93da50dd88cde2e25d0bcf3c73afbf13535","private_key":"14480f714ca25c522c067b4bce766945c9c2e8d0b697eacf9ff286fb0c26dac9","name":"node207","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"6d219b0ea4a1620a7ba051cc478da6df9ea443e87dde79e963ec789a5bad7459","private_key":"c756a2bbb4c7b536c169fea0b09c7834ce6ce6f687c4968e598b72606163b8f3","name":"node208","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","private_key":"a837afe73ba3598ea681339261b12f5b9f02f1ce5243d6f7d18c735562b5a485","name":"node209","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","private_key":"6dab6d6b30b7515d850f1f4e7d6fffc75064eafafd86b8754a4000153113b1de","name":"node210","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"9eec5d5357732a7acd26cc8aa0bc65f6c6069aedf4f3ab4a6790c7fff6bc2695","private_key":"431d8e4e06f15988a4ea9b3c077ca2b2bfc5b8b04135fea5ad7dee050940422b","name":"node211","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","private_key":"adf12e35cb550ed5a52fb5fa25fe7a6298e71b77a2dae474ebefbf2f9b8aac69","name":"node212","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","private_key":"7a94e705247608b3fe77122138a93f8477964e4a1a5d068091ff655f6ff0acd9","name":"node213","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","private_key":"c121bd7298c0130e294b88e6bb3b99fa0db790e9760b605448356e1fd89a3e5b","name":"node214","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"42c019ddad2fde0f0c7f5041eb584996976844f8b2e08e20458437578d0257ef","private_key":"562bd811ced052733cf87caf5888571e4482ae46e14ae1585debfb6b10298249","name":"node215","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","private_key":"8ebd5353e11b993fd7941ca1a936fc21799344607325c2879687b5e90adee6db","name":"node216","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","private_key":"8234acebca52619acf23d978bbc19bf2d8cbbc933bc7e18c7903d4b047471348","name":"node217","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"396b256b223f346a490844895b9f1046b4790d624af1c43ecae1de318c7214a8","private_key":"1a4a47a0b83da4f2ccd94ee24a3657f777cc93c6a58b8ec0145586ba2c161429","name":"node218","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","private_key":"5b5aede47f05e99b807a7451cec469c1f77786d45d9f55b5a797b82d76db93af","name":"node219","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"7a4118d2ce16e8258cc2cdf31c5891945e3787c46c0836fc916601584609b7db","private_key":"96ada7ff2fc583b6dd41941edfff92a81778c698d3b1f9fbf4130c2f7cbbec84","name":"node220","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"0210146e650132dd0ceb8cea779cafc5485822eeeb9fcc17f292af5cef6e9632","private_key":"1616cc42cae550c0104204c1c7f6ed0b3f65da627bd834a2d1239d70cc5b1e77","name":"node221","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1b1eca7b35086d8b217431c1d6a01fc07dc8bc816196a80015c9e163bed676ff","private_key":"1d029cbabb2eaef44889598c8fa2297996a69661d29c14035424dce781deb15e","name":"node222","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1e4441309404b0dd8dd9e8b1fe78708b015a0b0278ce86bcdaabc7d2d691791a","private_key":"83c0403796648d484818f74b9de3c755c56b24f69e3394e062dd55a9d7cecbc3","name":"node223","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","private_key":"3f64513ae7746b16fa9ef3978d1bdf3c87ab4842c75aa3b946385dcdb23a3430","name":"node224","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","private_key":"a9db544f2fa00dc2d658a531934db6efeec93208b76d5b6859e6f0c4abac116d","name":"node225","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"b71052b072cb98fb79151fd0c6044828e535a0547cace931183c7e4e7f227698","private_key":"d53b2fef355d63448b93647458df0ba5b73bc42492d376377401a568eeb4d81f","name":"node226","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c8fe626c60d6cd15912a22d2c5872f10a8f8aef5b6f6b2f1070edec6670bf8b1","private_key":"a04acf8d5198d0e7da1a6e9228c8ac6a74542a8d91906d5e83f1db219ae25350","name":"node227","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","private_key":"d5ad27697670f66cacf3e0ffb4473ab4912ed96fba311ddc8af12ad7663adeda","name":"node228","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"9a82d22b29c88eaca2f20a62e9e63675904065e72c70ce2fbbefc0c9d2e53e90","private_key":"dd217caf701902c94b57e1eeccbeaeddc4a7837b56d82ae2f034935d2828d222","name":"node229","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","private_key":"69b9f99ba6c47542a17c63be200102194fe3ab24084ea1e684033e68d580b5af","name":"node230","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"e787fd35b438dbae3d0283f7181fc4afbeb6f333201ad4cd1b46bf7094a6a85f","private_key":"ead9e2c8c0b3993304cb0a4a0dcb3ebe7c4331a87fee7c70194a3b3690413f43","name":"node231","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c98d389a6d8e6eb5a1eb5a6f235857d5d939d30e51c16d80c039c9e0eb2f7eca","private_key":"96cf35a39c0753abb6cc71c2a23e92fc936169a0600e936611216fd8ec31e310","name":"node232","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"bc08af284ab661dcc1cb23859271590e50a09ff8af8073769c812901ea0b9080","private_key":"12ecde7517662f6d425f22ce7ecde84688ab9ed7fd443de2f309f095857faca9","name":"node233","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c7703c4697637f40a50a5d0b656c4e54010ef1277b64d251ccd0752512e246e9","private_key":"bc558cb24210cea0443678c5a9a412b3512b5b965b14df80818039b6fe28fe06","name":"node234","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","private_key":"1f2ab03e6921c321f5f783a3105d69bb69a4c031d50195fb94324c3280d52310","name":"node235","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","private_key":"8f1333ea7bd671c671ba94c4c7c59d6b0687a5c475b21712c6a49600e71f78b5","name":"node236","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"b4639751d67db0b32ad1846996fed0d5c59be76b77efaec5f66e64df35eefae8","private_key":"e7fceaf57233e8a351a5a97e433d38131867965c883d53430f8f635f0563a168","name":"node237","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","private_key":"c5c6440cb8356bd270d8ce8d543b85c784f5c049b80e951c26fbcc92cfd1669e","name":"node238","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":false}},{"node":{"config":{"id":"d8b0425e4cb5134ddb2de5c05e0ae9b861bd67409c1446ca228318f79f5dd1f6","private_key":"64939e66b20f857b3b5c94f6e5f72f15ae524ce46058ebf20c98e796bcc608c8","name":"node239","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"f048e37427d68c4134a115cc7df6a8924ea95f24e7f2435372ae331e24b5b7fd","private_key":"9fa335b0c4f3fed78cb6aa4c0137589fe77d15b2127788adbca0633ef881b61c","name":"node240","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","private_key":"a36da338278d776a57dad648f8c5627834918fcaa86367e4aeaccefbb4142c1c","name":"node241","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"2fd800d224f124e47038bf140ebd486f1d0ab93169c998d2dfabfd1d3ced96ed","private_key":"dfbcc9238ce614f0a4711f04948064ddbd5b2997a19f1e62c8f9636e9961fd5d","name":"node242","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"194ad126c41c487d95c83b19ed2bb053e4d2ec0605952d3a396dd4304e8e363c","private_key":"ea0d5ec78fdcca32a257905b595a0c7bc8f2934d088b0f4c695a76927fa9e791","name":"node243","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"5fd02ea98977d976f5d47a4005300b9865b1dcdac77bf8a98a8920a89b57825c","private_key":"01bbc4df92bfd5efca325ff653fcab3ac6aca83696f57927d3313fdf03dadafb","name":"node244","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"4a81ee929074bd6d9932fb4d970f5207db8680bda0e951aa72a6bd64df45108d","private_key":"2e16fcbc5651872d962bf9cbbd32cccbef2adac9e86006e4b4e9abbe6c22d7cc","name":"node245","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","private_key":"a26a19f61e9c2c83a632e779651442713ae7026ff3889b0c2ba690ad206500cf","name":"node246","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1835f62fe3889f43ba7b4b577869ec764e96fa9a051f1301fb96795daa8b0755","private_key":"fd1c4ea6d2c07317eeaebbcd485aaf9267d81f2dc547b2136eba8ed02ae2c635","name":"node247","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"259d3874697a0c3192f973c620880b2a86567289f5222d07eb91a51867352559","private_key":"a655a637ca80f8c5354e51e46d31d79b36a8a8b44d50e2838a118ea8b33512ee","name":"node248","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"96b6ec2cda7c9e962bbc97e7916b92d69b112265c4456d30dade806e09fea62e","private_key":"02d1f6c1a867e9dd9cf7bff6121f3eb99e7c75866f8fb00d83782e69de6dacc5","name":"node249","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","private_key":"e0f98f6ae876455342403d6cd7ee64b21f5e5d691ed720d81e0db64529d8cd19","name":"node250","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"2013ce63483af2990b35f9ecbe5a7653c34b24ded173753575b24451787ed8b9","private_key":"53adafcbccccca52e7bbf9524234d7c6c5874e3a328d3ded48c93c7b07f34428","name":"node251","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","private_key":"b91ef086d245c3849c46234765c2d479c053b5974568062618a768ebb7014b64","name":"node252","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"daa2c427e8ecc566e634908fb0d47077d8e79854bf884342c06c4bcb355e80e6","private_key":"438a2cd1d35d995ba9e35fe1b4086196934e0a6087ca2ae18543512299224b1e","name":"node253","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"2f9f266a3b660fe685de2932de82979bd221706db1e2b954a93edc6609c378d9","private_key":"1945283cb814bb48cbb80a03a2660606e5c5e023e3b8ac887baf7cc912ec5be7","name":"node254","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"3dca1ae488cc497fe738c630465f987b419a5bb259fdfa17fe7130e238ca3022","private_key":"be6f375f22929e615c683dc9b07e4e2c609a06e07a849a718617c2a0c2ad49f6","name":"node255","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1b869ca5aec1ff0c9980b06b79084019752fd4557054e251c0091ee5d2c7d478","private_key":"9775c9bb0b01e8a605866b96d386a1f8c3dbba2605cb9f8089c4f85fecfb6c62","name":"node256","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}}],"conns":[{"one":"03f5f1be5c1ae35469333a5b08622372f65ef8f233746afde56d2647c5273630","other":"0ea201515867c6793cbe48350f209e69acd3829b9b16b6016d965d84fb6379c5","up":true},{"one":"a485db6ec1929aaf695a84294f52905aa602584954e9ef71b4e18f7b847da31d","other":"ed1348b4037004b61bd4d6f9be1648112f835943a3b997b91ac931301311e007","up":true},{"one":"b45d1aee940e5d2c71db22e2afbe908d5967e437fbb5bd051edea86ea4ac9d30","other":"3af3bb6c4343a19e3c0f9a89697cd70a3c79c64b1e40afa59b188c5553eed567","up":true},{"one":"237459ba37141f18b3cd40f5ec9cdaf6789fb87aa4511d489fdd98a960ef2883","other":"15f6bb0640c3ac8ae8ecf10d0d46cc739bb6f86b0c0cf706103aad9f3785219b","up":true},{"one":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","other":"f4eee71a143722ea8288279876bf5687c1ed65eede0129e987e53f5ec76d8543","up":true},{"one":"0f81caba7d2f752fc670829f74565114bcf10c5b97d7f93252b467d0eee278b3","other":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","up":true},{"one":"1d942c85568f640497c46a9bd5a9825bc6fb05571203a3f0f14a3032602d81fc","other":"52881f1fe0b8e9044a3bc035edbbd6343b9c41d49c0f2c2ca799c2e764489b91","up":true},{"one":"15f6bb0640c3ac8ae8ecf10d0d46cc739bb6f86b0c0cf706103aad9f3785219b","other":"c4844b8046d1c47f699ee1c232d945829105d45af26fba722ce4d0a937c99631","up":true},{"one":"f4eee71a143722ea8288279876bf5687c1ed65eede0129e987e53f5ec76d8543","other":"b310299bef4f4e84a1ecc960143e4b1bbe98cd4eb8352458c477569ca9994c20","up":true},{"one":"c4844b8046d1c47f699ee1c232d945829105d45af26fba722ce4d0a937c99631","other":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","up":true},{"one":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","other":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","up":true},{"one":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"b310299bef4f4e84a1ecc960143e4b1bbe98cd4eb8352458c477569ca9994c20","other":"edca23c0fccfd1ec66f4d39bb129462776b9091c2337f6afbfe8737a0ebc0d7d","up":true},{"one":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","other":"0592048ae4e6a3000f36e8e1c0e1328c26c0d4bb6369d6cd867a08d847dacb9e","up":true},{"one":"d6f37555e9f35a3ef3b8b4d5c18cf0702ed99c1c8b5b0a8554deddcbe542b88c","other":"dc3ec6529cec19709403e473d4546ba07f7aaaaec5be7cb0618246a4c0458944","up":true},{"one":"4a828523731caf47f9aa2dfa20f8a8895e9a6562b3a034574639a229a733b4a6","other":"b45d1aee940e5d2c71db22e2afbe908d5967e437fbb5bd051edea86ea4ac9d30","up":true},{"one":"0592048ae4e6a3000f36e8e1c0e1328c26c0d4bb6369d6cd867a08d847dacb9e","other":"03f5f1be5c1ae35469333a5b08622372f65ef8f233746afde56d2647c5273630","up":true},{"one":"1b869ca5aec1ff0c9980b06b79084019752fd4557054e251c0091ee5d2c7d478","other":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","up":true},{"one":"bf5ac6f9b651895c8d75ab3ae7994f91981dbbcdce25a7cd718fc996da96946d","other":"4cf655b03778bcb41c32664f13168dc32968354468c8aa699de52b43a2e0dc7d","up":true},{"one":"4cf655b03778bcb41c32664f13168dc32968354468c8aa699de52b43a2e0dc7d","other":"df5ea72c0c257bbea9b43901a1e406bf6055a307d083367e7f1d944e65c2b3cd","up":true},{"one":"e67d72c8c72bbc7bec1d4e936d65adbeab75d1b6cafdf2c39e38bfc723cceebf","other":"ecd2b502d4e9bf77369c62d1142a9fb4ab1071e751cc6971e9e381e953b0fc12","up":true},{"one":"7de7eb0b73f3b02b998e592e0fc51b26c0c3ae6695d41897874d2a3d8ce3554b","other":"7854fb9f0d2f016653994b9e23033f27e21a2ef186a352c8d068b75ea20beec2","up":true},{"one":"265d7fc553fb8dc016d7c6ccc31fc372912a11e9e8da0e5822e371a91d82e857","other":"bf5ac6f9b651895c8d75ab3ae7994f91981dbbcdce25a7cd718fc996da96946d","up":true},{"one":"df5ea72c0c257bbea9b43901a1e406bf6055a307d083367e7f1d944e65c2b3cd","other":"fed15b57e749c5f8f5fdb382eb95abe2ce0dee184c817e557122207f3729b05c","up":true},{"one":"cb69f673a54a75de300d1d1ab989caa04579504cbcaf78f03bc192c9578ec5a0","other":"237459ba37141f18b3cd40f5ec9cdaf6789fb87aa4511d489fdd98a960ef2883","up":true},{"one":"fed15b57e749c5f8f5fdb382eb95abe2ce0dee184c817e557122207f3729b05c","other":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","up":true},{"one":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","other":"265d7fc553fb8dc016d7c6ccc31fc372912a11e9e8da0e5822e371a91d82e857","up":true},{"one":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","other":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","up":true},{"one":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","other":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","up":true},{"one":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","other":"36485615576c68702382f614054494cc4a40b90fd66db86b9d86a6a131dcc6ac","up":true},{"one":"36485615576c68702382f614054494cc4a40b90fd66db86b9d86a6a131dcc6ac","other":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","up":true},{"one":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","other":"baf32cface953bd22141970b691567ed375f06043fe31de354598fb4230fb0a3","up":true},{"one":"baf32cface953bd22141970b691567ed375f06043fe31de354598fb4230fb0a3","other":"72acf53ab7dbda90ea70e78d001fe21e7ab88e4054ff0453212e33a26fbbf7f1","up":true},{"one":"72acf53ab7dbda90ea70e78d001fe21e7ab88e4054ff0453212e33a26fbbf7f1","other":"13d8107c669cc27f6cbc64cef6b2bd11f72db297f2857c2de7323b92632b8231","up":true},{"one":"13d8107c669cc27f6cbc64cef6b2bd11f72db297f2857c2de7323b92632b8231","other":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","up":true},{"one":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","other":"88da9dccda89c111dde40318fd1e5239e77b670f5a1b88a3e9320b7350885e31","up":true},{"one":"88da9dccda89c111dde40318fd1e5239e77b670f5a1b88a3e9320b7350885e31","other":"8163eebe696123817a6d998d7781612931df6fa2fd0293c0d0dd395d031c53f6","up":true},{"one":"8163eebe696123817a6d998d7781612931df6fa2fd0293c0d0dd395d031c53f6","other":"730749922ce3dcd8e249b988dbfb950c15473a2f6fd5cfe2a8126995fb1dc186","up":true},{"one":"730749922ce3dcd8e249b988dbfb950c15473a2f6fd5cfe2a8126995fb1dc186","other":"2f2284537629514837d5c2296d2871d8c4c147719f0ddc28ef7c629efd44dd61","up":true},{"one":"2f2284537629514837d5c2296d2871d8c4c147719f0ddc28ef7c629efd44dd61","other":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","up":true},{"one":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","other":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","up":true},{"one":"290fca7596740129fd1a3075bbf56885d8c83381f981adb0fb283799375a3bd5","other":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","up":true},{"one":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","other":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","up":true},{"one":"ed1348b4037004b61bd4d6f9be1648112f835943a3b997b91ac931301311e007","other":"4a828523731caf47f9aa2dfa20f8a8895e9a6562b3a034574639a229a733b4a6","up":true},{"one":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","other":"fb93417c597050f8deeb3098475f6cf0d213926332b59c25a538c1b4e6af89c0","up":true},{"one":"c4632e164170d35e91fce46f660265f73795f1d840effce5ab2aae18c73003d7","other":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","up":true},{"one":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","other":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","up":true},{"one":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","other":"7d941474f90981a03c95af4cf68be163d01a317e202ac0db82e25f46fc3db8aa","up":true},{"one":"7d941474f90981a03c95af4cf68be163d01a317e202ac0db82e25f46fc3db8aa","other":"2af02e62ff3e40be25d22c2f745a505b895b3804a80663cb53db4edd5365c568","up":true},{"one":"2af02e62ff3e40be25d22c2f745a505b895b3804a80663cb53db4edd5365c568","other":"6544c4a403d9809697420ff9840ea9de0c9ba6491de28dac288db6257f2dc218","up":true},{"one":"6544c4a403d9809697420ff9840ea9de0c9ba6491de28dac288db6257f2dc218","other":"21682cd736edfb8b451dfd9c55eb9ea3b7cf2e35cad32537f1f4800644fbf9a7","up":true},{"one":"fb93417c597050f8deeb3098475f6cf0d213926332b59c25a538c1b4e6af89c0","other":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","up":true},{"one":"21682cd736edfb8b451dfd9c55eb9ea3b7cf2e35cad32537f1f4800644fbf9a7","other":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","up":true},{"one":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","other":"5fa8a5a29a47694653a5e55fd9b76e9c4f717ecc9b7088a6ec7ee273b291d49e","up":true},{"one":"5fa8a5a29a47694653a5e55fd9b76e9c4f717ecc9b7088a6ec7ee273b291d49e","other":"5f056f9593be46d3eba4dc8d9e0b93262cd3f00e007c13c4c011d53e7c199628","up":true},{"one":"5f056f9593be46d3eba4dc8d9e0b93262cd3f00e007c13c4c011d53e7c199628","other":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","up":true},{"one":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","other":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","up":true},{"one":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","other":"ceee9ac63786ad68d884f5d018248ae1b7b60f0ad434776d29bd4dfcdc0f04c1","up":true},{"one":"ceee9ac63786ad68d884f5d018248ae1b7b60f0ad434776d29bd4dfcdc0f04c1","other":"3a4a300de1009d694bebce2362ca429086ade2f551989d55ba459a68efea15af","up":true},{"one":"3a4a300de1009d694bebce2362ca429086ade2f551989d55ba459a68efea15af","other":"99aaaa7c98113955ecc0163943317f6ec08ae755e96c25c9f79ca1f529c60587","up":true},{"one":"99aaaa7c98113955ecc0163943317f6ec08ae755e96c25c9f79ca1f529c60587","other":"99dbf361b67d6c370fd4f201b059e93da50dd88cde2e25d0bcf3c73afbf13535","up":true},{"one":"99dbf361b67d6c370fd4f201b059e93da50dd88cde2e25d0bcf3c73afbf13535","other":"6d219b0ea4a1620a7ba051cc478da6df9ea443e87dde79e963ec789a5bad7459","up":true},{"one":"6d219b0ea4a1620a7ba051cc478da6df9ea443e87dde79e963ec789a5bad7459","other":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","up":true},{"one":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","other":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","up":true},{"one":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","other":"9eec5d5357732a7acd26cc8aa0bc65f6c6069aedf4f3ab4a6790c7fff6bc2695","up":true},{"one":"9eec5d5357732a7acd26cc8aa0bc65f6c6069aedf4f3ab4a6790c7fff6bc2695","other":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","up":true},{"one":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","other":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","up":true},{"one":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","other":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","up":true},{"one":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","other":"42c019ddad2fde0f0c7f5041eb584996976844f8b2e08e20458437578d0257ef","up":true},{"one":"42c019ddad2fde0f0c7f5041eb584996976844f8b2e08e20458437578d0257ef","other":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","up":true},{"one":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","other":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","up":true},{"one":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","other":"396b256b223f346a490844895b9f1046b4790d624af1c43ecae1de318c7214a8","up":true},{"one":"396b256b223f346a490844895b9f1046b4790d624af1c43ecae1de318c7214a8","other":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","up":true},{"one":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","other":"7a4118d2ce16e8258cc2cdf31c5891945e3787c46c0836fc916601584609b7db","up":true},{"one":"7a4118d2ce16e8258cc2cdf31c5891945e3787c46c0836fc916601584609b7db","other":"0210146e650132dd0ceb8cea779cafc5485822eeeb9fcc17f292af5cef6e9632","up":true},{"one":"0210146e650132dd0ceb8cea779cafc5485822eeeb9fcc17f292af5cef6e9632","other":"1b1eca7b35086d8b217431c1d6a01fc07dc8bc816196a80015c9e163bed676ff","up":true},{"one":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","other":"f9243ad864859edd351592d24abdf5c9c4e37f4ffe2ae1201c45f26576ee5725","up":true},{"one":"1b1eca7b35086d8b217431c1d6a01fc07dc8bc816196a80015c9e163bed676ff","other":"1e4441309404b0dd8dd9e8b1fe78708b015a0b0278ce86bcdaabc7d2d691791a","up":true},{"one":"f9243ad864859edd351592d24abdf5c9c4e37f4ffe2ae1201c45f26576ee5725","other":"e8392bdce92a7dcfa5e3196a6e1d4e5dc4814c72e104d3e2aa1f1ab99e2a47a6","up":true},{"one":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","other":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","up":true},{"one":"e8392bdce92a7dcfa5e3196a6e1d4e5dc4814c72e104d3e2aa1f1ab99e2a47a6","other":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","up":true},{"one":"c8fe626c60d6cd15912a22d2c5872f10a8f8aef5b6f6b2f1070edec6670bf8b1","other":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","up":true},{"one":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","other":"a485db6ec1929aaf695a84294f52905aa602584954e9ef71b4e18f7b847da31d","up":true},{"one":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","other":"2e9f7d37106b59d6f411dfa0392325d1fa279fe45c18a101e32ece6716e88946","up":true},{"one":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","other":"f4e0d91ca58ed616918bf268565c2edb4176293e851b8d29cbbda55bdc608534","up":true},{"one":"9a82d22b29c88eaca2f20a62e9e63675904065e72c70ce2fbbefc0c9d2e53e90","other":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","up":true},{"one":"2e9f7d37106b59d6f411dfa0392325d1fa279fe45c18a101e32ece6716e88946","other":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","up":true},{"one":"4019ce1dc667d90a85021b5fbf49ad77c1de7fd3443a40a17bdfaf5175efa1e1","other":"5110c7e54a18df6963919866e7f6c0c3a483045d5ac20fc0dfa5e1926681be7f","up":true},{"one":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","other":"e787fd35b438dbae3d0283f7181fc4afbeb6f333201ad4cd1b46bf7094a6a85f","up":true},{"one":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","other":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","up":true},{"one":"d68f504755b48f8d41570169dd3544ef1caea2ab1bb334f8d8c5fb27fe816f01","other":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","up":true},{"one":"c98d389a6d8e6eb5a1eb5a6f235857d5d939d30e51c16d80c039c9e0eb2f7eca","other":"bc08af284ab661dcc1cb23859271590e50a09ff8af8073769c812901ea0b9080","up":true},{"one":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","other":"985c14910d922bbd1556c30620e3ab72170da6553e658dd3806d773a734a1c93","up":true},{"one":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","other":"5fab772fff883d04cf7e4713266fb1e98fa1746e12faeb04e87bcb37b8b61187","up":true},{"one":"2279d612454ff034032a5acf5a039477a11479b9b9a93edc26b61312e8d9b156","other":"e3c95a82f373da4522533252549c1d68b6f7621a887feb66199cfd4b7a35b6fc","up":true},{"one":"985c14910d922bbd1556c30620e3ab72170da6553e658dd3806d773a734a1c93","other":"6ea59b849a9754c022219146efc27fa6561e5aca8338a111847259695198267b","up":true},{"one":"5fab772fff883d04cf7e4713266fb1e98fa1746e12faeb04e87bcb37b8b61187","other":"0f5e34b25f6851d0f3b572c64420de0aec01754bea0854bd7e9910b4b1cc66c3","up":true},{"one":"c7703c4697637f40a50a5d0b656c4e54010ef1277b64d251ccd0752512e246e9","other":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","up":true},{"one":"6ea59b849a9754c022219146efc27fa6561e5aca8338a111847259695198267b","other":"211a516fb95d093eb43f4716edeb1e6f76ed79d2aeee3393740e244970187840","up":true},{"one":"0f5e34b25f6851d0f3b572c64420de0aec01754bea0854bd7e9910b4b1cc66c3","other":"d3d254a3024a7aac01e97a5f0aff13756741867e599bc94c068f0d1d19ca8176","up":true},{"one":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","other":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","up":true},{"one":"211a516fb95d093eb43f4716edeb1e6f76ed79d2aeee3393740e244970187840","other":"a749372cccb7001a480b9416c41305bd1d3bc417e15669ae587d4375f2bbcbf7","up":true},{"one":"d3d254a3024a7aac01e97a5f0aff13756741867e599bc94c068f0d1d19ca8176","other":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","up":true},{"one":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","other":"b4639751d67db0b32ad1846996fed0d5c59be76b77efaec5f66e64df35eefae8","up":true},{"one":"a749372cccb7001a480b9416c41305bd1d3bc417e15669ae587d4375f2bbcbf7","other":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","up":true},{"one":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","other":"92325e52babbf2b8ae3c37beec7e440d11efed2d103befa2c4a969315a2eab56","up":true},{"one":"b4639751d67db0b32ad1846996fed0d5c59be76b77efaec5f66e64df35eefae8","other":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","up":true},{"one":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","other":"b79fd26ce97e98e7432029dea6bf11e48ea9e8f2385dec5c60be0cbd51bbef54","up":true},{"one":"92325e52babbf2b8ae3c37beec7e440d11efed2d103befa2c4a969315a2eab56","other":"c0d1ac430a1466a1a28cb1aa3d29573ccedb13641ac19e6e615f2a96e8f0950b","up":true},{"one":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","other":"d8b0425e4cb5134ddb2de5c05e0ae9b861bd67409c1446ca228318f79f5dd1f6","up":true},{"one":"b79fd26ce97e98e7432029dea6bf11e48ea9e8f2385dec5c60be0cbd51bbef54","other":"18f9292fbe6e94ec3df4d0a896c71ed2b1771437e42d8475a7aae63c7992834d","up":true},{"one":"46c5a266dd46041a62f21946290aad736d2298277bbbb16b1efb4490d9d31fa8","other":"275c93677aefd92ab38f9e5b5b4d29f90e809ccaf77b088102071aba26421f3e","up":true},{"one":"d8b0425e4cb5134ddb2de5c05e0ae9b861bd67409c1446ca228318f79f5dd1f6","other":"f048e37427d68c4134a115cc7df6a8924ea95f24e7f2435372ae331e24b5b7fd","up":true},{"one":"18f9292fbe6e94ec3df4d0a896c71ed2b1771437e42d8475a7aae63c7992834d","other":"06aa2f43956f978abe7b8f8f78a5a8cd59decb66ec54b7c81ae50c3752798c5e","up":true},{"one":"f048e37427d68c4134a115cc7df6a8924ea95f24e7f2435372ae331e24b5b7fd","other":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","up":true},{"one":"06aa2f43956f978abe7b8f8f78a5a8cd59decb66ec54b7c81ae50c3752798c5e","other":"2279d612454ff034032a5acf5a039477a11479b9b9a93edc26b61312e8d9b156","up":true},{"one":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","other":"2fd800d224f124e47038bf140ebd486f1d0ab93169c998d2dfabfd1d3ced96ed","up":true},{"one":"0ea201515867c6793cbe48350f209e69acd3829b9b16b6016d965d84fb6379c5","other":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","up":true},{"one":"2fd800d224f124e47038bf140ebd486f1d0ab93169c998d2dfabfd1d3ced96ed","other":"194ad126c41c487d95c83b19ed2bb053e4d2ec0605952d3a396dd4304e8e363c","up":true},{"one":"194ad126c41c487d95c83b19ed2bb053e4d2ec0605952d3a396dd4304e8e363c","other":"5fd02ea98977d976f5d47a4005300b9865b1dcdac77bf8a98a8920a89b57825c","up":true},{"one":"5fd02ea98977d976f5d47a4005300b9865b1dcdac77bf8a98a8920a89b57825c","other":"4a81ee929074bd6d9932fb4d970f5207db8680bda0e951aa72a6bd64df45108d","up":true},{"one":"4a81ee929074bd6d9932fb4d970f5207db8680bda0e951aa72a6bd64df45108d","other":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","up":true},{"one":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","other":"1835f62fe3889f43ba7b4b577869ec764e96fa9a051f1301fb96795daa8b0755","up":true},{"one":"1835f62fe3889f43ba7b4b577869ec764e96fa9a051f1301fb96795daa8b0755","other":"259d3874697a0c3192f973c620880b2a86567289f5222d07eb91a51867352559","up":true},{"one":"259d3874697a0c3192f973c620880b2a86567289f5222d07eb91a51867352559","other":"96b6ec2cda7c9e962bbc97e7916b92d69b112265c4456d30dade806e09fea62e","up":true},{"one":"96b6ec2cda7c9e962bbc97e7916b92d69b112265c4456d30dade806e09fea62e","other":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","up":true},{"one":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","other":"2013ce63483af2990b35f9ecbe5a7653c34b24ded173753575b24451787ed8b9","up":true},{"one":"2013ce63483af2990b35f9ecbe5a7653c34b24ded173753575b24451787ed8b9","other":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","up":true},{"one":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","other":"daa2c427e8ecc566e634908fb0d47077d8e79854bf884342c06c4bcb355e80e6","up":true},{"one":"daa2c427e8ecc566e634908fb0d47077d8e79854bf884342c06c4bcb355e80e6","other":"2f9f266a3b660fe685de2932de82979bd221706db1e2b954a93edc6609c378d9","up":true},{"one":"2f9f266a3b660fe685de2932de82979bd221706db1e2b954a93edc6609c378d9","other":"3dca1ae488cc497fe738c630465f987b419a5bb259fdfa17fe7130e238ca3022","up":true},{"one":"3dca1ae488cc497fe738c630465f987b419a5bb259fdfa17fe7130e238ca3022","other":"1b869ca5aec1ff0c9980b06b79084019752fd4557054e251c0091ee5d2c7d478","up":true},{"one":"7854fb9f0d2f016653994b9e23033f27e21a2ef186a352c8d068b75ea20beec2","other":"e67d72c8c72bbc7bec1d4e936d65adbeab75d1b6cafdf2c39e38bfc723cceebf","up":true},{"one":"e3c95a82f373da4522533252549c1d68b6f7621a887feb66199cfd4b7a35b6fc","other":"bfeca898b1755a001eb0bcf8f5ad5713a550d0597f99ef7967ea4e7add8d3e81","up":true},{"one":"bfeca898b1755a001eb0bcf8f5ad5713a550d0597f99ef7967ea4e7add8d3e81","other":"f5dd00d94488f3387d64ccc2115cd91e5073c06de36d195a5c1a95db23cd5c68","up":true},{"one":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","other":"c15d13aff7f5694fb8fc2a2154846ac703064133feb632e28830777bbfc7c016","up":true},{"one":"dc3ec6529cec19709403e473d4546ba07f7aaaaec5be7cb0618246a4c0458944","other":"7471be56a2f617783d5a1ae4390794ad24f453259b7c1f8f65be8f71c8bf8bf7","up":true},{"one":"f5dd00d94488f3387d64ccc2115cd91e5073c06de36d195a5c1a95db23cd5c68","other":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","up":true},{"one":"52881f1fe0b8e9044a3bc035edbbd6343b9c41d49c0f2c2ca799c2e764489b91","other":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","up":true},{"one":"14c81fb2fb8fa5a6b47288356ab486012046d09959b9ef4814a09dc3facb4853","other":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","up":true},{"one":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","other":"0f81caba7d2f752fc670829f74565114bcf10c5b97d7f93252b467d0eee278b3","up":true},{"one":"275c93677aefd92ab38f9e5b5b4d29f90e809ccaf77b088102071aba26421f3e","other":"9294e5b39a1cd4230400e033da1ac1f79a4025adab41ce8c0e0e47dda2418cb5","up":true},{"one":"f4e0d91ca58ed616918bf268565c2edb4176293e851b8d29cbbda55bdc608534","other":"4019ce1dc667d90a85021b5fbf49ad77c1de7fd3443a40a17bdfaf5175efa1e1","up":true},{"one":"5110c7e54a18df6963919866e7f6c0c3a483045d5ac20fc0dfa5e1926681be7f","other":"d68f504755b48f8d41570169dd3544ef1caea2ab1bb334f8d8c5fb27fe816f01","up":true},{"one":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","other":"14c81fb2fb8fa5a6b47288356ab486012046d09959b9ef4814a09dc3facb4853","up":true},{"one":"edca23c0fccfd1ec66f4d39bb129462776b9091c2337f6afbfe8737a0ebc0d7d","other":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","up":true},{"one":"e787fd35b438dbae3d0283f7181fc4afbeb6f333201ad4cd1b46bf7094a6a85f","other":"c98d389a6d8e6eb5a1eb5a6f235857d5d939d30e51c16d80c039c9e0eb2f7eca","up":true},{"one":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","other":"9a82d22b29c88eaca2f20a62e9e63675904065e72c70ce2fbbefc0c9d2e53e90","up":true},{"one":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","other":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","up":true},{"one":"7471be56a2f617783d5a1ae4390794ad24f453259b7c1f8f65be8f71c8bf8bf7","other":"290fca7596740129fd1a3075bbf56885d8c83381f981adb0fb283799375a3bd5","up":true},{"one":"ecd2b502d4e9bf77369c62d1142a9fb4ab1071e751cc6971e9e381e953b0fc12","other":"4a670fe61711307f7781b3b5a8014fc0e63866efe356a25e61b2e768a7389980","up":true},{"one":"c15d13aff7f5694fb8fc2a2154846ac703064133feb632e28830777bbfc7c016","other":"7de7eb0b73f3b02b998e592e0fc51b26c0c3ae6695d41897874d2a3d8ce3554b","up":true},{"one":"3af3bb6c4343a19e3c0f9a89697cd70a3c79c64b1e40afa59b188c5553eed567","other":"cb69f673a54a75de300d1d1ab989caa04579504cbcaf78f03bc192c9578ec5a0","up":true},{"one":"670d1c31407b4f34bb955031e9a3ab7980273155080b459a082687d53a3b42a6","other":"ad36b802e9b01727e516879c0055e3c46b810845794f7772ad85e8062111b167","up":true},{"one":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","other":"458a31b8fdbc42f5e80f01118c9dc041740212c09ddfb4b137c9e7e93ae9ec51","up":true},{"one":"458a31b8fdbc42f5e80f01118c9dc041740212c09ddfb4b137c9e7e93ae9ec51","other":"e4c3fa0700516da5650f794067eb1c90ba7eaab2b65019bf34552c26f8c1d9a6","up":true},{"one":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","other":"32dd86f3f5c3abe55d6b1fe352e9d1d0cc63d61cfa60b8e70b2ee9db2e28e1aa","up":true},{"one":"c7230d8d56b65a66ab721a8268ed436af78210aae7d068cc2b8afe39b6e61c81","other":"ae715a40e9bf50b4eb954c6b87efbf1a6e7abc3da6d84055e4591aadf5bad5c8","up":true},{"one":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","other":"1d942c85568f640497c46a9bd5a9825bc6fb05571203a3f0f14a3032602d81fc","up":true},{"one":"b71052b072cb98fb79151fd0c6044828e535a0547cace931183c7e4e7f227698","other":"c8fe626c60d6cd15912a22d2c5872f10a8f8aef5b6f6b2f1070edec6670bf8b1","up":true},{"one":"32dd86f3f5c3abe55d6b1fe352e9d1d0cc63d61cfa60b8e70b2ee9db2e28e1aa","other":"487edf32027381256a864bc2e9bca175f7216cc1234731bd4dc90d2cbad6975d","up":true},{"one":"ae715a40e9bf50b4eb954c6b87efbf1a6e7abc3da6d84055e4591aadf5bad5c8","other":"f1fcfc064446a432ac7a21efc386fa3d053d0ca4540fd4ddbbb253ec11f9ce94","up":true},{"one":"f1fcfc064446a432ac7a21efc386fa3d053d0ca4540fd4ddbbb253ec11f9ce94","other":"7d45f1e91bcd683d870422b2f591c76f6ab07ff4b079f35c280ce46f7fc364e1","up":true},{"one":"487edf32027381256a864bc2e9bca175f7216cc1234731bd4dc90d2cbad6975d","other":"d6d2d35b3819e03b39d7146c4dd1b53fb7b70681c8860464650d5eb2d09dda2c","up":true},{"one":"9fee945c89d6bb59e77a24183c2855f4eece0b5d3266f750ea95483eae1e1871","other":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","up":true},{"one":"d6d2d35b3819e03b39d7146c4dd1b53fb7b70681c8860464650d5eb2d09dda2c","other":"9fee945c89d6bb59e77a24183c2855f4eece0b5d3266f750ea95483eae1e1871","up":true},{"one":"aa504681c991c69015c7ef0469902c44fc4e900356b181aa252a1280ffbd2e3b","other":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","up":true},{"one":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","other":"571638637156bdaf92d9c715647a1cf64e91877c2d998658b7c6cd27522271bd","up":true},{"one":"79abd33d150fdbcbce6f55f3d101608b80e03ede7e4627bc06ba5c1b001538a4","other":"aa504681c991c69015c7ef0469902c44fc4e900356b181aa252a1280ffbd2e3b","up":true},{"one":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","other":"a6720426faa1ea7e2d55c1c5ad05ba2e8c534d120ebb5e1375cf769d73191708","up":true},{"one":"a6720426faa1ea7e2d55c1c5ad05ba2e8c534d120ebb5e1375cf769d73191708","other":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","up":true},{"one":"31edba65e539a9a3c7107a131c4fb4b5a8e2c2d4e2db7874a0116c173961e27c","other":"adfc03118406bc5af4e3f3c6c89c3448c3fe4f9df20b15bcca9bbacf58ee5457","up":true},{"one":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","other":"c99c6a2a2fe7f4613c8d918404c956eae0a00fce544c9eeba5da640e5e958874","up":true},{"one":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","other":"abfabc2964ce2f7b8b3702e897a8cc83fb0c009d5af9670b889db582e57ef7c1","up":true},{"one":"1b7250dae1f2b07504b381d28084b751a26ac791a1ed842ea5746826ab85ba22","other":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","up":true},{"one":"adfc03118406bc5af4e3f3c6c89c3448c3fe4f9df20b15bcca9bbacf58ee5457","other":"79abd33d150fdbcbce6f55f3d101608b80e03ede7e4627bc06ba5c1b001538a4","up":true},{"one":"571638637156bdaf92d9c715647a1cf64e91877c2d998658b7c6cd27522271bd","other":"31edba65e539a9a3c7107a131c4fb4b5a8e2c2d4e2db7874a0116c173961e27c","up":true},{"one":"c99c6a2a2fe7f4613c8d918404c956eae0a00fce544c9eeba5da640e5e958874","other":"af358f626738d2e5d17060abbaf24cb0124a48c5edb8f3baeda30bea7b403dfa","up":true},{"one":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","other":"b71052b072cb98fb79151fd0c6044828e535a0547cace931183c7e4e7f227698","up":true},{"one":"abfabc2964ce2f7b8b3702e897a8cc83fb0c009d5af9670b889db582e57ef7c1","other":"c63e796efdc44712403199c11c3e5ff28e5d6a4d9ecdf3e95620fbd6afb1bc2d","up":true},{"one":"c63e796efdc44712403199c11c3e5ff28e5d6a4d9ecdf3e95620fbd6afb1bc2d","other":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","up":true},{"one":"af358f626738d2e5d17060abbaf24cb0124a48c5edb8f3baeda30bea7b403dfa","other":"1b7250dae1f2b07504b381d28084b751a26ac791a1ed842ea5746826ab85ba22","up":true},{"one":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","other":"604c6bfab4f5d16d5b786318cf8f9e0197fc18e5d8a8e6f4fd345e653d2abe17","up":true},{"one":"1a02108d9149739be6ba85ddd1e8a3961adeec38f77fcef95ab57f8f2f39f4bb","other":"34fce2b8208c6c8ed005943b3f2cb9f8923739ce262e2040f7ffc048093ef910","up":true},{"one":"13d8107c669cc27f6cbc64cef6b2bd11f72db297f2857c2de7323b92632b8231","other":"baf32cface953bd22141970b691567ed375f06043fe31de354598fb4230fb0a3","up":true},{"one":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","other":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","up":true},{"one":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","other":"4019ce1dc667d90a85021b5fbf49ad77c1de7fd3443a40a17bdfaf5175efa1e1","up":true},{"one":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","other":"0592048ae4e6a3000f36e8e1c0e1328c26c0d4bb6369d6cd867a08d847dacb9e","up":true},{"one":"34fce2b8208c6c8ed005943b3f2cb9f8923739ce262e2040f7ffc048093ef910","other":"4f90f53754ee50357491dbab7c3e6748b40133e3339920b32f4c3457f4258477","up":true},{"one":"7dc63c8146ed092aa4c9539abd112dc3a703631e364a62fd74b192eee5778123","other":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","up":true},{"one":"0ea201515867c6793cbe48350f209e69acd3829b9b16b6016d965d84fb6379c5","other":"14c81fb2fb8fa5a6b47288356ab486012046d09959b9ef4814a09dc3facb4853","up":true},{"one":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","other":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","up":true},{"one":"4f90f53754ee50357491dbab7c3e6748b40133e3339920b32f4c3457f4258477","other":"52619a523050aa8caa32448e59721a76dc3321dc92cfcb1b05f1cec5b79d424a","up":true},{"one":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","other":"6ea59b849a9754c022219146efc27fa6561e5aca8338a111847259695198267b","up":true},{"one":"52619a523050aa8caa32448e59721a76dc3321dc92cfcb1b05f1cec5b79d424a","other":"66100e4f9d103d935164f39ea3f0332708f3825ebcf31ac6f8c9714c75a50860","up":true},{"one":"66100e4f9d103d935164f39ea3f0332708f3825ebcf31ac6f8c9714c75a50860","other":"4fd6a4b99b1ba76775594673421c8eea175c114519c8554798c2c364175bbaae","up":true},{"one":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","other":"043f24ce87fbd01f6f663f323ac6c156810a67b12efbec2f219ae9a882a4bd16","up":true},{"one":"d8222a2c177f0cd17baae28327af5c13f2fb551dd01298d17855ce7f6f4f6189","other":"353875d4ae2b9c5cc6cb14059bdfb9540647100c7c3334bc60db2ca2b4ddf3f5","up":true},{"one":"3e85d2d3d6750f84500fcf523743e0c242943e69188cb83114adad1787e6c089","other":"4d447ea80bc126e57b9fc91203a310c5f4b242df5ad70d5087d16697a626e601","up":true},{"one":"043f24ce87fbd01f6f663f323ac6c156810a67b12efbec2f219ae9a882a4bd16","other":"f78a54ba4b3fa75cdb542bdcd6da0d49986a5f7e7939959a51b3b48a87605654","up":true},{"one":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","other":"6ea59b849a9754c022219146efc27fa6561e5aca8338a111847259695198267b","up":true},{"one":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","other":"3e85d2d3d6750f84500fcf523743e0c242943e69188cb83114adad1787e6c089","up":true},{"one":"211a516fb95d093eb43f4716edeb1e6f76ed79d2aeee3393740e244970187840","other":"985c14910d922bbd1556c30620e3ab72170da6553e658dd3806d773a734a1c93","up":true},{"one":"4d447ea80bc126e57b9fc91203a310c5f4b242df5ad70d5087d16697a626e601","other":"388d46d6ea3fd92aa959b04d597ff1096d6133f171181c8fb54e52935b62d33c","up":true},{"one":"9c01d016aaf11f9e7b048ba10cea3c351d9ab5c8c66e8605f78c06772708985c","other":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","up":true},{"one":"388d46d6ea3fd92aa959b04d597ff1096d6133f171181c8fb54e52935b62d33c","other":"3345e03880c377fc1fe7b6205011ce3e32525cefee5e03161105f1512893f9fb","up":true},{"one":"f78a54ba4b3fa75cdb542bdcd6da0d49986a5f7e7939959a51b3b48a87605654","other":"9c01d016aaf11f9e7b048ba10cea3c351d9ab5c8c66e8605f78c06772708985c","up":true},{"one":"14418557af44e33fc8d46369426c3e2fc91622cb08779b3fefce706969ec2163","other":"004e812995bc04f22993466df9889ca1b650bba242cf80bb070ce9564e7bedd8","up":true},{"one":"86128689b03706f9a86baf4753154afbb0c535f84089039ef5610d82fb969a6e","other":"245495250d38095106022d3fc905eb7eb6f54adb2cb0bacdda0b305974eaa50a","up":true},{"one":"89ee628583b6e0345ceb312c2e2cbf5ac410eb00531be903a4b94b60b639993b","other":"95e0529227286d442cdeeed428614f99c19f9223426e6f6cefbef2d9d47ce96f","up":true},{"one":"004e812995bc04f22993466df9889ca1b650bba242cf80bb070ce9564e7bedd8","other":"1d5fc65b4121cccaff0d2a853c6bbff0c10b4ec28cfc5ef5cbea8bc28bc4cbec","up":true},{"one":"245495250d38095106022d3fc905eb7eb6f54adb2cb0bacdda0b305974eaa50a","other":"a80b39cf280fa80b323370cfc37e8e3ff38de47d5de445c500f36c8cf2390b53","up":true},{"one":"1d5fc65b4121cccaff0d2a853c6bbff0c10b4ec28cfc5ef5cbea8bc28bc4cbec","other":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","up":true},{"one":"123fe1d442c7dc905a7206e712fbd9cea640295a6823b39b1945629ffa89e895","other":"be0ab3ddc656568900d8e1f42394cc942464596b7565db40f6c5a0c24ee942f2","up":true},{"one":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","other":"f048e37427d68c4134a115cc7df6a8924ea95f24e7f2435372ae331e24b5b7fd","up":true},{"one":"a80b39cf280fa80b323370cfc37e8e3ff38de47d5de445c500f36c8cf2390b53","other":"86f71d23eb4efeeed4679be7a3d0f58f98b83d23b3e0a8b73f60f1535b51f25c","up":true},{"one":"95e0529227286d442cdeeed428614f99c19f9223426e6f6cefbef2d9d47ce96f","other":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","up":true},{"one":"604c6bfab4f5d16d5b786318cf8f9e0197fc18e5d8a8e6f4fd345e653d2abe17","other":"7dc63c8146ed092aa4c9539abd112dc3a703631e364a62fd74b192eee5778123","up":true},{"one":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","other":"2f9f266a3b660fe685de2932de82979bd221706db1e2b954a93edc6609c378d9","up":true},{"one":"f4eee71a143722ea8288279876bf5687c1ed65eede0129e987e53f5ec76d8543","other":"edca23c0fccfd1ec66f4d39bb129462776b9091c2337f6afbfe8737a0ebc0d7d","up":true},{"one":"ad36b802e9b01727e516879c0055e3c46b810845794f7772ad85e8062111b167","other":"b4c7555a38d1d4e3bd1d01bcc47a84efd6128e8cc0fd803362da8b341f984a4d","up":true},{"one":"c7703c4697637f40a50a5d0b656c4e54010ef1277b64d251ccd0752512e246e9","other":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","up":true},{"one":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","other":"7de7eb0b73f3b02b998e592e0fc51b26c0c3ae6695d41897874d2a3d8ce3554b","up":true},{"one":"e4c3fa0700516da5650f794067eb1c90ba7eaab2b65019bf34552c26f8c1d9a6","other":"3f3e66f7c8fc7daae8a7a84a9c90f1ea64faa43930f36fc8f11e2d7b751f8ee9","up":true},{"one":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","other":"f048e37427d68c4134a115cc7df6a8924ea95f24e7f2435372ae331e24b5b7fd","up":true},{"one":"194ad126c41c487d95c83b19ed2bb053e4d2ec0605952d3a396dd4304e8e363c","other":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","up":true},{"one":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","other":"86128689b03706f9a86baf4753154afbb0c535f84089039ef5610d82fb969a6e","up":true},{"one":"b4c7555a38d1d4e3bd1d01bcc47a84efd6128e8cc0fd803362da8b341f984a4d","other":"ed6570e63fc0e2e2d6290645c4339b70a0f1c14064d286b4e0ba143607bd70c8","up":true},{"one":"3f3e66f7c8fc7daae8a7a84a9c90f1ea64faa43930f36fc8f11e2d7b751f8ee9","other":"167306fb0e24ddd775c2ac0722103ce5db98bb0ef60159b91f584475d34cb5bf","up":true},{"one":"ed6570e63fc0e2e2d6290645c4339b70a0f1c14064d286b4e0ba143607bd70c8","other":"29fd4473f1b6c37f8268d73dd5f683a0f7eb64bd793dab2c450c10d8ab0b666d","up":true},{"one":"1835f62fe3889f43ba7b4b577869ec764e96fa9a051f1301fb96795daa8b0755","other":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","up":true},{"one":"03f5f1be5c1ae35469333a5b08622372f65ef8f233746afde56d2647c5273630","other":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","up":true},{"one":"3af3bb6c4343a19e3c0f9a89697cd70a3c79c64b1e40afa59b188c5553eed567","other":"14c81fb2fb8fa5a6b47288356ab486012046d09959b9ef4814a09dc3facb4853","up":true},{"one":"77ec3a73ce1d597d307e87ea8d026baae2a7df4d288a657f240eaaaae7fdb456","other":"c3f356325eb1e61f2cfd8d80d085fb144c6887e1d710c9d133109b84b823425d","up":true},{"one":"167306fb0e24ddd775c2ac0722103ce5db98bb0ef60159b91f584475d34cb5bf","other":"77ec3a73ce1d597d307e87ea8d026baae2a7df4d288a657f240eaaaae7fdb456","up":true},{"one":"74062d9dd73e26066fd44e4f6c696f271c9dc6860bdc5ba3fcf447f455c8cafa","other":"7ffed78e11c71eccfdddf6a8e5e367831b26a6b11e4d3b60c5b9fc50f4bcdf33","up":true},{"one":"c98d389a6d8e6eb5a1eb5a6f235857d5d939d30e51c16d80c039c9e0eb2f7eca","other":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","up":true},{"one":"c3f356325eb1e61f2cfd8d80d085fb144c6887e1d710c9d133109b84b823425d","other":"7294223c0ab27851df8c25418acf09a28efa95fc238252af8a68390db07b77e4","up":true},{"one":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","other":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","up":true},{"one":"d68f504755b48f8d41570169dd3544ef1caea2ab1bb334f8d8c5fb27fe816f01","other":"c0d1ac430a1466a1a28cb1aa3d29573ccedb13641ac19e6e615f2a96e8f0950b","up":true},{"one":"29fd4473f1b6c37f8268d73dd5f683a0f7eb64bd793dab2c450c10d8ab0b666d","other":"74062d9dd73e26066fd44e4f6c696f271c9dc6860bdc5ba3fcf447f455c8cafa","up":true},{"one":"7294223c0ab27851df8c25418acf09a28efa95fc238252af8a68390db07b77e4","other":"9390ef61753fcf9dae5e585a85650287fe1f792538721a2cf767adc44c5e7ce0","up":true},{"one":"4a670fe61711307f7781b3b5a8014fc0e63866efe356a25e61b2e768a7389980","other":"7854fb9f0d2f016653994b9e23033f27e21a2ef186a352c8d068b75ea20beec2","up":true},{"one":"7ffed78e11c71eccfdddf6a8e5e367831b26a6b11e4d3b60c5b9fc50f4bcdf33","other":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","up":true},{"one":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","other":"67831469d7f9b779df4318cda9d4310c53fdd66332af3740e654233ceb3fbcbc","up":true},{"one":"9390ef61753fcf9dae5e585a85650287fe1f792538721a2cf767adc44c5e7ce0","other":"9c0c7b6da33520440b181a5fcc30aa8a7576ec9c6f64acef0c9525d05a2ee393","up":true},{"one":"9c0c7b6da33520440b181a5fcc30aa8a7576ec9c6f64acef0c9525d05a2ee393","other":"43afd835930eac91c0c8947001dd60d8da9112c19cf3f1356589b58b48962166","up":true},{"one":"2f9f266a3b660fe685de2932de82979bd221706db1e2b954a93edc6609c378d9","other":"0592048ae4e6a3000f36e8e1c0e1328c26c0d4bb6369d6cd867a08d847dacb9e","up":true},{"one":"43afd835930eac91c0c8947001dd60d8da9112c19cf3f1356589b58b48962166","other":"b60deba7c8676305b67af41d2de866bddd72edc0667d427e51fc3f2e91412b45","up":true},{"one":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","other":"c7230d8d56b65a66ab721a8268ed436af78210aae7d068cc2b8afe39b6e61c81","up":true},{"one":"67831469d7f9b779df4318cda9d4310c53fdd66332af3740e654233ceb3fbcbc","other":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","up":true},{"one":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","other":"47f95708475c9d61ff2016e7707c4a3796f6c87313bd1d2bf91aa620113ba5f8","up":true},{"one":"b60deba7c8676305b67af41d2de866bddd72edc0667d427e51fc3f2e91412b45","other":"670d1c31407b4f34bb955031e9a3ab7980273155080b459a082687d53a3b42a6","up":true},{"one":"c4844b8046d1c47f699ee1c232d945829105d45af26fba722ce4d0a937c99631","other":"cb69f673a54a75de300d1d1ab989caa04579504cbcaf78f03bc192c9578ec5a0","up":true},{"one":"c0d1ac430a1466a1a28cb1aa3d29573ccedb13641ac19e6e615f2a96e8f0950b","other":"d3d254a3024a7aac01e97a5f0aff13756741867e599bc94c068f0d1d19ca8176","up":true},{"one":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","other":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","up":true},{"one":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","other":"31edba65e539a9a3c7107a131c4fb4b5a8e2c2d4e2db7874a0116c173961e27c","up":true},{"one":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","other":"1d942c85568f640497c46a9bd5a9825bc6fb05571203a3f0f14a3032602d81fc","up":true},{"one":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","other":"14418557af44e33fc8d46369426c3e2fc91622cb08779b3fefce706969ec2163","up":true},{"one":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","other":"14c81fb2fb8fa5a6b47288356ab486012046d09959b9ef4814a09dc3facb4853","up":true},{"one":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","other":"2f9f266a3b660fe685de2932de82979bd221706db1e2b954a93edc6609c378d9","up":true},{"one":"be0ab3ddc656568900d8e1f42394cc942464596b7565db40f6c5a0c24ee942f2","other":"99fba7334f47bfcf21331d6179bfb2eee2ecebd1ea23fd829c542844b10ed974","up":true},{"one":"99fba7334f47bfcf21331d6179bfb2eee2ecebd1ea23fd829c542844b10ed974","other":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","up":true},{"one":"03f5f1be5c1ae35469333a5b08622372f65ef8f233746afde56d2647c5273630","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"1d942c85568f640497c46a9bd5a9825bc6fb05571203a3f0f14a3032602d81fc","other":"14c81fb2fb8fa5a6b47288356ab486012046d09959b9ef4814a09dc3facb4853","up":true},{"one":"7d45f1e91bcd683d870422b2f591c76f6ab07ff4b079f35c280ce46f7fc364e1","other":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","up":true},{"one":"0ea201515867c6793cbe48350f209e69acd3829b9b16b6016d965d84fb6379c5","other":"0592048ae4e6a3000f36e8e1c0e1328c26c0d4bb6369d6cd867a08d847dacb9e","up":true},{"one":"52881f1fe0b8e9044a3bc035edbbd6343b9c41d49c0f2c2ca799c2e764489b91","other":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","up":true},{"one":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","other":"cb69f673a54a75de300d1d1ab989caa04579504cbcaf78f03bc192c9578ec5a0","up":true},{"one":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","other":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","up":true},{"one":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","other":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","up":true},{"one":"a485db6ec1929aaf695a84294f52905aa602584954e9ef71b4e18f7b847da31d","other":"b45d1aee940e5d2c71db22e2afbe908d5967e437fbb5bd051edea86ea4ac9d30","up":true},{"one":"604c6bfab4f5d16d5b786318cf8f9e0197fc18e5d8a8e6f4fd345e653d2abe17","other":"1a02108d9149739be6ba85ddd1e8a3961adeec38f77fcef95ab57f8f2f39f4bb","up":true},{"one":"df5ea72c0c257bbea9b43901a1e406bf6055a307d083367e7f1d944e65c2b3cd","other":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","up":true},{"one":"fed15b57e749c5f8f5fdb382eb95abe2ce0dee184c817e557122207f3729b05c","other":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","up":true},{"one":"265d7fc553fb8dc016d7c6ccc31fc372912a11e9e8da0e5822e371a91d82e857","other":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","up":true},{"one":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","other":"f4e0d91ca58ed616918bf268565c2edb4176293e851b8d29cbbda55bdc608534","up":true},{"one":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","other":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","up":true},{"one":"4a670fe61711307f7781b3b5a8014fc0e63866efe356a25e61b2e768a7389980","other":"c15d13aff7f5694fb8fc2a2154846ac703064133feb632e28830777bbfc7c016","up":true},{"one":"4cf655b03778bcb41c32664f13168dc32968354468c8aa699de52b43a2e0dc7d","other":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","up":true},{"one":"1e4441309404b0dd8dd9e8b1fe78708b015a0b0278ce86bcdaabc7d2d691791a","other":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","up":true},{"one":"9294e5b39a1cd4230400e033da1ac1f79a4025adab41ce8c0e0e47dda2418cb5","other":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","up":true},{"one":"bc08af284ab661dcc1cb23859271590e50a09ff8af8073769c812901ea0b9080","other":"c7703c4697637f40a50a5d0b656c4e54010ef1277b64d251ccd0752512e246e9","up":true},{"one":"c0d1ac430a1466a1a28cb1aa3d29573ccedb13641ac19e6e615f2a96e8f0950b","other":"46c5a266dd46041a62f21946290aad736d2298277bbbb16b1efb4490d9d31fa8","up":true},{"one":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","other":"d6f37555e9f35a3ef3b8b4d5c18cf0702ed99c1c8b5b0a8554deddcbe542b88c","up":true},{"one":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","other":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","up":true},{"one":"4a670fe61711307f7781b3b5a8014fc0e63866efe356a25e61b2e768a7389980","other":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","up":true},{"one":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","other":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","up":true},{"one":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","other":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","up":true},{"one":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","other":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","up":true},{"one":"4fd6a4b99b1ba76775594673421c8eea175c114519c8554798c2c364175bbaae","other":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","up":true},{"one":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","other":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","up":true},{"one":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","other":"7dc63c8146ed092aa4c9539abd112dc3a703631e364a62fd74b192eee5778123","up":true},{"one":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","other":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","up":true},{"one":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","other":"c0d1ac430a1466a1a28cb1aa3d29573ccedb13641ac19e6e615f2a96e8f0950b","up":true},{"one":"0f5e34b25f6851d0f3b572c64420de0aec01754bea0854bd7e9910b4b1cc66c3","other":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","up":true},{"one":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","other":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","up":true},{"one":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","other":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","up":true},{"one":"fb93417c597050f8deeb3098475f6cf0d213926332b59c25a538c1b4e6af89c0","other":"f9243ad864859edd351592d24abdf5c9c4e37f4ffe2ae1201c45f26576ee5725","up":true},{"one":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","other":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","up":true},{"one":"baf32cface953bd22141970b691567ed375f06043fe31de354598fb4230fb0a3","other":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","up":true},{"one":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","other":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","up":true},{"one":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","other":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","up":true},{"one":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","other":"cb69f673a54a75de300d1d1ab989caa04579504cbcaf78f03bc192c9578ec5a0","up":true},{"one":"72acf53ab7dbda90ea70e78d001fe21e7ab88e4054ff0453212e33a26fbbf7f1","other":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","up":true},{"one":"353875d4ae2b9c5cc6cb14059bdfb9540647100c7c3334bc60db2ca2b4ddf3f5","other":"89ee628583b6e0345ceb312c2e2cbf5ac410eb00531be903a4b94b60b639993b","up":true},{"one":"3345e03880c377fc1fe7b6205011ce3e32525cefee5e03161105f1512893f9fb","other":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","up":true},{"one":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","other":"d8222a2c177f0cd17baae28327af5c13f2fb551dd01298d17855ce7f6f4f6189","up":true},{"one":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"14418557af44e33fc8d46369426c3e2fc91622cb08779b3fefce706969ec2163","other":"1d5fc65b4121cccaff0d2a853c6bbff0c10b4ec28cfc5ef5cbea8bc28bc4cbec","up":true},{"one":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","other":"4cf655b03778bcb41c32664f13168dc32968354468c8aa699de52b43a2e0dc7d","up":true},{"one":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","other":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","up":true},{"one":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","other":"4019ce1dc667d90a85021b5fbf49ad77c1de7fd3443a40a17bdfaf5175efa1e1","up":true},{"one":"13d8107c669cc27f6cbc64cef6b2bd11f72db297f2857c2de7323b92632b8231","other":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","up":true},{"one":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","other":"13d8107c669cc27f6cbc64cef6b2bd11f72db297f2857c2de7323b92632b8231","up":true},{"one":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","other":"b310299bef4f4e84a1ecc960143e4b1bbe98cd4eb8352458c477569ca9994c20","up":true},{"one":"0592048ae4e6a3000f36e8e1c0e1328c26c0d4bb6369d6cd867a08d847dacb9e","other":"194ad126c41c487d95c83b19ed2bb053e4d2ec0605952d3a396dd4304e8e363c","up":true},{"one":"2f2284537629514837d5c2296d2871d8c4c147719f0ddc28ef7c629efd44dd61","other":"8163eebe696123817a6d998d7781612931df6fa2fd0293c0d0dd395d031c53f6","up":true},{"one":"d68f504755b48f8d41570169dd3544ef1caea2ab1bb334f8d8c5fb27fe816f01","other":"d3d254a3024a7aac01e97a5f0aff13756741867e599bc94c068f0d1d19ca8176","up":true},{"one":"9a82d22b29c88eaca2f20a62e9e63675904065e72c70ce2fbbefc0c9d2e53e90","other":"b71052b072cb98fb79151fd0c6044828e535a0547cace931183c7e4e7f227698","up":true},{"one":"b310299bef4f4e84a1ecc960143e4b1bbe98cd4eb8352458c477569ca9994c20","other":"c15d13aff7f5694fb8fc2a2154846ac703064133feb632e28830777bbfc7c016","up":true},{"one":"18f9292fbe6e94ec3df4d0a896c71ed2b1771437e42d8475a7aae63c7992834d","other":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","up":true},{"one":"99dbf361b67d6c370fd4f201b059e93da50dd88cde2e25d0bcf3c73afbf13535","other":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","up":true},{"one":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","other":"f4e0d91ca58ed616918bf268565c2edb4176293e851b8d29cbbda55bdc608534","up":true},{"one":"0f5e34b25f6851d0f3b572c64420de0aec01754bea0854bd7e9910b4b1cc66c3","other":"4019ce1dc667d90a85021b5fbf49ad77c1de7fd3443a40a17bdfaf5175efa1e1","up":true},{"one":"2f2284537629514837d5c2296d2871d8c4c147719f0ddc28ef7c629efd44dd61","other":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","up":true},{"one":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","other":"c0d1ac430a1466a1a28cb1aa3d29573ccedb13641ac19e6e615f2a96e8f0950b","up":true},{"one":"c0d1ac430a1466a1a28cb1aa3d29573ccedb13641ac19e6e615f2a96e8f0950b","other":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","up":true},{"one":"2279d612454ff034032a5acf5a039477a11479b9b9a93edc26b61312e8d9b156","other":"211a516fb95d093eb43f4716edeb1e6f76ed79d2aeee3393740e244970187840","up":true},{"one":"458a31b8fdbc42f5e80f01118c9dc041740212c09ddfb4b137c9e7e93ae9ec51","other":"167306fb0e24ddd775c2ac0722103ce5db98bb0ef60159b91f584475d34cb5bf","up":true},{"one":"8163eebe696123817a6d998d7781612931df6fa2fd0293c0d0dd395d031c53f6","other":"baf32cface953bd22141970b691567ed375f06043fe31de354598fb4230fb0a3","up":true},{"one":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","other":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","up":true},{"one":"7294223c0ab27851df8c25418acf09a28efa95fc238252af8a68390db07b77e4","other":"43afd835930eac91c0c8947001dd60d8da9112c19cf3f1356589b58b48962166","up":true},{"one":"b71052b072cb98fb79151fd0c6044828e535a0547cace931183c7e4e7f227698","other":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","up":true},{"one":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","other":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","up":true},{"one":"b310299bef4f4e84a1ecc960143e4b1bbe98cd4eb8352458c477569ca9994c20","other":"7de7eb0b73f3b02b998e592e0fc51b26c0c3ae6695d41897874d2a3d8ce3554b","up":true},{"one":"f5dd00d94488f3387d64ccc2115cd91e5073c06de36d195a5c1a95db23cd5c68","other":"e3c95a82f373da4522533252549c1d68b6f7621a887feb66199cfd4b7a35b6fc","up":true},{"one":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","other":"2279d612454ff034032a5acf5a039477a11479b9b9a93edc26b61312e8d9b156","up":true},{"one":"2e9f7d37106b59d6f411dfa0392325d1fa279fe45c18a101e32ece6716e88946","other":"211a516fb95d093eb43f4716edeb1e6f76ed79d2aeee3393740e244970187840","up":true},{"one":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","other":"a749372cccb7001a480b9416c41305bd1d3bc417e15669ae587d4375f2bbcbf7","up":true},{"one":"86f71d23eb4efeeed4679be7a3d0f58f98b83d23b3e0a8b73f60f1535b51f25c","other":"123fe1d442c7dc905a7206e712fbd9cea640295a6823b39b1945629ffa89e895","up":true},{"one":"def4f0b988942a8699f6b500cc2531c7dea065e3dc5bdcbeb26bca8f00e1d3a6","other":"c4632e164170d35e91fce46f660265f73795f1d840effce5ab2aae18c73003d7","up":true},{"one":"99aaaa7c98113955ecc0163943317f6ec08ae755e96c25c9f79ca1f529c60587","other":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","up":true},{"one":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","other":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","up":true},{"one":"1b869ca5aec1ff0c9980b06b79084019752fd4557054e251c0091ee5d2c7d478","other":"1d942c85568f640497c46a9bd5a9825bc6fb05571203a3f0f14a3032602d81fc","up":true},{"one":"42c019ddad2fde0f0c7f5041eb584996976844f8b2e08e20458437578d0257ef","other":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","up":true},{"one":"47f95708475c9d61ff2016e7707c4a3796f6c87313bd1d2bf91aa620113ba5f8","other":"def4f0b988942a8699f6b500cc2531c7dea065e3dc5bdcbeb26bca8f00e1d3a6","up":true},{"one":"ecd2b502d4e9bf77369c62d1142a9fb4ab1071e751cc6971e9e381e953b0fc12","other":"c15d13aff7f5694fb8fc2a2154846ac703064133feb632e28830777bbfc7c016","up":true},{"one":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","other":"167306fb0e24ddd775c2ac0722103ce5db98bb0ef60159b91f584475d34cb5bf","up":true},{"one":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","other":"3f3e66f7c8fc7daae8a7a84a9c90f1ea64faa43930f36fc8f11e2d7b751f8ee9","up":true},{"one":"15f6bb0640c3ac8ae8ecf10d0d46cc739bb6f86b0c0cf706103aad9f3785219b","other":"03f5f1be5c1ae35469333a5b08622372f65ef8f233746afde56d2647c5273630","up":true},{"one":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","other":"9a82d22b29c88eaca2f20a62e9e63675904065e72c70ce2fbbefc0c9d2e53e90","up":true},{"one":"b45d1aee940e5d2c71db22e2afbe908d5967e437fbb5bd051edea86ea4ac9d30","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"b4c7555a38d1d4e3bd1d01bcc47a84efd6128e8cc0fd803362da8b341f984a4d","other":"b60deba7c8676305b67af41d2de866bddd72edc0667d427e51fc3f2e91412b45","up":true},{"one":"4a828523731caf47f9aa2dfa20f8a8895e9a6562b3a034574639a229a733b4a6","other":"52881f1fe0b8e9044a3bc035edbbd6343b9c41d49c0f2c2ca799c2e764489b91","up":true},{"one":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","other":"d6d2d35b3819e03b39d7146c4dd1b53fb7b70681c8860464650d5eb2d09dda2c","up":true},{"one":"e67d72c8c72bbc7bec1d4e936d65adbeab75d1b6cafdf2c39e38bfc723cceebf","other":"c15d13aff7f5694fb8fc2a2154846ac703064133feb632e28830777bbfc7c016","up":true},{"one":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","other":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","up":true},{"one":"ed6570e63fc0e2e2d6290645c4339b70a0f1c14064d286b4e0ba143607bd70c8","other":"b60deba7c8676305b67af41d2de866bddd72edc0667d427e51fc3f2e91412b45","up":true},{"one":"5fab772fff883d04cf7e4713266fb1e98fa1746e12faeb04e87bcb37b8b61187","other":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","up":true},{"one":"15f6bb0640c3ac8ae8ecf10d0d46cc739bb6f86b0c0cf706103aad9f3785219b","other":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","up":true},{"one":"5fa8a5a29a47694653a5e55fd9b76e9c4f717ecc9b7088a6ec7ee273b291d49e","other":"21682cd736edfb8b451dfd9c55eb9ea3b7cf2e35cad32537f1f4800644fbf9a7","up":true},{"one":"bf5ac6f9b651895c8d75ab3ae7994f91981dbbcdce25a7cd718fc996da96946d","other":"df5ea72c0c257bbea9b43901a1e406bf6055a307d083367e7f1d944e65c2b3cd","up":true},{"one":"cb69f673a54a75de300d1d1ab989caa04579504cbcaf78f03bc192c9578ec5a0","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"a6720426faa1ea7e2d55c1c5ad05ba2e8c534d120ebb5e1375cf769d73191708","other":"1b7250dae1f2b07504b381d28084b751a26ac791a1ed842ea5746826ab85ba22","up":true},{"one":"29fd4473f1b6c37f8268d73dd5f683a0f7eb64bd793dab2c450c10d8ab0b666d","other":"9c0c7b6da33520440b181a5fcc30aa8a7576ec9c6f64acef0c9525d05a2ee393","up":true},{"one":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","other":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","up":true},{"one":"74062d9dd73e26066fd44e4f6c696f271c9dc6860bdc5ba3fcf447f455c8cafa","other":"67831469d7f9b779df4318cda9d4310c53fdd66332af3740e654233ceb3fbcbc","up":true},{"one":"c4844b8046d1c47f699ee1c232d945829105d45af26fba722ce4d0a937c99631","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"043f24ce87fbd01f6f663f323ac6c156810a67b12efbec2f219ae9a882a4bd16","other":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","up":true},{"one":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","other":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","up":true},{"one":"5fab772fff883d04cf7e4713266fb1e98fa1746e12faeb04e87bcb37b8b61187","other":"d68f504755b48f8d41570169dd3544ef1caea2ab1bb334f8d8c5fb27fe816f01","up":true},{"one":"14c81fb2fb8fa5a6b47288356ab486012046d09959b9ef4814a09dc3facb4853","other":"03f5f1be5c1ae35469333a5b08622372f65ef8f233746afde56d2647c5273630","up":true},{"one":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","other":"b310299bef4f4e84a1ecc960143e4b1bbe98cd4eb8352458c477569ca9994c20","up":true},{"one":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","other":"1b7250dae1f2b07504b381d28084b751a26ac791a1ed842ea5746826ab85ba22","up":true},{"one":"7d941474f90981a03c95af4cf68be163d01a317e202ac0db82e25f46fc3db8aa","other":"21682cd736edfb8b451dfd9c55eb9ea3b7cf2e35cad32537f1f4800644fbf9a7","up":true},{"one":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","other":"7471be56a2f617783d5a1ae4390794ad24f453259b7c1f8f65be8f71c8bf8bf7","up":true},{"one":"ae715a40e9bf50b4eb954c6b87efbf1a6e7abc3da6d84055e4591aadf5bad5c8","other":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","up":true},{"one":"af358f626738d2e5d17060abbaf24cb0124a48c5edb8f3baeda30bea7b403dfa","other":"a6720426faa1ea7e2d55c1c5ad05ba2e8c534d120ebb5e1375cf769d73191708","up":true},{"one":"7ffed78e11c71eccfdddf6a8e5e367831b26a6b11e4d3b60c5b9fc50f4bcdf33","other":"67831469d7f9b779df4318cda9d4310c53fdd66332af3740e654233ceb3fbcbc","up":true},{"one":"f1fcfc064446a432ac7a21efc386fa3d053d0ca4540fd4ddbbb253ec11f9ce94","other":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","up":true},{"one":"7d45f1e91bcd683d870422b2f591c76f6ab07ff4b079f35c280ce46f7fc364e1","other":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","up":true},{"one":"c8fe626c60d6cd15912a22d2c5872f10a8f8aef5b6f6b2f1070edec6670bf8b1","other":"c98d389a6d8e6eb5a1eb5a6f235857d5d939d30e51c16d80c039c9e0eb2f7eca","up":true},{"one":"c8fe626c60d6cd15912a22d2c5872f10a8f8aef5b6f6b2f1070edec6670bf8b1","other":"e787fd35b438dbae3d0283f7181fc4afbeb6f333201ad4cd1b46bf7094a6a85f","up":true},{"one":"e4c3fa0700516da5650f794067eb1c90ba7eaab2b65019bf34552c26f8c1d9a6","other":"167306fb0e24ddd775c2ac0722103ce5db98bb0ef60159b91f584475d34cb5bf","up":true},{"one":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","other":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","up":true},{"one":"9a82d22b29c88eaca2f20a62e9e63675904065e72c70ce2fbbefc0c9d2e53e90","other":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","up":true},{"one":"9390ef61753fcf9dae5e585a85650287fe1f792538721a2cf767adc44c5e7ce0","other":"c3f356325eb1e61f2cfd8d80d085fb144c6887e1d710c9d133109b84b823425d","up":true},{"one":"f048e37427d68c4134a115cc7df6a8924ea95f24e7f2435372ae331e24b5b7fd","other":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","up":true},{"one":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","other":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","up":true},{"one":"b4639751d67db0b32ad1846996fed0d5c59be76b77efaec5f66e64df35eefae8","other":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","up":true},{"one":"32dd86f3f5c3abe55d6b1fe352e9d1d0cc63d61cfa60b8e70b2ee9db2e28e1aa","other":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","up":true},{"one":"c98d389a6d8e6eb5a1eb5a6f235857d5d939d30e51c16d80c039c9e0eb2f7eca","other":"c7703c4697637f40a50a5d0b656c4e54010ef1277b64d251ccd0752512e246e9","up":true},{"one":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","other":"985c14910d922bbd1556c30620e3ab72170da6553e658dd3806d773a734a1c93","up":true},{"one":"1b7250dae1f2b07504b381d28084b751a26ac791a1ed842ea5746826ab85ba22","other":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","up":true},{"one":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","other":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","up":true},{"one":"9fee945c89d6bb59e77a24183c2855f4eece0b5d3266f750ea95483eae1e1871","other":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","up":true},{"one":"237459ba37141f18b3cd40f5ec9cdaf6789fb87aa4511d489fdd98a960ef2883","other":"3af3bb6c4343a19e3c0f9a89697cd70a3c79c64b1e40afa59b188c5553eed567","up":true},{"one":"4a81ee929074bd6d9932fb4d970f5207db8680bda0e951aa72a6bd64df45108d","other":"259d3874697a0c3192f973c620880b2a86567289f5222d07eb91a51867352559","up":true},{"one":"985c14910d922bbd1556c30620e3ab72170da6553e658dd3806d773a734a1c93","other":"a749372cccb7001a480b9416c41305bd1d3bc417e15669ae587d4375f2bbcbf7","up":true},{"one":"3af3bb6c4343a19e3c0f9a89697cd70a3c79c64b1e40afa59b188c5553eed567","other":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","up":true},{"one":"9c0c7b6da33520440b181a5fcc30aa8a7576ec9c6f64acef0c9525d05a2ee393","other":"c3f356325eb1e61f2cfd8d80d085fb144c6887e1d710c9d133109b84b823425d","up":true},{"one":"96b6ec2cda7c9e962bbc97e7916b92d69b112265c4456d30dade806e09fea62e","other":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","up":true},{"one":"c99c6a2a2fe7f4613c8d918404c956eae0a00fce544c9eeba5da640e5e958874","other":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","up":true},{"one":"e8392bdce92a7dcfa5e3196a6e1d4e5dc4814c72e104d3e2aa1f1ab99e2a47a6","other":"c0d1ac430a1466a1a28cb1aa3d29573ccedb13641ac19e6e615f2a96e8f0950b","up":true},{"one":"52619a523050aa8caa32448e59721a76dc3321dc92cfcb1b05f1cec5b79d424a","other":"604c6bfab4f5d16d5b786318cf8f9e0197fc18e5d8a8e6f4fd345e653d2abe17","up":true},{"one":"03f5f1be5c1ae35469333a5b08622372f65ef8f233746afde56d2647c5273630","other":"0f81caba7d2f752fc670829f74565114bcf10c5b97d7f93252b467d0eee278b3","up":true},{"one":"b60deba7c8676305b67af41d2de866bddd72edc0667d427e51fc3f2e91412b45","other":"9c0c7b6da33520440b181a5fcc30aa8a7576ec9c6f64acef0c9525d05a2ee393","up":true},{"one":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","other":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","up":true},{"one":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","other":"abfabc2964ce2f7b8b3702e897a8cc83fb0c009d5af9670b889db582e57ef7c1","up":true},{"one":"0ea201515867c6793cbe48350f209e69acd3829b9b16b6016d965d84fb6379c5","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","other":"edca23c0fccfd1ec66f4d39bb129462776b9091c2337f6afbfe8737a0ebc0d7d","up":true},{"one":"2fd800d224f124e47038bf140ebd486f1d0ab93169c998d2dfabfd1d3ced96ed","other":"259d3874697a0c3192f973c620880b2a86567289f5222d07eb91a51867352559","up":true},{"one":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","other":"52881f1fe0b8e9044a3bc035edbbd6343b9c41d49c0f2c2ca799c2e764489b91","up":true},{"one":"b310299bef4f4e84a1ecc960143e4b1bbe98cd4eb8352458c477569ca9994c20","other":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","up":true},{"one":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","other":"ed1348b4037004b61bd4d6f9be1648112f835943a3b997b91ac931301311e007","up":true},{"one":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","other":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","up":true},{"one":"ed1348b4037004b61bd4d6f9be1648112f835943a3b997b91ac931301311e007","other":"cb69f673a54a75de300d1d1ab989caa04579504cbcaf78f03bc192c9578ec5a0","up":true},{"one":"b45d1aee940e5d2c71db22e2afbe908d5967e437fbb5bd051edea86ea4ac9d30","other":"cb69f673a54a75de300d1d1ab989caa04579504cbcaf78f03bc192c9578ec5a0","up":true},{"one":"265d7fc553fb8dc016d7c6ccc31fc372912a11e9e8da0e5822e371a91d82e857","other":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","up":true},{"one":"237459ba37141f18b3cd40f5ec9cdaf6789fb87aa4511d489fdd98a960ef2883","other":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","up":true},{"one":"15f6bb0640c3ac8ae8ecf10d0d46cc739bb6f86b0c0cf706103aad9f3785219b","other":"1b869ca5aec1ff0c9980b06b79084019752fd4557054e251c0091ee5d2c7d478","up":true},{"one":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","other":"4a670fe61711307f7781b3b5a8014fc0e63866efe356a25e61b2e768a7389980","up":true},{"one":"a485db6ec1929aaf695a84294f52905aa602584954e9ef71b4e18f7b847da31d","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"ceee9ac63786ad68d884f5d018248ae1b7b60f0ad434776d29bd4dfcdc0f04c1","other":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","up":true},{"one":"3af3bb6c4343a19e3c0f9a89697cd70a3c79c64b1e40afa59b188c5553eed567","other":"0ea201515867c6793cbe48350f209e69acd3829b9b16b6016d965d84fb6379c5","up":true},{"one":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","other":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","up":true},{"one":"c15d13aff7f5694fb8fc2a2154846ac703064133feb632e28830777bbfc7c016","other":"f4eee71a143722ea8288279876bf5687c1ed65eede0129e987e53f5ec76d8543","up":true},{"one":"4a828523731caf47f9aa2dfa20f8a8895e9a6562b3a034574639a229a733b4a6","other":"14c81fb2fb8fa5a6b47288356ab486012046d09959b9ef4814a09dc3facb4853","up":true},{"one":"13d8107c669cc27f6cbc64cef6b2bd11f72db297f2857c2de7323b92632b8231","other":"2f2284537629514837d5c2296d2871d8c4c147719f0ddc28ef7c629efd44dd61","up":true},{"one":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","other":"52881f1fe0b8e9044a3bc035edbbd6343b9c41d49c0f2c2ca799c2e764489b91","up":true},{"one":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","other":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","up":true},{"one":"0592048ae4e6a3000f36e8e1c0e1328c26c0d4bb6369d6cd867a08d847dacb9e","other":"0f81caba7d2f752fc670829f74565114bcf10c5b97d7f93252b467d0eee278b3","up":true},{"one":"7854fb9f0d2f016653994b9e23033f27e21a2ef186a352c8d068b75ea20beec2","other":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","up":true},{"one":"1a02108d9149739be6ba85ddd1e8a3961adeec38f77fcef95ab57f8f2f39f4bb","other":"66100e4f9d103d935164f39ea3f0332708f3825ebcf31ac6f8c9714c75a50860","up":true},{"one":"1d942c85568f640497c46a9bd5a9825bc6fb05571203a3f0f14a3032602d81fc","other":"194ad126c41c487d95c83b19ed2bb053e4d2ec0605952d3a396dd4304e8e363c","up":true},{"one":"730749922ce3dcd8e249b988dbfb950c15473a2f6fd5cfe2a8126995fb1dc186","other":"72acf53ab7dbda90ea70e78d001fe21e7ab88e4054ff0453212e33a26fbbf7f1","up":true},{"one":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","other":"e8392bdce92a7dcfa5e3196a6e1d4e5dc4814c72e104d3e2aa1f1ab99e2a47a6","up":true},{"one":"7de7eb0b73f3b02b998e592e0fc51b26c0c3ae6695d41897874d2a3d8ce3554b","other":"4a670fe61711307f7781b3b5a8014fc0e63866efe356a25e61b2e768a7389980","up":true},{"one":"e67d72c8c72bbc7bec1d4e936d65adbeab75d1b6cafdf2c39e38bfc723cceebf","other":"f4eee71a143722ea8288279876bf5687c1ed65eede0129e987e53f5ec76d8543","up":true},{"one":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","other":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","up":true},{"one":"52881f1fe0b8e9044a3bc035edbbd6343b9c41d49c0f2c2ca799c2e764489b91","other":"1b869ca5aec1ff0c9980b06b79084019752fd4557054e251c0091ee5d2c7d478","up":true},{"one":"bf5ac6f9b651895c8d75ab3ae7994f91981dbbcdce25a7cd718fc996da96946d","other":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","up":true},{"one":"8163eebe696123817a6d998d7781612931df6fa2fd0293c0d0dd395d031c53f6","other":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","up":true},{"one":"4019ce1dc667d90a85021b5fbf49ad77c1de7fd3443a40a17bdfaf5175efa1e1","other":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","up":true},{"one":"ecd2b502d4e9bf77369c62d1142a9fb4ab1071e751cc6971e9e381e953b0fc12","other":"f4eee71a143722ea8288279876bf5687c1ed65eede0129e987e53f5ec76d8543","up":true},{"one":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","other":"2f2284537629514837d5c2296d2871d8c4c147719f0ddc28ef7c629efd44dd61","up":true},{"one":"4f90f53754ee50357491dbab7c3e6748b40133e3339920b32f4c3457f4258477","other":"604c6bfab4f5d16d5b786318cf8f9e0197fc18e5d8a8e6f4fd345e653d2abe17","up":true},{"one":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","other":"2fd800d224f124e47038bf140ebd486f1d0ab93169c998d2dfabfd1d3ced96ed","up":true},{"one":"4cf655b03778bcb41c32664f13168dc32968354468c8aa699de52b43a2e0dc7d","other":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","up":true},{"one":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","other":"4cf655b03778bcb41c32664f13168dc32968354468c8aa699de52b43a2e0dc7d","up":true},{"one":"21682cd736edfb8b451dfd9c55eb9ea3b7cf2e35cad32537f1f4800644fbf9a7","other":"2af02e62ff3e40be25d22c2f745a505b895b3804a80663cb53db4edd5365c568","up":true},{"one":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","other":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","up":true},{"one":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","other":"d3d254a3024a7aac01e97a5f0aff13756741867e599bc94c068f0d1d19ca8176","up":true},{"one":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","other":"f4e0d91ca58ed616918bf268565c2edb4176293e851b8d29cbbda55bdc608534","up":true},{"one":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","other":"730749922ce3dcd8e249b988dbfb950c15473a2f6fd5cfe2a8126995fb1dc186","up":true},{"one":"5fab772fff883d04cf7e4713266fb1e98fa1746e12faeb04e87bcb37b8b61187","other":"4019ce1dc667d90a85021b5fbf49ad77c1de7fd3443a40a17bdfaf5175efa1e1","up":true},{"one":"290fca7596740129fd1a3075bbf56885d8c83381f981adb0fb283799375a3bd5","other":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","up":true},{"one":"730749922ce3dcd8e249b988dbfb950c15473a2f6fd5cfe2a8126995fb1dc186","other":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","up":true},{"one":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","other":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","up":true},{"one":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","other":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","up":true},{"one":"fb93417c597050f8deeb3098475f6cf0d213926332b59c25a538c1b4e6af89c0","other":"e8392bdce92a7dcfa5e3196a6e1d4e5dc4814c72e104d3e2aa1f1ab99e2a47a6","up":true},{"one":"d68f504755b48f8d41570169dd3544ef1caea2ab1bb334f8d8c5fb27fe816f01","other":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","up":true},{"one":"e8392bdce92a7dcfa5e3196a6e1d4e5dc4814c72e104d3e2aa1f1ab99e2a47a6","other":"baf32cface953bd22141970b691567ed375f06043fe31de354598fb4230fb0a3","up":true},{"one":"5110c7e54a18df6963919866e7f6c0c3a483045d5ac20fc0dfa5e1926681be7f","other":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","up":true},{"one":"2f2284537629514837d5c2296d2871d8c4c147719f0ddc28ef7c629efd44dd61","other":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","up":true},{"one":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","other":"f048e37427d68c4134a115cc7df6a8924ea95f24e7f2435372ae331e24b5b7fd","up":true},{"one":"0f5e34b25f6851d0f3b572c64420de0aec01754bea0854bd7e9910b4b1cc66c3","other":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","up":true},{"one":"3345e03880c377fc1fe7b6205011ce3e32525cefee5e03161105f1512893f9fb","other":"3e85d2d3d6750f84500fcf523743e0c242943e69188cb83114adad1787e6c089","up":true},{"one":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","other":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","up":true},{"one":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","other":"4cf655b03778bcb41c32664f13168dc32968354468c8aa699de52b43a2e0dc7d","up":true},{"one":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","other":"1a02108d9149739be6ba85ddd1e8a3961adeec38f77fcef95ab57f8f2f39f4bb","up":true},{"one":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","other":"4d447ea80bc126e57b9fc91203a310c5f4b242df5ad70d5087d16697a626e601","up":true},{"one":"5f056f9593be46d3eba4dc8d9e0b93262cd3f00e007c13c4c011d53e7c199628","other":"21682cd736edfb8b451dfd9c55eb9ea3b7cf2e35cad32537f1f4800644fbf9a7","up":true},{"one":"985c14910d922bbd1556c30620e3ab72170da6553e658dd3806d773a734a1c93","other":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","up":true},{"one":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","other":"72acf53ab7dbda90ea70e78d001fe21e7ab88e4054ff0453212e33a26fbbf7f1","up":true},{"one":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","other":"1d5fc65b4121cccaff0d2a853c6bbff0c10b4ec28cfc5ef5cbea8bc28bc4cbec","up":true},{"one":"95e0529227286d442cdeeed428614f99c19f9223426e6f6cefbef2d9d47ce96f","other":"86128689b03706f9a86baf4753154afbb0c535f84089039ef5610d82fb969a6e","up":true},{"one":"b79fd26ce97e98e7432029dea6bf11e48ea9e8f2385dec5c60be0cbd51bbef54","other":"985c14910d922bbd1556c30620e3ab72170da6553e658dd3806d773a734a1c93","up":true},{"one":"baf32cface953bd22141970b691567ed375f06043fe31de354598fb4230fb0a3","other":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","up":true},{"one":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","other":"72acf53ab7dbda90ea70e78d001fe21e7ab88e4054ff0453212e33a26fbbf7f1","up":true},{"one":"4019ce1dc667d90a85021b5fbf49ad77c1de7fd3443a40a17bdfaf5175efa1e1","other":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","up":true},{"one":"72acf53ab7dbda90ea70e78d001fe21e7ab88e4054ff0453212e33a26fbbf7f1","other":"4cf655b03778bcb41c32664f13168dc32968354468c8aa699de52b43a2e0dc7d","up":true},{"one":"e3c95a82f373da4522533252549c1d68b6f7621a887feb66199cfd4b7a35b6fc","other":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","up":true},{"one":"571638637156bdaf92d9c715647a1cf64e91877c2d998658b7c6cd27522271bd","other":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","up":true},{"one":"5110c7e54a18df6963919866e7f6c0c3a483045d5ac20fc0dfa5e1926681be7f","other":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","up":true},{"one":"7a4118d2ce16e8258cc2cdf31c5891945e3787c46c0836fc916601584609b7db","other":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","up":true},{"one":"14418557af44e33fc8d46369426c3e2fc91622cb08779b3fefce706969ec2163","other":"86128689b03706f9a86baf4753154afbb0c535f84089039ef5610d82fb969a6e","up":true},{"one":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","other":"2e9f7d37106b59d6f411dfa0392325d1fa279fe45c18a101e32ece6716e88946","up":true},{"one":"66100e4f9d103d935164f39ea3f0332708f3825ebcf31ac6f8c9714c75a50860","other":"604c6bfab4f5d16d5b786318cf8f9e0197fc18e5d8a8e6f4fd345e653d2abe17","up":true},{"one":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","other":"245495250d38095106022d3fc905eb7eb6f54adb2cb0bacdda0b305974eaa50a","up":true},{"one":"c0d1ac430a1466a1a28cb1aa3d29573ccedb13641ac19e6e615f2a96e8f0950b","other":"f4e0d91ca58ed616918bf268565c2edb4176293e851b8d29cbbda55bdc608534","up":true},{"one":"79abd33d150fdbcbce6f55f3d101608b80e03ede7e4627bc06ba5c1b001538a4","other":"571638637156bdaf92d9c715647a1cf64e91877c2d998658b7c6cd27522271bd","up":true},{"one":"004e812995bc04f22993466df9889ca1b650bba242cf80bb070ce9564e7bedd8","other":"86128689b03706f9a86baf4753154afbb0c535f84089039ef5610d82fb969a6e","up":true},{"one":"2e9f7d37106b59d6f411dfa0392325d1fa279fe45c18a101e32ece6716e88946","other":"2279d612454ff034032a5acf5a039477a11479b9b9a93edc26b61312e8d9b156","up":true},{"one":"259d3874697a0c3192f973c620880b2a86567289f5222d07eb91a51867352559","other":"194ad126c41c487d95c83b19ed2bb053e4d2ec0605952d3a396dd4304e8e363c","up":true},{"one":"67831469d7f9b779df4318cda9d4310c53fdd66332af3740e654233ceb3fbcbc","other":"29fd4473f1b6c37f8268d73dd5f683a0f7eb64bd793dab2c450c10d8ab0b666d","up":true},{"one":"47f95708475c9d61ff2016e7707c4a3796f6c87313bd1d2bf91aa620113ba5f8","other":"99fba7334f47bfcf21331d6179bfb2eee2ecebd1ea23fd829c542844b10ed974","up":true},{"one":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","other":"211a516fb95d093eb43f4716edeb1e6f76ed79d2aeee3393740e244970187840","up":true},{"one":"a80b39cf280fa80b323370cfc37e8e3ff38de47d5de445c500f36c8cf2390b53","other":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","up":true},{"one":"1d5fc65b4121cccaff0d2a853c6bbff0c10b4ec28cfc5ef5cbea8bc28bc4cbec","other":"86128689b03706f9a86baf4753154afbb0c535f84089039ef5610d82fb969a6e","up":true},{"one":"c4632e164170d35e91fce46f660265f73795f1d840effce5ab2aae18c73003d7","other":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","up":true},{"one":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","other":"adfc03118406bc5af4e3f3c6c89c3448c3fe4f9df20b15bcca9bbacf58ee5457","up":true},{"one":"f5dd00d94488f3387d64ccc2115cd91e5073c06de36d195a5c1a95db23cd5c68","other":"c3f356325eb1e61f2cfd8d80d085fb144c6887e1d710c9d133109b84b823425d","up":true},{"one":"458a31b8fdbc42f5e80f01118c9dc041740212c09ddfb4b137c9e7e93ae9ec51","other":"7294223c0ab27851df8c25418acf09a28efa95fc238252af8a68390db07b77e4","up":true},{"one":"0210146e650132dd0ceb8cea779cafc5485822eeeb9fcc17f292af5cef6e9632","other":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","up":true},{"one":"c3f356325eb1e61f2cfd8d80d085fb144c6887e1d710c9d133109b84b823425d","other":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","up":true},{"one":"f1fcfc064446a432ac7a21efc386fa3d053d0ca4540fd4ddbbb253ec11f9ce94","other":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","up":true},{"one":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","other":"9294e5b39a1cd4230400e033da1ac1f79a4025adab41ce8c0e0e47dda2418cb5","up":true},{"one":"7294223c0ab27851df8c25418acf09a28efa95fc238252af8a68390db07b77e4","other":"670d1c31407b4f34bb955031e9a3ab7980273155080b459a082687d53a3b42a6","up":true},{"one":"77ec3a73ce1d597d307e87ea8d026baae2a7df4d288a657f240eaaaae7fdb456","other":"7294223c0ab27851df8c25418acf09a28efa95fc238252af8a68390db07b77e4","up":true},{"one":"e4c3fa0700516da5650f794067eb1c90ba7eaab2b65019bf34552c26f8c1d9a6","other":"c3f356325eb1e61f2cfd8d80d085fb144c6887e1d710c9d133109b84b823425d","up":true},{"one":"9390ef61753fcf9dae5e585a85650287fe1f792538721a2cf767adc44c5e7ce0","other":"ed6570e63fc0e2e2d6290645c4339b70a0f1c14064d286b4e0ba143607bd70c8","up":true},{"one":"9fee945c89d6bb59e77a24183c2855f4eece0b5d3266f750ea95483eae1e1871","other":"ae715a40e9bf50b4eb954c6b87efbf1a6e7abc3da6d84055e4591aadf5bad5c8","up":true},{"one":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","other":"043f24ce87fbd01f6f663f323ac6c156810a67b12efbec2f219ae9a882a4bd16","up":true},{"one":"18f9292fbe6e94ec3df4d0a896c71ed2b1771437e42d8475a7aae63c7992834d","other":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","up":true},{"one":"ae715a40e9bf50b4eb954c6b87efbf1a6e7abc3da6d84055e4591aadf5bad5c8","other":"d6d2d35b3819e03b39d7146c4dd1b53fb7b70681c8860464650d5eb2d09dda2c","up":true},{"one":"dc3ec6529cec19709403e473d4546ba07f7aaaaec5be7cb0618246a4c0458944","other":"290fca7596740129fd1a3075bbf56885d8c83381f981adb0fb283799375a3bd5","up":true},{"one":"edca23c0fccfd1ec66f4d39bb129462776b9091c2337f6afbfe8737a0ebc0d7d","other":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","up":true},{"one":"7ffed78e11c71eccfdddf6a8e5e367831b26a6b11e4d3b60c5b9fc50f4bcdf33","other":"29fd4473f1b6c37f8268d73dd5f683a0f7eb64bd793dab2c450c10d8ab0b666d","up":true},{"one":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","other":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","up":true},{"one":"2279d612454ff034032a5acf5a039477a11479b9b9a93edc26b61312e8d9b156","other":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","up":true},{"one":"245495250d38095106022d3fc905eb7eb6f54adb2cb0bacdda0b305974eaa50a","other":"004e812995bc04f22993466df9889ca1b650bba242cf80bb070ce9564e7bedd8","up":true},{"one":"99aaaa7c98113955ecc0163943317f6ec08ae755e96c25c9f79ca1f529c60587","other":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","up":true},{"one":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","other":"487edf32027381256a864bc2e9bca175f7216cc1234731bd4dc90d2cbad6975d","up":true},{"one":"6544c4a403d9809697420ff9840ea9de0c9ba6491de28dac288db6257f2dc218","other":"7d941474f90981a03c95af4cf68be163d01a317e202ac0db82e25f46fc3db8aa","up":true},{"one":"32dd86f3f5c3abe55d6b1fe352e9d1d0cc63d61cfa60b8e70b2ee9db2e28e1aa","other":"7d45f1e91bcd683d870422b2f591c76f6ab07ff4b079f35c280ce46f7fc364e1","up":true},{"one":"571638637156bdaf92d9c715647a1cf64e91877c2d998658b7c6cd27522271bd","other":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","up":true},{"one":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","other":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","up":true},{"one":"e3c95a82f373da4522533252549c1d68b6f7621a887feb66199cfd4b7a35b6fc","other":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","up":true},{"one":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"99dbf361b67d6c370fd4f201b059e93da50dd88cde2e25d0bcf3c73afbf13535","other":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","up":true},{"one":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","other":"3af3bb6c4343a19e3c0f9a89697cd70a3c79c64b1e40afa59b188c5553eed567","up":true},{"one":"6d219b0ea4a1620a7ba051cc478da6df9ea443e87dde79e963ec789a5bad7459","other":"5fa8a5a29a47694653a5e55fd9b76e9c4f717ecc9b7088a6ec7ee273b291d49e","up":true},{"one":"43afd835930eac91c0c8947001dd60d8da9112c19cf3f1356589b58b48962166","other":"458a31b8fdbc42f5e80f01118c9dc041740212c09ddfb4b137c9e7e93ae9ec51","up":true},{"one":"adfc03118406bc5af4e3f3c6c89c3448c3fe4f9df20b15bcca9bbacf58ee5457","other":"aa504681c991c69015c7ef0469902c44fc4e900356b181aa252a1280ffbd2e3b","up":true},{"one":"3f3e66f7c8fc7daae8a7a84a9c90f1ea64faa43930f36fc8f11e2d7b751f8ee9","other":"77ec3a73ce1d597d307e87ea8d026baae2a7df4d288a657f240eaaaae7fdb456","up":true},{"one":"dc3ec6529cec19709403e473d4546ba07f7aaaaec5be7cb0618246a4c0458944","other":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","up":true},{"one":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","other":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","up":true},{"one":"ed6570e63fc0e2e2d6290645c4339b70a0f1c14064d286b4e0ba143607bd70c8","other":"9c0c7b6da33520440b181a5fcc30aa8a7576ec9c6f64acef0c9525d05a2ee393","up":true},{"one":"b4c7555a38d1d4e3bd1d01bcc47a84efd6128e8cc0fd803362da8b341f984a4d","other":"9c0c7b6da33520440b181a5fcc30aa8a7576ec9c6f64acef0c9525d05a2ee393","up":true},{"one":"86f71d23eb4efeeed4679be7a3d0f58f98b83d23b3e0a8b73f60f1535b51f25c","other":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","up":true},{"one":"f4eee71a143722ea8288279876bf5687c1ed65eede0129e987e53f5ec76d8543","other":"7de7eb0b73f3b02b998e592e0fc51b26c0c3ae6695d41897874d2a3d8ce3554b","up":true},{"one":"31edba65e539a9a3c7107a131c4fb4b5a8e2c2d4e2db7874a0116c173961e27c","other":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","up":true},{"one":"29fd4473f1b6c37f8268d73dd5f683a0f7eb64bd793dab2c450c10d8ab0b666d","other":"b60deba7c8676305b67af41d2de866bddd72edc0667d427e51fc3f2e91412b45","up":true},{"one":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","other":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","up":true},{"one":"42c019ddad2fde0f0c7f5041eb584996976844f8b2e08e20458437578d0257ef","other":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","up":true},{"one":"f4e0d91ca58ed616918bf268565c2edb4176293e851b8d29cbbda55bdc608534","other":"f9243ad864859edd351592d24abdf5c9c4e37f4ffe2ae1201c45f26576ee5725","up":true},{"one":"86f71d23eb4efeeed4679be7a3d0f58f98b83d23b3e0a8b73f60f1535b51f25c","other":"004e812995bc04f22993466df9889ca1b650bba242cf80bb070ce9564e7bedd8","up":true},{"one":"4fd6a4b99b1ba76775594673421c8eea175c114519c8554798c2c364175bbaae","other":"52619a523050aa8caa32448e59721a76dc3321dc92cfcb1b05f1cec5b79d424a","up":true},{"one":"79abd33d150fdbcbce6f55f3d101608b80e03ede7e4627bc06ba5c1b001538a4","other":"31edba65e539a9a3c7107a131c4fb4b5a8e2c2d4e2db7874a0116c173961e27c","up":true},{"one":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","other":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","up":true},{"one":"1b1eca7b35086d8b217431c1d6a01fc07dc8bc816196a80015c9e163bed676ff","other":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","up":true},{"one":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","other":"0210146e650132dd0ceb8cea779cafc5485822eeeb9fcc17f292af5cef6e9632","up":true},{"one":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","other":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","up":true},{"one":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","other":"86f71d23eb4efeeed4679be7a3d0f58f98b83d23b3e0a8b73f60f1535b51f25c","up":true},{"one":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","other":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","up":true},{"one":"ad36b802e9b01727e516879c0055e3c46b810845794f7772ad85e8062111b167","other":"b60deba7c8676305b67af41d2de866bddd72edc0667d427e51fc3f2e91412b45","up":true},{"one":"f4eee71a143722ea8288279876bf5687c1ed65eede0129e987e53f5ec76d8543","other":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","up":true},{"one":"211a516fb95d093eb43f4716edeb1e6f76ed79d2aeee3393740e244970187840","other":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","up":true},{"one":"1b869ca5aec1ff0c9980b06b79084019752fd4557054e251c0091ee5d2c7d478","other":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","up":true},{"one":"ad36b802e9b01727e516879c0055e3c46b810845794f7772ad85e8062111b167","other":"9c0c7b6da33520440b181a5fcc30aa8a7576ec9c6f64acef0c9525d05a2ee393","up":true},{"one":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","other":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","up":true},{"one":"e787fd35b438dbae3d0283f7181fc4afbeb6f333201ad4cd1b46bf7094a6a85f","other":"c7703c4697637f40a50a5d0b656c4e54010ef1277b64d251ccd0752512e246e9","up":true},{"one":"a6720426faa1ea7e2d55c1c5ad05ba2e8c534d120ebb5e1375cf769d73191708","other":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","up":true},{"one":"211a516fb95d093eb43f4716edeb1e6f76ed79d2aeee3393740e244970187840","other":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","up":true},{"one":"194ad126c41c487d95c83b19ed2bb053e4d2ec0605952d3a396dd4304e8e363c","other":"1835f62fe3889f43ba7b4b577869ec764e96fa9a051f1301fb96795daa8b0755","up":true},{"one":"86128689b03706f9a86baf4753154afbb0c535f84089039ef5610d82fb969a6e","other":"a80b39cf280fa80b323370cfc37e8e3ff38de47d5de445c500f36c8cf2390b53","up":true},{"one":"86128689b03706f9a86baf4753154afbb0c535f84089039ef5610d82fb969a6e","other":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","up":true},{"one":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","other":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","up":true},{"one":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","other":"c63e796efdc44712403199c11c3e5ff28e5d6a4d9ecdf3e95620fbd6afb1bc2d","up":true},{"one":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","other":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","up":true},{"one":"46c5a266dd46041a62f21946290aad736d2298277bbbb16b1efb4490d9d31fa8","other":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","up":true},{"one":"c99c6a2a2fe7f4613c8d918404c956eae0a00fce544c9eeba5da640e5e958874","other":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","up":true},{"one":"2f9f266a3b660fe685de2932de82979bd221706db1e2b954a93edc6609c378d9","other":"2fd800d224f124e47038bf140ebd486f1d0ab93169c998d2dfabfd1d3ced96ed","up":true},{"one":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","other":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","up":true},{"one":"f048e37427d68c4134a115cc7df6a8924ea95f24e7f2435372ae331e24b5b7fd","other":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","up":true},{"one":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","other":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","up":true},{"one":"1835f62fe3889f43ba7b4b577869ec764e96fa9a051f1301fb96795daa8b0755","other":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","up":true},{"one":"af358f626738d2e5d17060abbaf24cb0124a48c5edb8f3baeda30bea7b403dfa","other":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","up":true},{"one":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","other":"194ad126c41c487d95c83b19ed2bb053e4d2ec0605952d3a396dd4304e8e363c","up":true},{"one":"88da9dccda89c111dde40318fd1e5239e77b670f5a1b88a3e9320b7350885e31","other":"baf32cface953bd22141970b691567ed375f06043fe31de354598fb4230fb0a3","up":true},{"one":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","other":"e3c95a82f373da4522533252549c1d68b6f7621a887feb66199cfd4b7a35b6fc","up":true},{"one":"c63e796efdc44712403199c11c3e5ff28e5d6a4d9ecdf3e95620fbd6afb1bc2d","other":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","up":true},{"one":"daa2c427e8ecc566e634908fb0d47077d8e79854bf884342c06c4bcb355e80e6","other":"96b6ec2cda7c9e962bbc97e7916b92d69b112265c4456d30dade806e09fea62e","up":true},{"one":"b4639751d67db0b32ad1846996fed0d5c59be76b77efaec5f66e64df35eefae8","other":"c7703c4697637f40a50a5d0b656c4e54010ef1277b64d251ccd0752512e246e9","up":true},{"one":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","other":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","up":true},{"one":"c4632e164170d35e91fce46f660265f73795f1d840effce5ab2aae18c73003d7","other":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","up":true},{"one":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","other":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","up":true},{"one":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","other":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","up":true},{"one":"46c5a266dd46041a62f21946290aad736d2298277bbbb16b1efb4490d9d31fa8","other":"9294e5b39a1cd4230400e033da1ac1f79a4025adab41ce8c0e0e47dda2418cb5","up":true},{"one":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","other":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","up":true},{"one":"7d941474f90981a03c95af4cf68be163d01a317e202ac0db82e25f46fc3db8aa","other":"5fa8a5a29a47694653a5e55fd9b76e9c4f717ecc9b7088a6ec7ee273b291d49e","up":true},{"one":"b4639751d67db0b32ad1846996fed0d5c59be76b77efaec5f66e64df35eefae8","other":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","up":true},{"one":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","other":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","up":true},{"one":"6544c4a403d9809697420ff9840ea9de0c9ba6491de28dac288db6257f2dc218","other":"5fa8a5a29a47694653a5e55fd9b76e9c4f717ecc9b7088a6ec7ee273b291d49e","up":true},{"one":"604c6bfab4f5d16d5b786318cf8f9e0197fc18e5d8a8e6f4fd345e653d2abe17","other":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","up":true},{"one":"670d1c31407b4f34bb955031e9a3ab7980273155080b459a082687d53a3b42a6","other":"b4c7555a38d1d4e3bd1d01bcc47a84efd6128e8cc0fd803362da8b341f984a4d","up":true},{"one":"21682cd736edfb8b451dfd9c55eb9ea3b7cf2e35cad32537f1f4800644fbf9a7","other":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","up":true},{"one":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","other":"d3d254a3024a7aac01e97a5f0aff13756741867e599bc94c068f0d1d19ca8176","up":true},{"one":"1a02108d9149739be6ba85ddd1e8a3961adeec38f77fcef95ab57f8f2f39f4bb","other":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","up":true},{"one":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","other":"29fd4473f1b6c37f8268d73dd5f683a0f7eb64bd793dab2c450c10d8ab0b666d","up":true},{"one":"9c0c7b6da33520440b181a5fcc30aa8a7576ec9c6f64acef0c9525d05a2ee393","other":"670d1c31407b4f34bb955031e9a3ab7980273155080b459a082687d53a3b42a6","up":true},{"one":"c7703c4697637f40a50a5d0b656c4e54010ef1277b64d251ccd0752512e246e9","other":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","up":true},{"one":"f78a54ba4b3fa75cdb542bdcd6da0d49986a5f7e7939959a51b3b48a87605654","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"2af02e62ff3e40be25d22c2f745a505b895b3804a80663cb53db4edd5365c568","other":"5fa8a5a29a47694653a5e55fd9b76e9c4f717ecc9b7088a6ec7ee273b291d49e","up":true},{"one":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","other":"72acf53ab7dbda90ea70e78d001fe21e7ab88e4054ff0453212e33a26fbbf7f1","up":true},{"one":"abfabc2964ce2f7b8b3702e897a8cc83fb0c009d5af9670b889db582e57ef7c1","other":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","up":true},{"one":"b79fd26ce97e98e7432029dea6bf11e48ea9e8f2385dec5c60be0cbd51bbef54","other":"9294e5b39a1cd4230400e033da1ac1f79a4025adab41ce8c0e0e47dda2418cb5","up":true},{"one":"d3d254a3024a7aac01e97a5f0aff13756741867e599bc94c068f0d1d19ca8176","other":"f4e0d91ca58ed616918bf268565c2edb4176293e851b8d29cbbda55bdc608534","up":true},{"one":"4f90f53754ee50357491dbab7c3e6748b40133e3339920b32f4c3457f4258477","other":"4fd6a4b99b1ba76775594673421c8eea175c114519c8554798c2c364175bbaae","up":true},{"one":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","other":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","up":true},{"one":"7d45f1e91bcd683d870422b2f591c76f6ab07ff4b079f35c280ce46f7fc364e1","other":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","up":true},{"one":"670d1c31407b4f34bb955031e9a3ab7980273155080b459a082687d53a3b42a6","other":"74062d9dd73e26066fd44e4f6c696f271c9dc6860bdc5ba3fcf447f455c8cafa","up":true},{"one":"5fa8a5a29a47694653a5e55fd9b76e9c4f717ecc9b7088a6ec7ee273b291d49e","other":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","up":true},{"one":"52619a523050aa8caa32448e59721a76dc3321dc92cfcb1b05f1cec5b79d424a","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"c7703c4697637f40a50a5d0b656c4e54010ef1277b64d251ccd0752512e246e9","other":"d8b0425e4cb5134ddb2de5c05e0ae9b861bd67409c1446ca228318f79f5dd1f6","up":true},{"one":"d3d254a3024a7aac01e97a5f0aff13756741867e599bc94c068f0d1d19ca8176","other":"4019ce1dc667d90a85021b5fbf49ad77c1de7fd3443a40a17bdfaf5175efa1e1","up":true},{"one":"7471be56a2f617783d5a1ae4390794ad24f453259b7c1f8f65be8f71c8bf8bf7","other":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","up":true},{"one":"66100e4f9d103d935164f39ea3f0332708f3825ebcf31ac6f8c9714c75a50860","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"5f056f9593be46d3eba4dc8d9e0b93262cd3f00e007c13c4c011d53e7c199628","other":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","up":true},{"one":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","other":"52619a523050aa8caa32448e59721a76dc3321dc92cfcb1b05f1cec5b79d424a","up":true},{"one":"4a81ee929074bd6d9932fb4d970f5207db8680bda0e951aa72a6bd64df45108d","other":"2fd800d224f124e47038bf140ebd486f1d0ab93169c998d2dfabfd1d3ced96ed","up":true},{"one":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","other":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","up":true},{"one":"3e85d2d3d6750f84500fcf523743e0c242943e69188cb83114adad1787e6c089","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","other":"7d941474f90981a03c95af4cf68be163d01a317e202ac0db82e25f46fc3db8aa","up":true},{"one":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","other":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","up":true},{"one":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","other":"2f9f266a3b660fe685de2932de82979bd221706db1e2b954a93edc6609c378d9","up":true},{"one":"4d447ea80bc126e57b9fc91203a310c5f4b242df5ad70d5087d16697a626e601","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"3dca1ae488cc497fe738c630465f987b419a5bb259fdfa17fe7130e238ca3022","other":"2fd800d224f124e47038bf140ebd486f1d0ab93169c998d2dfabfd1d3ced96ed","up":true},{"one":"1835f62fe3889f43ba7b4b577869ec764e96fa9a051f1301fb96795daa8b0755","other":"14c81fb2fb8fa5a6b47288356ab486012046d09959b9ef4814a09dc3facb4853","up":true},{"one":"043f24ce87fbd01f6f663f323ac6c156810a67b12efbec2f219ae9a882a4bd16","other":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","up":true},{"one":"259d3874697a0c3192f973c620880b2a86567289f5222d07eb91a51867352559","other":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","up":true},{"one":"99aaaa7c98113955ecc0163943317f6ec08ae755e96c25c9f79ca1f529c60587","other":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","up":true},{"one":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","other":"3345e03880c377fc1fe7b6205011ce3e32525cefee5e03161105f1512893f9fb","up":true},{"one":"96b6ec2cda7c9e962bbc97e7916b92d69b112265c4456d30dade806e09fea62e","other":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","up":true},{"one":"99dbf361b67d6c370fd4f201b059e93da50dd88cde2e25d0bcf3c73afbf13535","other":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","up":true},{"one":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","other":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","up":true},{"one":"daa2c427e8ecc566e634908fb0d47077d8e79854bf884342c06c4bcb355e80e6","other":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","up":true},{"one":"6d219b0ea4a1620a7ba051cc478da6df9ea443e87dde79e963ec789a5bad7459","other":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","up":true},{"one":"2f9f266a3b660fe685de2932de82979bd221706db1e2b954a93edc6609c378d9","other":"2013ce63483af2990b35f9ecbe5a7653c34b24ded173753575b24451787ed8b9","up":true},{"one":"d8222a2c177f0cd17baae28327af5c13f2fb551dd01298d17855ce7f6f4f6189","other":"89ee628583b6e0345ceb312c2e2cbf5ac410eb00531be903a4b94b60b639993b","up":true},{"one":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","other":"99aaaa7c98113955ecc0163943317f6ec08ae755e96c25c9f79ca1f529c60587","up":true},{"one":"5fd02ea98977d976f5d47a4005300b9865b1dcdac77bf8a98a8920a89b57825c","other":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","up":true},{"one":"ceee9ac63786ad68d884f5d018248ae1b7b60f0ad434776d29bd4dfcdc0f04c1","other":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","up":true},{"one":"f9243ad864859edd351592d24abdf5c9c4e37f4ffe2ae1201c45f26576ee5725","other":"baf32cface953bd22141970b691567ed375f06043fe31de354598fb4230fb0a3","up":true},{"one":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","other":"daa2c427e8ecc566e634908fb0d47077d8e79854bf884342c06c4bcb355e80e6","up":true},{"one":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","other":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","up":true},{"one":"0592048ae4e6a3000f36e8e1c0e1328c26c0d4bb6369d6cd867a08d847dacb9e","other":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","up":true},{"one":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","other":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","up":true},{"one":"487edf32027381256a864bc2e9bca175f7216cc1234731bd4dc90d2cbad6975d","other":"7d45f1e91bcd683d870422b2f591c76f6ab07ff4b079f35c280ce46f7fc364e1","up":true},{"one":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","other":"d8b0425e4cb5134ddb2de5c05e0ae9b861bd67409c1446ca228318f79f5dd1f6","up":true},{"one":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","other":"96b6ec2cda7c9e962bbc97e7916b92d69b112265c4456d30dade806e09fea62e","up":true},{"one":"7a4118d2ce16e8258cc2cdf31c5891945e3787c46c0836fc916601584609b7db","other":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","up":true},{"one":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","other":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","up":true},{"one":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","other":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","up":true},{"one":"3345e03880c377fc1fe7b6205011ce3e32525cefee5e03161105f1512893f9fb","other":"043f24ce87fbd01f6f663f323ac6c156810a67b12efbec2f219ae9a882a4bd16","up":true},{"one":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","other":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","up":true},{"one":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","other":"e787fd35b438dbae3d0283f7181fc4afbeb6f333201ad4cd1b46bf7094a6a85f","up":true},{"one":"c8fe626c60d6cd15912a22d2c5872f10a8f8aef5b6f6b2f1070edec6670bf8b1","other":"c7703c4697637f40a50a5d0b656c4e54010ef1277b64d251ccd0752512e246e9","up":true},{"one":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"0210146e650132dd0ceb8cea779cafc5485822eeeb9fcc17f292af5cef6e9632","other":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","up":true},{"one":"123fe1d442c7dc905a7206e712fbd9cea640295a6823b39b1945629ffa89e895","other":"99fba7334f47bfcf21331d6179bfb2eee2ecebd1ea23fd829c542844b10ed974","up":true},{"one":"1b1eca7b35086d8b217431c1d6a01fc07dc8bc816196a80015c9e163bed676ff","other":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","up":true},{"one":"34fce2b8208c6c8ed005943b3f2cb9f8923739ce262e2040f7ffc048093ef910","other":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","up":true},{"one":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","other":"1e4441309404b0dd8dd9e8b1fe78708b015a0b0278ce86bcdaabc7d2d691791a","up":true},{"one":"a749372cccb7001a480b9416c41305bd1d3bc417e15669ae587d4375f2bbcbf7","other":"b79fd26ce97e98e7432029dea6bf11e48ea9e8f2385dec5c60be0cbd51bbef54","up":true},{"one":"03f5f1be5c1ae35469333a5b08622372f65ef8f233746afde56d2647c5273630","other":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","up":true},{"one":"7dc63c8146ed092aa4c9539abd112dc3a703631e364a62fd74b192eee5778123","other":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","up":true},{"one":"b71052b072cb98fb79151fd0c6044828e535a0547cace931183c7e4e7f227698","other":"c98d389a6d8e6eb5a1eb5a6f235857d5d939d30e51c16d80c039c9e0eb2f7eca","up":true},{"one":"bfeca898b1755a001eb0bcf8f5ad5713a550d0597f99ef7967ea4e7add8d3e81","other":"c3f356325eb1e61f2cfd8d80d085fb144c6887e1d710c9d133109b84b823425d","up":true},{"one":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","other":"42c019ddad2fde0f0c7f5041eb584996976844f8b2e08e20458437578d0257ef","up":true},{"one":"95e0529227286d442cdeeed428614f99c19f9223426e6f6cefbef2d9d47ce96f","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"353875d4ae2b9c5cc6cb14059bdfb9540647100c7c3334bc60db2ca2b4ddf3f5","other":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","up":true},{"one":"0ea201515867c6793cbe48350f209e69acd3829b9b16b6016d965d84fb6379c5","other":"0f81caba7d2f752fc670829f74565114bcf10c5b97d7f93252b467d0eee278b3","up":true},{"one":"36485615576c68702382f614054494cc4a40b90fd66db86b9d86a6a131dcc6ac","other":"2f2284537629514837d5c2296d2871d8c4c147719f0ddc28ef7c629efd44dd61","up":true},{"one":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","other":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","up":true},{"one":"14c81fb2fb8fa5a6b47288356ab486012046d09959b9ef4814a09dc3facb4853","other":"194ad126c41c487d95c83b19ed2bb053e4d2ec0605952d3a396dd4304e8e363c","up":true},{"one":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","other":"c4844b8046d1c47f699ee1c232d945829105d45af26fba722ce4d0a937c99631","up":true},{"one":"fed15b57e749c5f8f5fdb382eb95abe2ce0dee184c817e557122207f3729b05c","other":"8163eebe696123817a6d998d7781612931df6fa2fd0293c0d0dd395d031c53f6","up":true},{"one":"be0ab3ddc656568900d8e1f42394cc942464596b7565db40f6c5a0c24ee942f2","other":"86f71d23eb4efeeed4679be7a3d0f58f98b83d23b3e0a8b73f60f1535b51f25c","up":true},{"one":"b45d1aee940e5d2c71db22e2afbe908d5967e437fbb5bd051edea86ea4ac9d30","other":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","up":true},{"one":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","other":"72acf53ab7dbda90ea70e78d001fe21e7ab88e4054ff0453212e33a26fbbf7f1","up":true},{"one":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","other":"72acf53ab7dbda90ea70e78d001fe21e7ab88e4054ff0453212e33a26fbbf7f1","up":true},{"one":"237459ba37141f18b3cd40f5ec9cdaf6789fb87aa4511d489fdd98a960ef2883","other":"2013ce63483af2990b35f9ecbe5a7653c34b24ded173753575b24451787ed8b9","up":true},{"one":"89ee628583b6e0345ceb312c2e2cbf5ac410eb00531be903a4b94b60b639993b","other":"86128689b03706f9a86baf4753154afbb0c535f84089039ef5610d82fb969a6e","up":true},{"one":"15f6bb0640c3ac8ae8ecf10d0d46cc739bb6f86b0c0cf706103aad9f3785219b","other":"194ad126c41c487d95c83b19ed2bb053e4d2ec0605952d3a396dd4304e8e363c","up":true},{"one":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","other":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","up":true},{"one":"88da9dccda89c111dde40318fd1e5239e77b670f5a1b88a3e9320b7350885e31","other":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","up":true},{"one":"dc3ec6529cec19709403e473d4546ba07f7aaaaec5be7cb0618246a4c0458944","other":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","up":true},{"one":"36485615576c68702382f614054494cc4a40b90fd66db86b9d86a6a131dcc6ac","other":"265d7fc553fb8dc016d7c6ccc31fc372912a11e9e8da0e5822e371a91d82e857","up":true},{"one":"7471be56a2f617783d5a1ae4390794ad24f453259b7c1f8f65be8f71c8bf8bf7","other":"d6f37555e9f35a3ef3b8b4d5c18cf0702ed99c1c8b5b0a8554deddcbe542b88c","up":true},{"one":"06aa2f43956f978abe7b8f8f78a5a8cd59decb66ec54b7c81ae50c3752798c5e","other":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","up":true},{"one":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","other":"0f5e34b25f6851d0f3b572c64420de0aec01754bea0854bd7e9910b4b1cc66c3","up":true},{"one":"baf32cface953bd22141970b691567ed375f06043fe31de354598fb4230fb0a3","other":"bf5ac6f9b651895c8d75ab3ae7994f91981dbbcdce25a7cd718fc996da96946d","up":true},{"one":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","other":"c15d13aff7f5694fb8fc2a2154846ac703064133feb632e28830777bbfc7c016","up":true},{"one":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","other":"8163eebe696123817a6d998d7781612931df6fa2fd0293c0d0dd395d031c53f6","up":true},{"one":"13d8107c669cc27f6cbc64cef6b2bd11f72db297f2857c2de7323b92632b8231","other":"0f5e34b25f6851d0f3b572c64420de0aec01754bea0854bd7e9910b4b1cc66c3","up":true},{"one":"3a4a300de1009d694bebce2362ca429086ade2f551989d55ba459a68efea15af","other":"21682cd736edfb8b451dfd9c55eb9ea3b7cf2e35cad32537f1f4800644fbf9a7","up":true},{"one":"14418557af44e33fc8d46369426c3e2fc91622cb08779b3fefce706969ec2163","other":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","up":true},{"one":"2f2284537629514837d5c2296d2871d8c4c147719f0ddc28ef7c629efd44dd61","other":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","up":true},{"one":"9eec5d5357732a7acd26cc8aa0bc65f6c6069aedf4f3ab4a6790c7fff6bc2695","other":"99dbf361b67d6c370fd4f201b059e93da50dd88cde2e25d0bcf3c73afbf13535","up":true},{"one":"fb93417c597050f8deeb3098475f6cf0d213926332b59c25a538c1b4e6af89c0","other":"c0d1ac430a1466a1a28cb1aa3d29573ccedb13641ac19e6e615f2a96e8f0950b","up":true},{"one":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","other":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","up":true},{"one":"d6f37555e9f35a3ef3b8b4d5c18cf0702ed99c1c8b5b0a8554deddcbe542b88c","other":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","up":true},{"one":"2e9f7d37106b59d6f411dfa0392325d1fa279fe45c18a101e32ece6716e88946","other":"9294e5b39a1cd4230400e033da1ac1f79a4025adab41ce8c0e0e47dda2418cb5","up":true},{"one":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","other":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","up":true},{"one":"d6d2d35b3819e03b39d7146c4dd1b53fb7b70681c8860464650d5eb2d09dda2c","other":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","up":true},{"one":"5110c7e54a18df6963919866e7f6c0c3a483045d5ac20fc0dfa5e1926681be7f","other":"5fab772fff883d04cf7e4713266fb1e98fa1746e12faeb04e87bcb37b8b61187","up":true},{"one":"985c14910d922bbd1556c30620e3ab72170da6553e658dd3806d773a734a1c93","other":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","up":true},{"one":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","other":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","up":true},{"one":"92325e52babbf2b8ae3c37beec7e440d11efed2d103befa2c4a969315a2eab56","other":"baf32cface953bd22141970b691567ed375f06043fe31de354598fb4230fb0a3","up":true},{"one":"a749372cccb7001a480b9416c41305bd1d3bc417e15669ae587d4375f2bbcbf7","other":"9294e5b39a1cd4230400e033da1ac1f79a4025adab41ce8c0e0e47dda2418cb5","up":true},{"one":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","other":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","up":true},{"one":"b4c7555a38d1d4e3bd1d01bcc47a84efd6128e8cc0fd803362da8b341f984a4d","other":"74062d9dd73e26066fd44e4f6c696f271c9dc6860bdc5ba3fcf447f455c8cafa","up":true},{"one":"77ec3a73ce1d597d307e87ea8d026baae2a7df4d288a657f240eaaaae7fdb456","other":"458a31b8fdbc42f5e80f01118c9dc041740212c09ddfb4b137c9e7e93ae9ec51","up":true},{"one":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","other":"730749922ce3dcd8e249b988dbfb950c15473a2f6fd5cfe2a8126995fb1dc186","up":true},{"one":"d3d254a3024a7aac01e97a5f0aff13756741867e599bc94c068f0d1d19ca8176","other":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","up":true},{"one":"7294223c0ab27851df8c25418acf09a28efa95fc238252af8a68390db07b77e4","other":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","up":true},{"one":"46c5a266dd46041a62f21946290aad736d2298277bbbb16b1efb4490d9d31fa8","other":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","up":true},{"one":"e67d72c8c72bbc7bec1d4e936d65adbeab75d1b6cafdf2c39e38bfc723cceebf","other":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","up":true},{"one":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","other":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","up":true},{"one":"74062d9dd73e26066fd44e4f6c696f271c9dc6860bdc5ba3fcf447f455c8cafa","other":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","up":true},{"one":"9294e5b39a1cd4230400e033da1ac1f79a4025adab41ce8c0e0e47dda2418cb5","other":"c0d1ac430a1466a1a28cb1aa3d29573ccedb13641ac19e6e615f2a96e8f0950b","up":true},{"one":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","other":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","up":true},{"one":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","other":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","up":true},{"one":"7854fb9f0d2f016653994b9e23033f27e21a2ef186a352c8d068b75ea20beec2","other":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","up":true},{"one":"2013ce63483af2990b35f9ecbe5a7653c34b24ded173753575b24451787ed8b9","other":"259d3874697a0c3192f973c620880b2a86567289f5222d07eb91a51867352559","up":true},{"one":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","other":"004e812995bc04f22993466df9889ca1b650bba242cf80bb070ce9564e7bedd8","up":true},{"one":"d6f37555e9f35a3ef3b8b4d5c18cf0702ed99c1c8b5b0a8554deddcbe542b88c","other":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","up":true},{"one":"f048e37427d68c4134a115cc7df6a8924ea95f24e7f2435372ae331e24b5b7fd","other":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","up":true},{"one":"06aa2f43956f978abe7b8f8f78a5a8cd59decb66ec54b7c81ae50c3752798c5e","other":"2e9f7d37106b59d6f411dfa0392325d1fa279fe45c18a101e32ece6716e88946","up":true},{"one":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","other":"99aaaa7c98113955ecc0163943317f6ec08ae755e96c25c9f79ca1f529c60587","up":true},{"one":"4a670fe61711307f7781b3b5a8014fc0e63866efe356a25e61b2e768a7389980","other":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","up":true},{"one":"265d7fc553fb8dc016d7c6ccc31fc372912a11e9e8da0e5822e371a91d82e857","other":"13d8107c669cc27f6cbc64cef6b2bd11f72db297f2857c2de7323b92632b8231","up":true},{"one":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","other":"2013ce63483af2990b35f9ecbe5a7653c34b24ded173753575b24451787ed8b9","up":true},{"one":"2fd800d224f124e47038bf140ebd486f1d0ab93169c998d2dfabfd1d3ced96ed","other":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","up":true},{"one":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","other":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","up":true},{"one":"9a82d22b29c88eaca2f20a62e9e63675904065e72c70ce2fbbefc0c9d2e53e90","other":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","up":true},{"one":"e3c95a82f373da4522533252549c1d68b6f7621a887feb66199cfd4b7a35b6fc","other":"06aa2f43956f978abe7b8f8f78a5a8cd59decb66ec54b7c81ae50c3752798c5e","up":true},{"one":"1e4441309404b0dd8dd9e8b1fe78708b015a0b0278ce86bcdaabc7d2d691791a","other":"0210146e650132dd0ceb8cea779cafc5485822eeeb9fcc17f292af5cef6e9632","up":true},{"one":"275c93677aefd92ab38f9e5b5b4d29f90e809ccaf77b088102071aba26421f3e","other":"211a516fb95d093eb43f4716edeb1e6f76ed79d2aeee3393740e244970187840","up":true},{"one":"e787fd35b438dbae3d0283f7181fc4afbeb6f333201ad4cd1b46bf7094a6a85f","other":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","up":true},{"one":"d8b0425e4cb5134ddb2de5c05e0ae9b861bd67409c1446ca228318f79f5dd1f6","other":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","up":true},{"one":"c98d389a6d8e6eb5a1eb5a6f235857d5d939d30e51c16d80c039c9e0eb2f7eca","other":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","up":true},{"one":"0f81caba7d2f752fc670829f74565114bcf10c5b97d7f93252b467d0eee278b3","other":"1d942c85568f640497c46a9bd5a9825bc6fb05571203a3f0f14a3032602d81fc","up":true},{"one":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","other":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","up":true},{"one":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","other":"74062d9dd73e26066fd44e4f6c696f271c9dc6860bdc5ba3fcf447f455c8cafa","up":true},{"one":"396b256b223f346a490844895b9f1046b4790d624af1c43ecae1de318c7214a8","other":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","up":true},{"one":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","other":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","up":true},{"one":"b79fd26ce97e98e7432029dea6bf11e48ea9e8f2385dec5c60be0cbd51bbef54","other":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","up":true},{"one":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","other":"004e812995bc04f22993466df9889ca1b650bba242cf80bb070ce9564e7bedd8","up":true},{"one":"42c019ddad2fde0f0c7f5041eb584996976844f8b2e08e20458437578d0257ef","other":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","up":true},{"one":"aa504681c991c69015c7ef0469902c44fc4e900356b181aa252a1280ffbd2e3b","other":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","up":true},{"one":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","other":"194ad126c41c487d95c83b19ed2bb053e4d2ec0605952d3a396dd4304e8e363c","up":true},{"one":"aa504681c991c69015c7ef0469902c44fc4e900356b181aa252a1280ffbd2e3b","other":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","up":true},{"one":"1b869ca5aec1ff0c9980b06b79084019752fd4557054e251c0091ee5d2c7d478","other":"194ad126c41c487d95c83b19ed2bb053e4d2ec0605952d3a396dd4304e8e363c","up":true},{"one":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","other":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","up":true},{"one":"b60deba7c8676305b67af41d2de866bddd72edc0667d427e51fc3f2e91412b45","other":"c3f356325eb1e61f2cfd8d80d085fb144c6887e1d710c9d133109b84b823425d","up":true},{"one":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","other":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","up":true},{"one":"a80b39cf280fa80b323370cfc37e8e3ff38de47d5de445c500f36c8cf2390b53","other":"004e812995bc04f22993466df9889ca1b650bba242cf80bb070ce9564e7bedd8","up":true},{"one":"9c01d016aaf11f9e7b048ba10cea3c351d9ab5c8c66e8605f78c06772708985c","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","other":"245495250d38095106022d3fc905eb7eb6f54adb2cb0bacdda0b305974eaa50a","up":true},{"one":"bfeca898b1755a001eb0bcf8f5ad5713a550d0597f99ef7967ea4e7add8d3e81","other":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","up":true},{"one":"ae715a40e9bf50b4eb954c6b87efbf1a6e7abc3da6d84055e4591aadf5bad5c8","other":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","up":true},{"one":"f9243ad864859edd351592d24abdf5c9c4e37f4ffe2ae1201c45f26576ee5725","other":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","up":true},{"one":"388d46d6ea3fd92aa959b04d597ff1096d6133f171181c8fb54e52935b62d33c","other":"3e85d2d3d6750f84500fcf523743e0c242943e69188cb83114adad1787e6c089","up":true},{"one":"c7230d8d56b65a66ab721a8268ed436af78210aae7d068cc2b8afe39b6e61c81","other":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","up":true},{"one":"c7230d8d56b65a66ab721a8268ed436af78210aae7d068cc2b8afe39b6e61c81","other":"d6d2d35b3819e03b39d7146c4dd1b53fb7b70681c8860464650d5eb2d09dda2c","up":true},{"one":"b71052b072cb98fb79151fd0c6044828e535a0547cace931183c7e4e7f227698","other":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","up":true},{"one":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","other":"c7230d8d56b65a66ab721a8268ed436af78210aae7d068cc2b8afe39b6e61c81","up":true},{"one":"275c93677aefd92ab38f9e5b5b4d29f90e809ccaf77b088102071aba26421f3e","other":"2e9f7d37106b59d6f411dfa0392325d1fa279fe45c18a101e32ece6716e88946","up":true},{"one":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","other":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","up":true},{"one":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","other":"275c93677aefd92ab38f9e5b5b4d29f90e809ccaf77b088102071aba26421f3e","up":true},{"one":"d6d2d35b3819e03b39d7146c4dd1b53fb7b70681c8860464650d5eb2d09dda2c","other":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","up":true},{"one":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","other":"99dbf361b67d6c370fd4f201b059e93da50dd88cde2e25d0bcf3c73afbf13535","up":true},{"one":"86f71d23eb4efeeed4679be7a3d0f58f98b83d23b3e0a8b73f60f1535b51f25c","other":"99fba7334f47bfcf21331d6179bfb2eee2ecebd1ea23fd829c542844b10ed974","up":true},{"one":"9eec5d5357732a7acd26cc8aa0bc65f6c6069aedf4f3ab4a6790c7fff6bc2695","other":"99aaaa7c98113955ecc0163943317f6ec08ae755e96c25c9f79ca1f529c60587","up":true},{"one":"a80b39cf280fa80b323370cfc37e8e3ff38de47d5de445c500f36c8cf2390b53","other":"123fe1d442c7dc905a7206e712fbd9cea640295a6823b39b1945629ffa89e895","up":true},{"one":"1b1eca7b35086d8b217431c1d6a01fc07dc8bc816196a80015c9e163bed676ff","other":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","up":true},{"one":"bc08af284ab661dcc1cb23859271590e50a09ff8af8073769c812901ea0b9080","other":"b71052b072cb98fb79151fd0c6044828e535a0547cace931183c7e4e7f227698","up":true},{"one":"bc08af284ab661dcc1cb23859271590e50a09ff8af8073769c812901ea0b9080","other":"9a82d22b29c88eaca2f20a62e9e63675904065e72c70ce2fbbefc0c9d2e53e90","up":true},{"one":"123fe1d442c7dc905a7206e712fbd9cea640295a6823b39b1945629ffa89e895","other":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","up":true},{"one":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","other":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","up":true},{"one":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","other":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","up":true},{"one":"396b256b223f346a490844895b9f1046b4790d624af1c43ecae1de318c7214a8","other":"0210146e650132dd0ceb8cea779cafc5485822eeeb9fcc17f292af5cef6e9632","up":true},{"one":"9fee945c89d6bb59e77a24183c2855f4eece0b5d3266f750ea95483eae1e1871","other":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","up":true},{"one":"c8fe626c60d6cd15912a22d2c5872f10a8f8aef5b6f6b2f1070edec6670bf8b1","other":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","up":true},{"one":"be0ab3ddc656568900d8e1f42394cc942464596b7565db40f6c5a0c24ee942f2","other":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","up":true},{"one":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","other":"c7703c4697637f40a50a5d0b656c4e54010ef1277b64d251ccd0752512e246e9","up":true},{"one":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","other":"a80b39cf280fa80b323370cfc37e8e3ff38de47d5de445c500f36c8cf2390b53","up":true},{"one":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","other":"c8fe626c60d6cd15912a22d2c5872f10a8f8aef5b6f6b2f1070edec6670bf8b1","up":true},{"one":"7a4118d2ce16e8258cc2cdf31c5891945e3787c46c0836fc916601584609b7db","other":"396b256b223f346a490844895b9f1046b4790d624af1c43ecae1de318c7214a8","up":true},{"one":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","other":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","up":true},{"one":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","other":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","up":true},{"one":"47f95708475c9d61ff2016e7707c4a3796f6c87313bd1d2bf91aa620113ba5f8","other":"86f71d23eb4efeeed4679be7a3d0f58f98b83d23b3e0a8b73f60f1535b51f25c","up":true},{"one":"0210146e650132dd0ceb8cea779cafc5485822eeeb9fcc17f292af5cef6e9632","other":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","up":true},{"one":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","other":"c98d389a6d8e6eb5a1eb5a6f235857d5d939d30e51c16d80c039c9e0eb2f7eca","up":true},{"one":"d8b0425e4cb5134ddb2de5c05e0ae9b861bd67409c1446ca228318f79f5dd1f6","other":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","up":true},{"one":"c98d389a6d8e6eb5a1eb5a6f235857d5d939d30e51c16d80c039c9e0eb2f7eca","other":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","up":true},{"one":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","other":"c4632e164170d35e91fce46f660265f73795f1d840effce5ab2aae18c73003d7","up":true},{"one":"245495250d38095106022d3fc905eb7eb6f54adb2cb0bacdda0b305974eaa50a","other":"14418557af44e33fc8d46369426c3e2fc91622cb08779b3fefce706969ec2163","up":true},{"one":"1e4441309404b0dd8dd9e8b1fe78708b015a0b0278ce86bcdaabc7d2d691791a","other":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","up":true},{"one":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","other":"c4632e164170d35e91fce46f660265f73795f1d840effce5ab2aae18c73003d7","up":true},{"one":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","other":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","up":true},{"one":"487edf32027381256a864bc2e9bca175f7216cc1234731bd4dc90d2cbad6975d","other":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","up":true},{"one":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","other":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","up":true},{"one":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","other":"99dbf361b67d6c370fd4f201b059e93da50dd88cde2e25d0bcf3c73afbf13535","up":true},{"one":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","other":"af358f626738d2e5d17060abbaf24cb0124a48c5edb8f3baeda30bea7b403dfa","up":true},{"one":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","other":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","up":true},{"one":"e4c3fa0700516da5650f794067eb1c90ba7eaab2b65019bf34552c26f8c1d9a6","other":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","up":true},{"one":"3a4a300de1009d694bebce2362ca429086ade2f551989d55ba459a68efea15af","other":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","up":true},{"one":"2fd800d224f124e47038bf140ebd486f1d0ab93169c998d2dfabfd1d3ced96ed","other":"1835f62fe3889f43ba7b4b577869ec764e96fa9a051f1301fb96795daa8b0755","up":true},{"one":"730749922ce3dcd8e249b988dbfb950c15473a2f6fd5cfe2a8126995fb1dc186","other":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","up":true},{"one":"388d46d6ea3fd92aa959b04d597ff1096d6133f171181c8fb54e52935b62d33c","other":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","up":true},{"one":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","other":"d68f504755b48f8d41570169dd3544ef1caea2ab1bb334f8d8c5fb27fe816f01","up":true},{"one":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"52881f1fe0b8e9044a3bc035edbbd6343b9c41d49c0f2c2ca799c2e764489b91","other":"daa2c427e8ecc566e634908fb0d47077d8e79854bf884342c06c4bcb355e80e6","up":true},{"one":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","other":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","up":true},{"one":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","other":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","up":true},{"one":"0f81caba7d2f752fc670829f74565114bcf10c5b97d7f93252b467d0eee278b3","other":"194ad126c41c487d95c83b19ed2bb053e4d2ec0605952d3a396dd4304e8e363c","up":true},{"one":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","other":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","up":true},{"one":"ed1348b4037004b61bd4d6f9be1648112f835943a3b997b91ac931301311e007","other":"c4844b8046d1c47f699ee1c232d945829105d45af26fba722ce4d0a937c99631","up":true},{"one":"14c81fb2fb8fa5a6b47288356ab486012046d09959b9ef4814a09dc3facb4853","other":"15f6bb0640c3ac8ae8ecf10d0d46cc739bb6f86b0c0cf706103aad9f3785219b","up":true},{"one":"c63e796efdc44712403199c11c3e5ff28e5d6a4d9ecdf3e95620fbd6afb1bc2d","other":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","up":true},{"one":"edca23c0fccfd1ec66f4d39bb129462776b9091c2337f6afbfe8737a0ebc0d7d","other":"c15d13aff7f5694fb8fc2a2154846ac703064133feb632e28830777bbfc7c016","up":true},{"one":"e67d72c8c72bbc7bec1d4e936d65adbeab75d1b6cafdf2c39e38bfc723cceebf","other":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","up":true},{"one":"4f90f53754ee50357491dbab7c3e6748b40133e3339920b32f4c3457f4258477","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"96b6ec2cda7c9e962bbc97e7916b92d69b112265c4456d30dade806e09fea62e","other":"f048e37427d68c4134a115cc7df6a8924ea95f24e7f2435372ae331e24b5b7fd","up":true},{"one":"ecd2b502d4e9bf77369c62d1142a9fb4ab1071e751cc6971e9e381e953b0fc12","other":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","up":true},{"one":"4fd6a4b99b1ba76775594673421c8eea175c114519c8554798c2c364175bbaae","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","other":"4cf655b03778bcb41c32664f13168dc32968354468c8aa699de52b43a2e0dc7d","up":true},{"one":"b310299bef4f4e84a1ecc960143e4b1bbe98cd4eb8352458c477569ca9994c20","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"99fba7334f47bfcf21331d6179bfb2eee2ecebd1ea23fd829c542844b10ed974","other":"a80b39cf280fa80b323370cfc37e8e3ff38de47d5de445c500f36c8cf2390b53","up":true},{"one":"265d7fc553fb8dc016d7c6ccc31fc372912a11e9e8da0e5822e371a91d82e857","other":"0f5e34b25f6851d0f3b572c64420de0aec01754bea0854bd7e9910b4b1cc66c3","up":true},{"one":"bf5ac6f9b651895c8d75ab3ae7994f91981dbbcdce25a7cd718fc996da96946d","other":"fed15b57e749c5f8f5fdb382eb95abe2ce0dee184c817e557122207f3729b05c","up":true},{"one":"4d447ea80bc126e57b9fc91203a310c5f4b242df5ad70d5087d16697a626e601","other":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","up":true},{"one":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","other":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","up":true},{"one":"e8392bdce92a7dcfa5e3196a6e1d4e5dc4814c72e104d3e2aa1f1ab99e2a47a6","other":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","up":true},{"one":"d68f504755b48f8d41570169dd3544ef1caea2ab1bb334f8d8c5fb27fe816f01","other":"baf32cface953bd22141970b691567ed375f06043fe31de354598fb4230fb0a3","up":true},{"one":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","other":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","up":true},{"one":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","other":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","up":true},{"one":"5fab772fff883d04cf7e4713266fb1e98fa1746e12faeb04e87bcb37b8b61187","other":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","up":true},{"one":"e3c95a82f373da4522533252549c1d68b6f7621a887feb66199cfd4b7a35b6fc","other":"c3f356325eb1e61f2cfd8d80d085fb144c6887e1d710c9d133109b84b823425d","up":true},{"one":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","other":"3345e03880c377fc1fe7b6205011ce3e32525cefee5e03161105f1512893f9fb","up":true},{"one":"92325e52babbf2b8ae3c37beec7e440d11efed2d103befa2c4a969315a2eab56","other":"8163eebe696123817a6d998d7781612931df6fa2fd0293c0d0dd395d031c53f6","up":true},{"one":"d8222a2c177f0cd17baae28327af5c13f2fb551dd01298d17855ce7f6f4f6189","other":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","up":true},{"one":"7294223c0ab27851df8c25418acf09a28efa95fc238252af8a68390db07b77e4","other":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","up":true},{"one":"9390ef61753fcf9dae5e585a85650287fe1f792538721a2cf767adc44c5e7ce0","other":"b4c7555a38d1d4e3bd1d01bcc47a84efd6128e8cc0fd803362da8b341f984a4d","up":true},{"one":"353875d4ae2b9c5cc6cb14059bdfb9540647100c7c3334bc60db2ca2b4ddf3f5","other":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","up":true},{"one":"7ffed78e11c71eccfdddf6a8e5e367831b26a6b11e4d3b60c5b9fc50f4bcdf33","other":"7294223c0ab27851df8c25418acf09a28efa95fc238252af8a68390db07b77e4","up":true},{"one":"e4c3fa0700516da5650f794067eb1c90ba7eaab2b65019bf34552c26f8c1d9a6","other":"43afd835930eac91c0c8947001dd60d8da9112c19cf3f1356589b58b48962166","up":true},{"one":"43afd835930eac91c0c8947001dd60d8da9112c19cf3f1356589b58b48962166","other":"74062d9dd73e26066fd44e4f6c696f271c9dc6860bdc5ba3fcf447f455c8cafa","up":true},{"one":"f1fcfc064446a432ac7a21efc386fa3d053d0ca4540fd4ddbbb253ec11f9ce94","other":"c7230d8d56b65a66ab721a8268ed436af78210aae7d068cc2b8afe39b6e61c81","up":true},{"one":"237459ba37141f18b3cd40f5ec9cdaf6789fb87aa4511d489fdd98a960ef2883","other":"259d3874697a0c3192f973c620880b2a86567289f5222d07eb91a51867352559","up":true},{"one":"3af3bb6c4343a19e3c0f9a89697cd70a3c79c64b1e40afa59b188c5553eed567","other":"3dca1ae488cc497fe738c630465f987b419a5bb259fdfa17fe7130e238ca3022","up":true},{"one":"74062d9dd73e26066fd44e4f6c696f271c9dc6860bdc5ba3fcf447f455c8cafa","other":"7294223c0ab27851df8c25418acf09a28efa95fc238252af8a68390db07b77e4","up":true},{"one":"15f6bb0640c3ac8ae8ecf10d0d46cc739bb6f86b0c0cf706103aad9f3785219b","other":"1d942c85568f640497c46a9bd5a9825bc6fb05571203a3f0f14a3032602d81fc","up":true},{"one":"7d45f1e91bcd683d870422b2f591c76f6ab07ff4b079f35c280ce46f7fc364e1","other":"7ffed78e11c71eccfdddf6a8e5e367831b26a6b11e4d3b60c5b9fc50f4bcdf33","up":true},{"one":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","other":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","up":true},{"one":"06aa2f43956f978abe7b8f8f78a5a8cd59decb66ec54b7c81ae50c3752798c5e","other":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","up":true},{"one":"9294e5b39a1cd4230400e033da1ac1f79a4025adab41ce8c0e0e47dda2418cb5","other":"baf32cface953bd22141970b691567ed375f06043fe31de354598fb4230fb0a3","up":true},{"one":"18f9292fbe6e94ec3df4d0a896c71ed2b1771437e42d8475a7aae63c7992834d","other":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","up":true},{"one":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","other":"b45d1aee940e5d2c71db22e2afbe908d5967e437fbb5bd051edea86ea4ac9d30","up":true},{"one":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","other":"4a81ee929074bd6d9932fb4d970f5207db8680bda0e951aa72a6bd64df45108d","up":true},{"one":"f5dd00d94488f3387d64ccc2115cd91e5073c06de36d195a5c1a95db23cd5c68","other":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","up":true},{"one":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"9fee945c89d6bb59e77a24183c2855f4eece0b5d3266f750ea95483eae1e1871","other":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","up":true},{"one":"1d942c85568f640497c46a9bd5a9825bc6fb05571203a3f0f14a3032602d81fc","other":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","up":true},{"one":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","other":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","up":true},{"one":"31edba65e539a9a3c7107a131c4fb4b5a8e2c2d4e2db7874a0116c173961e27c","other":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","up":true},{"one":"4a81ee929074bd6d9932fb4d970f5207db8680bda0e951aa72a6bd64df45108d","other":"0ea201515867c6793cbe48350f209e69acd3829b9b16b6016d965d84fb6379c5","up":true},{"one":"2013ce63483af2990b35f9ecbe5a7653c34b24ded173753575b24451787ed8b9","other":"2fd800d224f124e47038bf140ebd486f1d0ab93169c998d2dfabfd1d3ced96ed","up":true},{"one":"adfc03118406bc5af4e3f3c6c89c3448c3fe4f9df20b15bcca9bbacf58ee5457","other":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","up":true},{"one":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","other":"5fd02ea98977d976f5d47a4005300b9865b1dcdac77bf8a98a8920a89b57825c","up":true},{"one":"daa2c427e8ecc566e634908fb0d47077d8e79854bf884342c06c4bcb355e80e6","other":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","up":true},{"one":"def4f0b988942a8699f6b500cc2531c7dea065e3dc5bdcbeb26bca8f00e1d3a6","other":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","up":true},{"one":"5f056f9593be46d3eba4dc8d9e0b93262cd3f00e007c13c4c011d53e7c199628","other":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","up":true},{"one":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","other":"ae715a40e9bf50b4eb954c6b87efbf1a6e7abc3da6d84055e4591aadf5bad5c8","up":true},{"one":"7854fb9f0d2f016653994b9e23033f27e21a2ef186a352c8d068b75ea20beec2","other":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","up":true},{"one":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","other":"2fd800d224f124e47038bf140ebd486f1d0ab93169c998d2dfabfd1d3ced96ed","up":true},{"one":"a6720426faa1ea7e2d55c1c5ad05ba2e8c534d120ebb5e1375cf769d73191708","other":"adfc03118406bc5af4e3f3c6c89c3448c3fe4f9df20b15bcca9bbacf58ee5457","up":true},{"one":"c99c6a2a2fe7f4613c8d918404c956eae0a00fce544c9eeba5da640e5e958874","other":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","up":true},{"one":"604c6bfab4f5d16d5b786318cf8f9e0197fc18e5d8a8e6f4fd345e653d2abe17","other":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","up":true},{"one":"3345e03880c377fc1fe7b6205011ce3e32525cefee5e03161105f1512893f9fb","other":"4d447ea80bc126e57b9fc91203a310c5f4b242df5ad70d5087d16697a626e601","up":true},{"one":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","other":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","up":true},{"one":"730749922ce3dcd8e249b988dbfb950c15473a2f6fd5cfe2a8126995fb1dc186","other":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","up":true},{"one":"88da9dccda89c111dde40318fd1e5239e77b670f5a1b88a3e9320b7350885e31","other":"bf5ac6f9b651895c8d75ab3ae7994f91981dbbcdce25a7cd718fc996da96946d","up":true},{"one":"4cf655b03778bcb41c32664f13168dc32968354468c8aa699de52b43a2e0dc7d","other":"4a670fe61711307f7781b3b5a8014fc0e63866efe356a25e61b2e768a7389980","up":true},{"one":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","other":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","up":true},{"one":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","other":"f78a54ba4b3fa75cdb542bdcd6da0d49986a5f7e7939959a51b3b48a87605654","up":true},{"one":"abfabc2964ce2f7b8b3702e897a8cc83fb0c009d5af9670b889db582e57ef7c1","other":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","up":true},{"one":"8163eebe696123817a6d998d7781612931df6fa2fd0293c0d0dd395d031c53f6","other":"bf5ac6f9b651895c8d75ab3ae7994f91981dbbcdce25a7cd718fc996da96946d","up":true},{"one":"604c6bfab4f5d16d5b786318cf8f9e0197fc18e5d8a8e6f4fd345e653d2abe17","other":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","up":true},{"one":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","other":"2f2284537629514837d5c2296d2871d8c4c147719f0ddc28ef7c629efd44dd61","up":true},{"one":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","other":"1b869ca5aec1ff0c9980b06b79084019752fd4557054e251c0091ee5d2c7d478","up":true},{"one":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","other":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","up":true},{"one":"dc3ec6529cec19709403e473d4546ba07f7aaaaec5be7cb0618246a4c0458944","other":"c4844b8046d1c47f699ee1c232d945829105d45af26fba722ce4d0a937c99631","up":true},{"one":"237459ba37141f18b3cd40f5ec9cdaf6789fb87aa4511d489fdd98a960ef2883","other":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","up":true},{"one":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","other":"b45d1aee940e5d2c71db22e2afbe908d5967e437fbb5bd051edea86ea4ac9d30","up":true},{"one":"0592048ae4e6a3000f36e8e1c0e1328c26c0d4bb6369d6cd867a08d847dacb9e","other":"b45d1aee940e5d2c71db22e2afbe908d5967e437fbb5bd051edea86ea4ac9d30","up":true},{"one":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","other":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","up":true},{"one":"c4844b8046d1c47f699ee1c232d945829105d45af26fba722ce4d0a937c99631","other":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","up":true},{"one":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","other":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","up":true},{"one":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","other":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","up":true},{"one":"ed6570e63fc0e2e2d6290645c4339b70a0f1c14064d286b4e0ba143607bd70c8","other":"c3f356325eb1e61f2cfd8d80d085fb144c6887e1d710c9d133109b84b823425d","up":true},{"one":"2279d612454ff034032a5acf5a039477a11479b9b9a93edc26b61312e8d9b156","other":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","up":true},{"one":"1a02108d9149739be6ba85ddd1e8a3961adeec38f77fcef95ab57f8f2f39f4bb","other":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","up":true},{"one":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","other":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","up":true},{"one":"34fce2b8208c6c8ed005943b3f2cb9f8923739ce262e2040f7ffc048093ef910","other":"3e85d2d3d6750f84500fcf523743e0c242943e69188cb83114adad1787e6c089","up":true},{"one":"ae715a40e9bf50b4eb954c6b87efbf1a6e7abc3da6d84055e4591aadf5bad5c8","other":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","up":true},{"one":"194ad126c41c487d95c83b19ed2bb053e4d2ec0605952d3a396dd4304e8e363c","other":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","up":true},{"one":"b45d1aee940e5d2c71db22e2afbe908d5967e437fbb5bd051edea86ea4ac9d30","other":"ed1348b4037004b61bd4d6f9be1648112f835943a3b997b91ac931301311e007","up":true},{"one":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","other":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","up":true},{"one":"1835f62fe3889f43ba7b4b577869ec764e96fa9a051f1301fb96795daa8b0755","other":"0592048ae4e6a3000f36e8e1c0e1328c26c0d4bb6369d6cd867a08d847dacb9e","up":true},{"one":"f78a54ba4b3fa75cdb542bdcd6da0d49986a5f7e7939959a51b3b48a87605654","other":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","up":true},{"one":"3af3bb6c4343a19e3c0f9a89697cd70a3c79c64b1e40afa59b188c5553eed567","other":"2013ce63483af2990b35f9ecbe5a7653c34b24ded173753575b24451787ed8b9","up":true},{"one":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","other":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","up":true},{"one":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","other":"47f95708475c9d61ff2016e7707c4a3796f6c87313bd1d2bf91aa620113ba5f8","up":true},{"one":"a485db6ec1929aaf695a84294f52905aa602584954e9ef71b4e18f7b847da31d","other":"52881f1fe0b8e9044a3bc035edbbd6343b9c41d49c0f2c2ca799c2e764489b91","up":true},{"one":"52881f1fe0b8e9044a3bc035edbbd6343b9c41d49c0f2c2ca799c2e764489b91","other":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","up":true},{"one":"e787fd35b438dbae3d0283f7181fc4afbeb6f333201ad4cd1b46bf7094a6a85f","other":"d8b0425e4cb5134ddb2de5c05e0ae9b861bd67409c1446ca228318f79f5dd1f6","up":true},{"one":"4f90f53754ee50357491dbab7c3e6748b40133e3339920b32f4c3457f4258477","other":"4d447ea80bc126e57b9fc91203a310c5f4b242df5ad70d5087d16697a626e601","up":true},{"one":"4a828523731caf47f9aa2dfa20f8a8895e9a6562b3a034574639a229a733b4a6","other":"15f6bb0640c3ac8ae8ecf10d0d46cc739bb6f86b0c0cf706103aad9f3785219b","up":true},{"one":"1835f62fe3889f43ba7b4b577869ec764e96fa9a051f1301fb96795daa8b0755","other":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","up":true},{"one":"bc08af284ab661dcc1cb23859271590e50a09ff8af8073769c812901ea0b9080","other":"c8fe626c60d6cd15912a22d2c5872f10a8f8aef5b6f6b2f1070edec6670bf8b1","up":true},{"one":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","other":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","up":true},{"one":"6544c4a403d9809697420ff9840ea9de0c9ba6491de28dac288db6257f2dc218","other":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","up":true},{"one":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","other":"4f90f53754ee50357491dbab7c3e6748b40133e3339920b32f4c3457f4258477","up":true},{"one":"1b1eca7b35086d8b217431c1d6a01fc07dc8bc816196a80015c9e163bed676ff","other":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","up":true},{"one":"3e85d2d3d6750f84500fcf523743e0c242943e69188cb83114adad1787e6c089","other":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","up":true},{"one":"7dc63c8146ed092aa4c9539abd112dc3a703631e364a62fd74b192eee5778123","other":"52619a523050aa8caa32448e59721a76dc3321dc92cfcb1b05f1cec5b79d424a","up":true},{"one":"c7230d8d56b65a66ab721a8268ed436af78210aae7d068cc2b8afe39b6e61c81","other":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","up":true},{"one":"4d447ea80bc126e57b9fc91203a310c5f4b242df5ad70d5087d16697a626e601","other":"4fd6a4b99b1ba76775594673421c8eea175c114519c8554798c2c364175bbaae","up":true},{"one":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","other":"d8222a2c177f0cd17baae28327af5c13f2fb551dd01298d17855ce7f6f4f6189","up":true},{"one":"ae715a40e9bf50b4eb954c6b87efbf1a6e7abc3da6d84055e4591aadf5bad5c8","other":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","up":true},{"one":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","other":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","up":true},{"one":"34fce2b8208c6c8ed005943b3f2cb9f8923739ce262e2040f7ffc048093ef910","other":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","up":true},{"one":"4f90f53754ee50357491dbab7c3e6748b40133e3339920b32f4c3457f4258477","other":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","up":true},{"one":"388d46d6ea3fd92aa959b04d597ff1096d6133f171181c8fb54e52935b62d33c","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","other":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","up":true},{"one":"c4632e164170d35e91fce46f660265f73795f1d840effce5ab2aae18c73003d7","other":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","up":true},{"one":"6544c4a403d9809697420ff9840ea9de0c9ba6491de28dac288db6257f2dc218","other":"c4632e164170d35e91fce46f660265f73795f1d840effce5ab2aae18c73003d7","up":true},{"one":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","other":"388d46d6ea3fd92aa959b04d597ff1096d6133f171181c8fb54e52935b62d33c","up":true},{"one":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","other":"def4f0b988942a8699f6b500cc2531c7dea065e3dc5bdcbeb26bca8f00e1d3a6","up":true},{"one":"79abd33d150fdbcbce6f55f3d101608b80e03ede7e4627bc06ba5c1b001538a4","other":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","up":true},{"one":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","other":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","up":true},{"one":"1b1eca7b35086d8b217431c1d6a01fc07dc8bc816196a80015c9e163bed676ff","other":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","up":true},{"one":"1e4441309404b0dd8dd9e8b1fe78708b015a0b0278ce86bcdaabc7d2d691791a","other":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","up":true},{"one":"c8fe626c60d6cd15912a22d2c5872f10a8f8aef5b6f6b2f1070edec6670bf8b1","other":"d8b0425e4cb5134ddb2de5c05e0ae9b861bd67409c1446ca228318f79f5dd1f6","up":true},{"one":"9c01d016aaf11f9e7b048ba10cea3c351d9ab5c8c66e8605f78c06772708985c","other":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","up":true},{"one":"b71052b072cb98fb79151fd0c6044828e535a0547cace931183c7e4e7f227698","other":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","up":true},{"one":"1b869ca5aec1ff0c9980b06b79084019752fd4557054e251c0091ee5d2c7d478","other":"1835f62fe3889f43ba7b4b577869ec764e96fa9a051f1301fb96795daa8b0755","up":true},{"one":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","other":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","up":true},{"one":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","other":"c7703c4697637f40a50a5d0b656c4e54010ef1277b64d251ccd0752512e246e9","up":true},{"one":"388d46d6ea3fd92aa959b04d597ff1096d6133f171181c8fb54e52935b62d33c","other":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","up":true},{"one":"9a82d22b29c88eaca2f20a62e9e63675904065e72c70ce2fbbefc0c9d2e53e90","other":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","up":true},{"one":"d8222a2c177f0cd17baae28327af5c13f2fb551dd01298d17855ce7f6f4f6189","other":"f78a54ba4b3fa75cdb542bdcd6da0d49986a5f7e7939959a51b3b48a87605654","up":true},{"one":"3a4a300de1009d694bebce2362ca429086ade2f551989d55ba459a68efea15af","other":"5fa8a5a29a47694653a5e55fd9b76e9c4f717ecc9b7088a6ec7ee273b291d49e","up":true},{"one":"d8b0425e4cb5134ddb2de5c05e0ae9b861bd67409c1446ca228318f79f5dd1f6","other":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","up":true},{"one":"c98d389a6d8e6eb5a1eb5a6f235857d5d939d30e51c16d80c039c9e0eb2f7eca","other":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","up":true},{"one":"e787fd35b438dbae3d0283f7181fc4afbeb6f333201ad4cd1b46bf7094a6a85f","other":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","up":true},{"one":"353875d4ae2b9c5cc6cb14059bdfb9540647100c7c3334bc60db2ca2b4ddf3f5","other":"043f24ce87fbd01f6f663f323ac6c156810a67b12efbec2f219ae9a882a4bd16","up":true},{"one":"bc08af284ab661dcc1cb23859271590e50a09ff8af8073769c812901ea0b9080","other":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","up":true},{"one":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","other":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","up":true},{"one":"9c01d016aaf11f9e7b048ba10cea3c351d9ab5c8c66e8605f78c06772708985c","other":"d8222a2c177f0cd17baae28327af5c13f2fb551dd01298d17855ce7f6f4f6189","up":true},{"one":"194ad126c41c487d95c83b19ed2bb053e4d2ec0605952d3a396dd4304e8e363c","other":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","up":true},{"one":"4a81ee929074bd6d9932fb4d970f5207db8680bda0e951aa72a6bd64df45108d","other":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","up":true},{"one":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","other":"ceee9ac63786ad68d884f5d018248ae1b7b60f0ad434776d29bd4dfcdc0f04c1","up":true},{"one":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","other":"c8fe626c60d6cd15912a22d2c5872f10a8f8aef5b6f6b2f1070edec6670bf8b1","up":true},{"one":"c8fe626c60d6cd15912a22d2c5872f10a8f8aef5b6f6b2f1070edec6670bf8b1","other":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","up":true},{"one":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","other":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","up":true},{"one":"1835f62fe3889f43ba7b4b577869ec764e96fa9a051f1301fb96795daa8b0755","other":"5fd02ea98977d976f5d47a4005300b9865b1dcdac77bf8a98a8920a89b57825c","up":true},{"one":"d8222a2c177f0cd17baae28327af5c13f2fb551dd01298d17855ce7f6f4f6189","other":"95e0529227286d442cdeeed428614f99c19f9223426e6f6cefbef2d9d47ce96f","up":true},{"one":"42c019ddad2fde0f0c7f5041eb584996976844f8b2e08e20458437578d0257ef","other":"0210146e650132dd0ceb8cea779cafc5485822eeeb9fcc17f292af5cef6e9632","up":true},{"one":"353875d4ae2b9c5cc6cb14059bdfb9540647100c7c3334bc60db2ca2b4ddf3f5","other":"3345e03880c377fc1fe7b6205011ce3e32525cefee5e03161105f1512893f9fb","up":true},{"one":"2013ce63483af2990b35f9ecbe5a7653c34b24ded173753575b24451787ed8b9","other":"f048e37427d68c4134a115cc7df6a8924ea95f24e7f2435372ae331e24b5b7fd","up":true},{"one":"3a4a300de1009d694bebce2362ca429086ade2f551989d55ba459a68efea15af","other":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","up":true},{"one":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","other":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","up":true},{"one":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","other":"52881f1fe0b8e9044a3bc035edbbd6343b9c41d49c0f2c2ca799c2e764489b91","up":true},{"one":"ceee9ac63786ad68d884f5d018248ae1b7b60f0ad434776d29bd4dfcdc0f04c1","other":"99aaaa7c98113955ecc0163943317f6ec08ae755e96c25c9f79ca1f529c60587","up":true},{"one":"dc3ec6529cec19709403e473d4546ba07f7aaaaec5be7cb0618246a4c0458944","other":"cb69f673a54a75de300d1d1ab989caa04579504cbcaf78f03bc192c9578ec5a0","up":true},{"one":"42c019ddad2fde0f0c7f5041eb584996976844f8b2e08e20458437578d0257ef","other":"7a4118d2ce16e8258cc2cdf31c5891945e3787c46c0836fc916601584609b7db","up":true},{"one":"290fca7596740129fd1a3075bbf56885d8c83381f981adb0fb283799375a3bd5","other":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","up":true},{"one":"0f81caba7d2f752fc670829f74565114bcf10c5b97d7f93252b467d0eee278b3","other":"259d3874697a0c3192f973c620880b2a86567289f5222d07eb91a51867352559","up":true},{"one":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","other":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","up":true},{"one":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","other":"0ea201515867c6793cbe48350f209e69acd3829b9b16b6016d965d84fb6379c5","up":true},{"one":"1d942c85568f640497c46a9bd5a9825bc6fb05571203a3f0f14a3032602d81fc","other":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","up":true},{"one":"ceee9ac63786ad68d884f5d018248ae1b7b60f0ad434776d29bd4dfcdc0f04c1","other":"c4632e164170d35e91fce46f660265f73795f1d840effce5ab2aae18c73003d7","up":true},{"one":"52881f1fe0b8e9044a3bc035edbbd6343b9c41d49c0f2c2ca799c2e764489b91","other":"4a81ee929074bd6d9932fb4d970f5207db8680bda0e951aa72a6bd64df45108d","up":true},{"one":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","other":"3af3bb6c4343a19e3c0f9a89697cd70a3c79c64b1e40afa59b188c5553eed567","up":true},{"one":"396b256b223f346a490844895b9f1046b4790d624af1c43ecae1de318c7214a8","other":"1b1eca7b35086d8b217431c1d6a01fc07dc8bc816196a80015c9e163bed676ff","up":true},{"one":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","other":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","up":true},{"one":"0592048ae4e6a3000f36e8e1c0e1328c26c0d4bb6369d6cd867a08d847dacb9e","other":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","up":true},{"one":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","other":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","up":true},{"one":"03f5f1be5c1ae35469333a5b08622372f65ef8f233746afde56d2647c5273630","other":"52881f1fe0b8e9044a3bc035edbbd6343b9c41d49c0f2c2ca799c2e764489b91","up":true},{"one":"fed15b57e749c5f8f5fdb382eb95abe2ce0dee184c817e557122207f3729b05c","other":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","up":true},{"one":"396b256b223f346a490844895b9f1046b4790d624af1c43ecae1de318c7214a8","other":"42c019ddad2fde0f0c7f5041eb584996976844f8b2e08e20458437578d0257ef","up":true},{"one":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","other":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","up":true},{"one":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","other":"0ea201515867c6793cbe48350f209e69acd3829b9b16b6016d965d84fb6379c5","up":true},{"one":"a485db6ec1929aaf695a84294f52905aa602584954e9ef71b4e18f7b847da31d","other":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","up":true},{"one":"4a828523731caf47f9aa2dfa20f8a8895e9a6562b3a034574639a229a733b4a6","other":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","up":true},{"one":"b45d1aee940e5d2c71db22e2afbe908d5967e437fbb5bd051edea86ea4ac9d30","other":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","up":true},{"one":"245495250d38095106022d3fc905eb7eb6f54adb2cb0bacdda0b305974eaa50a","other":"123fe1d442c7dc905a7206e712fbd9cea640295a6823b39b1945629ffa89e895","up":true},{"one":"a80b39cf280fa80b323370cfc37e8e3ff38de47d5de445c500f36c8cf2390b53","other":"be0ab3ddc656568900d8e1f42394cc942464596b7565db40f6c5a0c24ee942f2","up":true},{"one":"8163eebe696123817a6d998d7781612931df6fa2fd0293c0d0dd395d031c53f6","other":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","up":true},{"one":"92325e52babbf2b8ae3c37beec7e440d11efed2d103befa2c4a969315a2eab56","other":"9294e5b39a1cd4230400e033da1ac1f79a4025adab41ce8c0e0e47dda2418cb5","up":true},{"one":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","other":"5fab772fff883d04cf7e4713266fb1e98fa1746e12faeb04e87bcb37b8b61187","up":true},{"one":"e3c95a82f373da4522533252549c1d68b6f7621a887feb66199cfd4b7a35b6fc","other":"e4c3fa0700516da5650f794067eb1c90ba7eaab2b65019bf34552c26f8c1d9a6","up":true},{"one":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","other":"99dbf361b67d6c370fd4f201b059e93da50dd88cde2e25d0bcf3c73afbf13535","up":true},{"one":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","other":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","up":true},{"one":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","other":"d8b0425e4cb5134ddb2de5c05e0ae9b861bd67409c1446ca228318f79f5dd1f6","up":true},{"one":"b4c7555a38d1d4e3bd1d01bcc47a84efd6128e8cc0fd803362da8b341f984a4d","other":"bfeca898b1755a001eb0bcf8f5ad5713a550d0597f99ef7967ea4e7add8d3e81","up":true},{"one":"259d3874697a0c3192f973c620880b2a86567289f5222d07eb91a51867352559","other":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","up":true},{"one":"67831469d7f9b779df4318cda9d4310c53fdd66332af3740e654233ceb3fbcbc","other":"670d1c31407b4f34bb955031e9a3ab7980273155080b459a082687d53a3b42a6","up":true},{"one":"7a4118d2ce16e8258cc2cdf31c5891945e3787c46c0836fc916601584609b7db","other":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","up":true},{"one":"c7230d8d56b65a66ab721a8268ed436af78210aae7d068cc2b8afe39b6e61c81","other":"c3f356325eb1e61f2cfd8d80d085fb144c6887e1d710c9d133109b84b823425d","up":true},{"one":"ae715a40e9bf50b4eb954c6b87efbf1a6e7abc3da6d84055e4591aadf5bad5c8","other":"a6720426faa1ea7e2d55c1c5ad05ba2e8c534d120ebb5e1375cf769d73191708","up":true},{"one":"123fe1d442c7dc905a7206e712fbd9cea640295a6823b39b1945629ffa89e895","other":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","up":true},{"one":"571638637156bdaf92d9c715647a1cf64e91877c2d998658b7c6cd27522271bd","other":"7ffed78e11c71eccfdddf6a8e5e367831b26a6b11e4d3b60c5b9fc50f4bcdf33","up":true},{"one":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","other":"c7703c4697637f40a50a5d0b656c4e54010ef1277b64d251ccd0752512e246e9","up":true},{"one":"7dc63c8146ed092aa4c9539abd112dc3a703631e364a62fd74b192eee5778123","other":"66100e4f9d103d935164f39ea3f0332708f3825ebcf31ac6f8c9714c75a50860","up":true},{"one":"14418557af44e33fc8d46369426c3e2fc91622cb08779b3fefce706969ec2163","other":"123fe1d442c7dc905a7206e712fbd9cea640295a6823b39b1945629ffa89e895","up":true},{"one":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","other":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","up":true},{"one":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","other":"3e85d2d3d6750f84500fcf523743e0c242943e69188cb83114adad1787e6c089","up":true},{"one":"604c6bfab4f5d16d5b786318cf8f9e0197fc18e5d8a8e6f4fd345e653d2abe17","other":"c63e796efdc44712403199c11c3e5ff28e5d6a4d9ecdf3e95620fbd6afb1bc2d","up":true},{"one":"a80b39cf280fa80b323370cfc37e8e3ff38de47d5de445c500f36c8cf2390b53","other":"95e0529227286d442cdeeed428614f99c19f9223426e6f6cefbef2d9d47ce96f","up":true},{"one":"1a02108d9149739be6ba85ddd1e8a3961adeec38f77fcef95ab57f8f2f39f4bb","other":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","up":true},{"one":"2fd800d224f124e47038bf140ebd486f1d0ab93169c998d2dfabfd1d3ced96ed","other":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","up":true},{"one":"96b6ec2cda7c9e962bbc97e7916b92d69b112265c4456d30dade806e09fea62e","other":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","up":true},{"one":"5fd02ea98977d976f5d47a4005300b9865b1dcdac77bf8a98a8920a89b57825c","other":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","up":true},{"one":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","other":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","up":true},{"one":"2fd800d224f124e47038bf140ebd486f1d0ab93169c998d2dfabfd1d3ced96ed","other":"14c81fb2fb8fa5a6b47288356ab486012046d09959b9ef4814a09dc3facb4853","up":true},{"one":"9a82d22b29c88eaca2f20a62e9e63675904065e72c70ce2fbbefc0c9d2e53e90","other":"b4639751d67db0b32ad1846996fed0d5c59be76b77efaec5f66e64df35eefae8","up":true},{"one":"47f95708475c9d61ff2016e7707c4a3796f6c87313bd1d2bf91aa620113ba5f8","other":"123fe1d442c7dc905a7206e712fbd9cea640295a6823b39b1945629ffa89e895","up":true},{"one":"e787fd35b438dbae3d0283f7181fc4afbeb6f333201ad4cd1b46bf7094a6a85f","other":"f048e37427d68c4134a115cc7df6a8924ea95f24e7f2435372ae331e24b5b7fd","up":true},{"one":"2fd800d224f124e47038bf140ebd486f1d0ab93169c998d2dfabfd1d3ced96ed","other":"15f6bb0640c3ac8ae8ecf10d0d46cc739bb6f86b0c0cf706103aad9f3785219b","up":true},{"one":"bc08af284ab661dcc1cb23859271590e50a09ff8af8073769c812901ea0b9080","other":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","up":true},{"one":"a485db6ec1929aaf695a84294f52905aa602584954e9ef71b4e18f7b847da31d","other":"b310299bef4f4e84a1ecc960143e4b1bbe98cd4eb8352458c477569ca9994c20","up":true},{"one":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","other":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","up":true},{"one":"47f95708475c9d61ff2016e7707c4a3796f6c87313bd1d2bf91aa620113ba5f8","other":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","up":true},{"one":"1835f62fe3889f43ba7b4b577869ec764e96fa9a051f1301fb96795daa8b0755","other":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","up":true},{"one":"99fba7334f47bfcf21331d6179bfb2eee2ecebd1ea23fd829c542844b10ed974","other":"def4f0b988942a8699f6b500cc2531c7dea065e3dc5bdcbeb26bca8f00e1d3a6","up":true},{"one":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","other":"0ea201515867c6793cbe48350f209e69acd3829b9b16b6016d965d84fb6379c5","up":true},{"one":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","other":"194ad126c41c487d95c83b19ed2bb053e4d2ec0605952d3a396dd4304e8e363c","up":true},{"one":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","other":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","up":true},{"one":"5fd02ea98977d976f5d47a4005300b9865b1dcdac77bf8a98a8920a89b57825c","other":"14c81fb2fb8fa5a6b47288356ab486012046d09959b9ef4814a09dc3facb4853","up":true},{"one":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","other":"5fd02ea98977d976f5d47a4005300b9865b1dcdac77bf8a98a8920a89b57825c","up":true},{"one":"03f5f1be5c1ae35469333a5b08622372f65ef8f233746afde56d2647c5273630","other":"259d3874697a0c3192f973c620880b2a86567289f5222d07eb91a51867352559","up":true},{"one":"043f24ce87fbd01f6f663f323ac6c156810a67b12efbec2f219ae9a882a4bd16","other":"d8222a2c177f0cd17baae28327af5c13f2fb551dd01298d17855ce7f6f4f6189","up":true},{"one":"3dca1ae488cc497fe738c630465f987b419a5bb259fdfa17fe7130e238ca3022","other":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","up":true},{"one":"dc3ec6529cec19709403e473d4546ba07f7aaaaec5be7cb0618246a4c0458944","other":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","up":true},{"one":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","other":"aa504681c991c69015c7ef0469902c44fc4e900356b181aa252a1280ffbd2e3b","up":true},{"one":"290fca7596740129fd1a3075bbf56885d8c83381f981adb0fb283799375a3bd5","other":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","up":true},{"one":"36485615576c68702382f614054494cc4a40b90fd66db86b9d86a6a131dcc6ac","other":"0f5e34b25f6851d0f3b572c64420de0aec01754bea0854bd7e9910b4b1cc66c3","up":true},{"one":"2f2284537629514837d5c2296d2871d8c4c147719f0ddc28ef7c629efd44dd61","other":"265d7fc553fb8dc016d7c6ccc31fc372912a11e9e8da0e5822e371a91d82e857","up":true},{"one":"3dca1ae488cc497fe738c630465f987b419a5bb259fdfa17fe7130e238ca3022","other":"259d3874697a0c3192f973c620880b2a86567289f5222d07eb91a51867352559","up":true},{"one":"36485615576c68702382f614054494cc4a40b90fd66db86b9d86a6a131dcc6ac","other":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","up":true},{"one":"c0d1ac430a1466a1a28cb1aa3d29573ccedb13641ac19e6e615f2a96e8f0950b","other":"df5ea72c0c257bbea9b43901a1e406bf6055a307d083367e7f1d944e65c2b3cd","up":true},{"one":"f9243ad864859edd351592d24abdf5c9c4e37f4ffe2ae1201c45f26576ee5725","other":"c0d1ac430a1466a1a28cb1aa3d29573ccedb13641ac19e6e615f2a96e8f0950b","up":true},{"one":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","other":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","up":true},{"one":"e3c95a82f373da4522533252549c1d68b6f7621a887feb66199cfd4b7a35b6fc","other":"ed6570e63fc0e2e2d6290645c4339b70a0f1c14064d286b4e0ba143607bd70c8","up":true},{"one":"571638637156bdaf92d9c715647a1cf64e91877c2d998658b7c6cd27522271bd","other":"487edf32027381256a864bc2e9bca175f7216cc1234731bd4dc90d2cbad6975d","up":true},{"one":"4019ce1dc667d90a85021b5fbf49ad77c1de7fd3443a40a17bdfaf5175efa1e1","other":"46c5a266dd46041a62f21946290aad736d2298277bbbb16b1efb4490d9d31fa8","up":true},{"one":"f9243ad864859edd351592d24abdf5c9c4e37f4ffe2ae1201c45f26576ee5725","other":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","up":true},{"one":"c63e796efdc44712403199c11c3e5ff28e5d6a4d9ecdf3e95620fbd6afb1bc2d","other":"1b7250dae1f2b07504b381d28084b751a26ac791a1ed842ea5746826ab85ba22","up":true},{"one":"4019ce1dc667d90a85021b5fbf49ad77c1de7fd3443a40a17bdfaf5175efa1e1","other":"4cf655b03778bcb41c32664f13168dc32968354468c8aa699de52b43a2e0dc7d","up":true},{"one":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","other":"15f6bb0640c3ac8ae8ecf10d0d46cc739bb6f86b0c0cf706103aad9f3785219b","up":true},{"one":"15f6bb0640c3ac8ae8ecf10d0d46cc739bb6f86b0c0cf706103aad9f3785219b","other":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","up":true},{"one":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","other":"14c81fb2fb8fa5a6b47288356ab486012046d09959b9ef4814a09dc3facb4853","up":true},{"one":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","other":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","up":true},{"one":"a80b39cf280fa80b323370cfc37e8e3ff38de47d5de445c500f36c8cf2390b53","other":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","up":true},{"one":"86f71d23eb4efeeed4679be7a3d0f58f98b83d23b3e0a8b73f60f1535b51f25c","other":"86128689b03706f9a86baf4753154afbb0c535f84089039ef5610d82fb969a6e","up":true},{"one":"99fba7334f47bfcf21331d6179bfb2eee2ecebd1ea23fd829c542844b10ed974","other":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","up":true},{"one":"bc08af284ab661dcc1cb23859271590e50a09ff8af8073769c812901ea0b9080","other":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","up":true},{"one":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","other":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","up":true},{"one":"47f95708475c9d61ff2016e7707c4a3796f6c87313bd1d2bf91aa620113ba5f8","other":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","up":true},{"one":"7a4118d2ce16e8258cc2cdf31c5891945e3787c46c0836fc916601584609b7db","other":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","up":true},{"one":"5fd02ea98977d976f5d47a4005300b9865b1dcdac77bf8a98a8920a89b57825c","other":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","up":true},{"one":"c4632e164170d35e91fce46f660265f73795f1d840effce5ab2aae18c73003d7","other":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","up":true},{"one":"2af02e62ff3e40be25d22c2f745a505b895b3804a80663cb53db4edd5365c568","other":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","up":true},{"one":"1835f62fe3889f43ba7b4b577869ec764e96fa9a051f1301fb96795daa8b0755","other":"1d942c85568f640497c46a9bd5a9825bc6fb05571203a3f0f14a3032602d81fc","up":true},{"one":"1e4441309404b0dd8dd9e8b1fe78708b015a0b0278ce86bcdaabc7d2d691791a","other":"194ad126c41c487d95c83b19ed2bb053e4d2ec0605952d3a396dd4304e8e363c","up":true},{"one":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","other":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","up":true},{"one":"ed1348b4037004b61bd4d6f9be1648112f835943a3b997b91ac931301311e007","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"cb69f673a54a75de300d1d1ab989caa04579504cbcaf78f03bc192c9578ec5a0","other":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","up":true},{"one":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","other":"5fd02ea98977d976f5d47a4005300b9865b1dcdac77bf8a98a8920a89b57825c","up":true},{"one":"0210146e650132dd0ceb8cea779cafc5485822eeeb9fcc17f292af5cef6e9632","other":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","up":true},{"one":"9eec5d5357732a7acd26cc8aa0bc65f6c6069aedf4f3ab4a6790c7fff6bc2695","other":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","up":true},{"one":"d6f37555e9f35a3ef3b8b4d5c18cf0702ed99c1c8b5b0a8554deddcbe542b88c","other":"c4844b8046d1c47f699ee1c232d945829105d45af26fba722ce4d0a937c99631","up":true},{"one":"290fca7596740129fd1a3075bbf56885d8c83381f981adb0fb283799375a3bd5","other":"2f9f266a3b660fe685de2932de82979bd221706db1e2b954a93edc6609c378d9","up":true},{"one":"a485db6ec1929aaf695a84294f52905aa602584954e9ef71b4e18f7b847da31d","other":"96b6ec2cda7c9e962bbc97e7916b92d69b112265c4456d30dade806e09fea62e","up":true},{"one":"ed1348b4037004b61bd4d6f9be1648112f835943a3b997b91ac931301311e007","other":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","up":true},{"one":"4fd6a4b99b1ba76775594673421c8eea175c114519c8554798c2c364175bbaae","other":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","up":true},{"one":"d6f37555e9f35a3ef3b8b4d5c18cf0702ed99c1c8b5b0a8554deddcbe542b88c","other":"edca23c0fccfd1ec66f4d39bb129462776b9091c2337f6afbfe8737a0ebc0d7d","up":true},{"one":"9eec5d5357732a7acd26cc8aa0bc65f6c6069aedf4f3ab4a6790c7fff6bc2695","other":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","up":true},{"one":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","other":"290fca7596740129fd1a3075bbf56885d8c83381f981adb0fb283799375a3bd5","up":true},{"one":"df5ea72c0c257bbea9b43901a1e406bf6055a307d083367e7f1d944e65c2b3cd","other":"d68f504755b48f8d41570169dd3544ef1caea2ab1bb334f8d8c5fb27fe816f01","up":true},{"one":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","other":"f9243ad864859edd351592d24abdf5c9c4e37f4ffe2ae1201c45f26576ee5725","up":true},{"one":"fed15b57e749c5f8f5fdb382eb95abe2ce0dee184c817e557122207f3729b05c","other":"fb93417c597050f8deeb3098475f6cf0d213926332b59c25a538c1b4e6af89c0","up":true},{"one":"7471be56a2f617783d5a1ae4390794ad24f453259b7c1f8f65be8f71c8bf8bf7","other":"edca23c0fccfd1ec66f4d39bb129462776b9091c2337f6afbfe8737a0ebc0d7d","up":true},{"one":"edca23c0fccfd1ec66f4d39bb129462776b9091c2337f6afbfe8737a0ebc0d7d","other":"ecd2b502d4e9bf77369c62d1142a9fb4ab1071e751cc6971e9e381e953b0fc12","up":true},{"one":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","other":"290fca7596740129fd1a3075bbf56885d8c83381f981adb0fb283799375a3bd5","up":true},{"one":"c15d13aff7f5694fb8fc2a2154846ac703064133feb632e28830777bbfc7c016","other":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","up":true},{"one":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","other":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","up":true},{"one":"e67d72c8c72bbc7bec1d4e936d65adbeab75d1b6cafdf2c39e38bfc723cceebf","other":"edca23c0fccfd1ec66f4d39bb129462776b9091c2337f6afbfe8737a0ebc0d7d","up":true},{"one":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","other":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","up":true},{"one":"4019ce1dc667d90a85021b5fbf49ad77c1de7fd3443a40a17bdfaf5175efa1e1","other":"4a670fe61711307f7781b3b5a8014fc0e63866efe356a25e61b2e768a7389980","up":true},{"one":"f9243ad864859edd351592d24abdf5c9c4e37f4ffe2ae1201c45f26576ee5725","other":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","up":true},{"one":"4a670fe61711307f7781b3b5a8014fc0e63866efe356a25e61b2e768a7389980","other":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","up":true},{"one":"c15d13aff7f5694fb8fc2a2154846ac703064133feb632e28830777bbfc7c016","other":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","up":true},{"one":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","other":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","up":true},{"one":"72acf53ab7dbda90ea70e78d001fe21e7ab88e4054ff0453212e33a26fbbf7f1","other":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","up":true},{"one":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","other":"730749922ce3dcd8e249b988dbfb950c15473a2f6fd5cfe2a8126995fb1dc186","up":true},{"one":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","other":"e67d72c8c72bbc7bec1d4e936d65adbeab75d1b6cafdf2c39e38bfc723cceebf","up":true},{"one":"4cf655b03778bcb41c32664f13168dc32968354468c8aa699de52b43a2e0dc7d","other":"265d7fc553fb8dc016d7c6ccc31fc372912a11e9e8da0e5822e371a91d82e857","up":true},{"one":"571638637156bdaf92d9c715647a1cf64e91877c2d998658b7c6cd27522271bd","other":"43afd835930eac91c0c8947001dd60d8da9112c19cf3f1356589b58b48962166","up":true},{"one":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","other":"730749922ce3dcd8e249b988dbfb950c15473a2f6fd5cfe2a8126995fb1dc186","up":true},{"one":"c63e796efdc44712403199c11c3e5ff28e5d6a4d9ecdf3e95620fbd6afb1bc2d","other":"c99c6a2a2fe7f4613c8d918404c956eae0a00fce544c9eeba5da640e5e958874","up":true},{"one":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","other":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","up":true},{"one":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","other":"72acf53ab7dbda90ea70e78d001fe21e7ab88e4054ff0453212e33a26fbbf7f1","up":true},{"one":"fb93417c597050f8deeb3098475f6cf0d213926332b59c25a538c1b4e6af89c0","other":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","up":true},{"one":"7d941474f90981a03c95af4cf68be163d01a317e202ac0db82e25f46fc3db8aa","other":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","up":true},{"one":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","other":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","up":true},{"one":"86f71d23eb4efeeed4679be7a3d0f58f98b83d23b3e0a8b73f60f1535b51f25c","other":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","up":true},{"one":"fb93417c597050f8deeb3098475f6cf0d213926332b59c25a538c1b4e6af89c0","other":"8163eebe696123817a6d998d7781612931df6fa2fd0293c0d0dd395d031c53f6","up":true},{"one":"47f95708475c9d61ff2016e7707c4a3796f6c87313bd1d2bf91aa620113ba5f8","other":"5f056f9593be46d3eba4dc8d9e0b93262cd3f00e007c13c4c011d53e7c199628","up":true},{"one":"c4632e164170d35e91fce46f660265f73795f1d840effce5ab2aae18c73003d7","other":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","up":true},{"one":"1e4441309404b0dd8dd9e8b1fe78708b015a0b0278ce86bcdaabc7d2d691791a","other":"1835f62fe3889f43ba7b4b577869ec764e96fa9a051f1301fb96795daa8b0755","up":true},{"one":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","other":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","up":true},{"one":"99aaaa7c98113955ecc0163943317f6ec08ae755e96c25c9f79ca1f529c60587","other":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","up":true},{"one":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","other":"4a670fe61711307f7781b3b5a8014fc0e63866efe356a25e61b2e768a7389980","up":true},{"one":"f4e0d91ca58ed616918bf268565c2edb4176293e851b8d29cbbda55bdc608534","other":"fb93417c597050f8deeb3098475f6cf0d213926332b59c25a538c1b4e6af89c0","up":true},{"one":"99dbf361b67d6c370fd4f201b059e93da50dd88cde2e25d0bcf3c73afbf13535","other":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","up":true},{"one":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","other":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","up":true},{"one":"bc08af284ab661dcc1cb23859271590e50a09ff8af8073769c812901ea0b9080","other":"b4639751d67db0b32ad1846996fed0d5c59be76b77efaec5f66e64df35eefae8","up":true},{"one":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","other":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","up":true},{"one":"9eec5d5357732a7acd26cc8aa0bc65f6c6069aedf4f3ab4a6790c7fff6bc2695","other":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","up":true},{"one":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","other":"5f056f9593be46d3eba4dc8d9e0b93262cd3f00e007c13c4c011d53e7c199628","up":true},{"one":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","other":"2af02e62ff3e40be25d22c2f745a505b895b3804a80663cb53db4edd5365c568","up":true},{"one":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","other":"6d219b0ea4a1620a7ba051cc478da6df9ea443e87dde79e963ec789a5bad7459","up":true},{"one":"7a4118d2ce16e8258cc2cdf31c5891945e3787c46c0836fc916601584609b7db","other":"6d219b0ea4a1620a7ba051cc478da6df9ea443e87dde79e963ec789a5bad7459","up":true},{"one":"d68f504755b48f8d41570169dd3544ef1caea2ab1bb334f8d8c5fb27fe816f01","other":"8163eebe696123817a6d998d7781612931df6fa2fd0293c0d0dd395d031c53f6","up":true},{"one":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","other":"0f5e34b25f6851d0f3b572c64420de0aec01754bea0854bd7e9910b4b1cc66c3","up":true},{"one":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","other":"c0d1ac430a1466a1a28cb1aa3d29573ccedb13641ac19e6e615f2a96e8f0950b","up":true},{"one":"f4e0d91ca58ed616918bf268565c2edb4176293e851b8d29cbbda55bdc608534","other":"e8392bdce92a7dcfa5e3196a6e1d4e5dc4814c72e104d3e2aa1f1ab99e2a47a6","up":true},{"one":"5fab772fff883d04cf7e4713266fb1e98fa1746e12faeb04e87bcb37b8b61187","other":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","up":true},{"one":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","other":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","up":true},{"one":"52881f1fe0b8e9044a3bc035edbbd6343b9c41d49c0f2c2ca799c2e764489b91","other":"5fd02ea98977d976f5d47a4005300b9865b1dcdac77bf8a98a8920a89b57825c","up":true},{"one":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","other":"5110c7e54a18df6963919866e7f6c0c3a483045d5ac20fc0dfa5e1926681be7f","up":true},{"one":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","other":"7471be56a2f617783d5a1ae4390794ad24f453259b7c1f8f65be8f71c8bf8bf7","up":true},{"one":"0f5e34b25f6851d0f3b572c64420de0aec01754bea0854bd7e9910b4b1cc66c3","other":"72acf53ab7dbda90ea70e78d001fe21e7ab88e4054ff0453212e33a26fbbf7f1","up":true},{"one":"a485db6ec1929aaf695a84294f52905aa602584954e9ef71b4e18f7b847da31d","other":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","up":true},{"one":"5fab772fff883d04cf7e4713266fb1e98fa1746e12faeb04e87bcb37b8b61187","other":"46c5a266dd46041a62f21946290aad736d2298277bbbb16b1efb4490d9d31fa8","up":true},{"one":"ed1348b4037004b61bd4d6f9be1648112f835943a3b997b91ac931301311e007","other":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","up":true},{"one":"7de7eb0b73f3b02b998e592e0fc51b26c0c3ae6695d41897874d2a3d8ce3554b","other":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","up":true},{"one":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","other":"df5ea72c0c257bbea9b43901a1e406bf6055a307d083367e7f1d944e65c2b3cd","up":true},{"one":"15f6bb0640c3ac8ae8ecf10d0d46cc739bb6f86b0c0cf706103aad9f3785219b","other":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","up":true},{"one":"fed15b57e749c5f8f5fdb382eb95abe2ce0dee184c817e557122207f3729b05c","other":"f9243ad864859edd351592d24abdf5c9c4e37f4ffe2ae1201c45f26576ee5725","up":true},{"one":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","other":"1e4441309404b0dd8dd9e8b1fe78708b015a0b0278ce86bcdaabc7d2d691791a","up":true},{"one":"46c5a266dd46041a62f21946290aad736d2298277bbbb16b1efb4490d9d31fa8","other":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","up":true},{"one":"7854fb9f0d2f016653994b9e23033f27e21a2ef186a352c8d068b75ea20beec2","other":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","up":true},{"one":"d6f37555e9f35a3ef3b8b4d5c18cf0702ed99c1c8b5b0a8554deddcbe542b88c","other":"cb69f673a54a75de300d1d1ab989caa04579504cbcaf78f03bc192c9578ec5a0","up":true},{"one":"dc3ec6529cec19709403e473d4546ba07f7aaaaec5be7cb0618246a4c0458944","other":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","up":true},{"one":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","other":"7de7eb0b73f3b02b998e592e0fc51b26c0c3ae6695d41897874d2a3d8ce3554b","up":true},{"one":"275c93677aefd92ab38f9e5b5b4d29f90e809ccaf77b088102071aba26421f3e","other":"2279d612454ff034032a5acf5a039477a11479b9b9a93edc26b61312e8d9b156","up":true},{"one":"7471be56a2f617783d5a1ae4390794ad24f453259b7c1f8f65be8f71c8bf8bf7","other":"7854fb9f0d2f016653994b9e23033f27e21a2ef186a352c8d068b75ea20beec2","up":true},{"one":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","other":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","up":true},{"one":"ecd2b502d4e9bf77369c62d1142a9fb4ab1071e751cc6971e9e381e953b0fc12","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","other":"fb93417c597050f8deeb3098475f6cf0d213926332b59c25a538c1b4e6af89c0","up":true},{"one":"36485615576c68702382f614054494cc4a40b90fd66db86b9d86a6a131dcc6ac","other":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","up":true},{"one":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","other":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","up":true},{"one":"265d7fc553fb8dc016d7c6ccc31fc372912a11e9e8da0e5822e371a91d82e857","other":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","up":true},{"one":"bf5ac6f9b651895c8d75ab3ae7994f91981dbbcdce25a7cd718fc996da96946d","other":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","up":true},{"one":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","other":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","up":true},{"one":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","other":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","up":true},{"one":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","other":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","up":true},{"one":"2e9f7d37106b59d6f411dfa0392325d1fa279fe45c18a101e32ece6716e88946","other":"18f9292fbe6e94ec3df4d0a896c71ed2b1771437e42d8475a7aae63c7992834d","up":true},{"one":"9294e5b39a1cd4230400e033da1ac1f79a4025adab41ce8c0e0e47dda2418cb5","other":"985c14910d922bbd1556c30620e3ab72170da6553e658dd3806d773a734a1c93","up":true},{"one":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","other":"46c5a266dd46041a62f21946290aad736d2298277bbbb16b1efb4490d9d31fa8","up":true},{"one":"46c5a266dd46041a62f21946290aad736d2298277bbbb16b1efb4490d9d31fa8","other":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","up":true},{"one":"290fca7596740129fd1a3075bbf56885d8c83381f981adb0fb283799375a3bd5","other":"2fd800d224f124e47038bf140ebd486f1d0ab93169c998d2dfabfd1d3ced96ed","up":true},{"one":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","other":"b45d1aee940e5d2c71db22e2afbe908d5967e437fbb5bd051edea86ea4ac9d30","up":true},{"one":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","other":"72acf53ab7dbda90ea70e78d001fe21e7ab88e4054ff0453212e33a26fbbf7f1","up":true},{"one":"b310299bef4f4e84a1ecc960143e4b1bbe98cd4eb8352458c477569ca9994c20","other":"b45d1aee940e5d2c71db22e2afbe908d5967e437fbb5bd051edea86ea4ac9d30","up":true},{"one":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","other":"167306fb0e24ddd775c2ac0722103ce5db98bb0ef60159b91f584475d34cb5bf","up":true},{"one":"6ea59b849a9754c022219146efc27fa6561e5aca8338a111847259695198267b","other":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","up":true},{"one":"211a516fb95d093eb43f4716edeb1e6f76ed79d2aeee3393740e244970187840","other":"18f9292fbe6e94ec3df4d0a896c71ed2b1771437e42d8475a7aae63c7992834d","up":true},{"one":"2e9f7d37106b59d6f411dfa0392325d1fa279fe45c18a101e32ece6716e88946","other":"a749372cccb7001a480b9416c41305bd1d3bc417e15669ae587d4375f2bbcbf7","up":true},{"one":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","other":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","up":true},{"one":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","other":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","up":true},{"one":"b79fd26ce97e98e7432029dea6bf11e48ea9e8f2385dec5c60be0cbd51bbef54","other":"baf32cface953bd22141970b691567ed375f06043fe31de354598fb4230fb0a3","up":true},{"one":"1e4441309404b0dd8dd9e8b1fe78708b015a0b0278ce86bcdaabc7d2d691791a","other":"1d942c85568f640497c46a9bd5a9825bc6fb05571203a3f0f14a3032602d81fc","up":true},{"one":"46c5a266dd46041a62f21946290aad736d2298277bbbb16b1efb4490d9d31fa8","other":"72acf53ab7dbda90ea70e78d001fe21e7ab88e4054ff0453212e33a26fbbf7f1","up":true},{"one":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","other":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","up":true},{"one":"06aa2f43956f978abe7b8f8f78a5a8cd59decb66ec54b7c81ae50c3752798c5e","other":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","up":true},{"one":"2279d612454ff034032a5acf5a039477a11479b9b9a93edc26b61312e8d9b156","other":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","up":true},{"one":"f5dd00d94488f3387d64ccc2115cd91e5073c06de36d195a5c1a95db23cd5c68","other":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","up":true},{"one":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","other":"c0d1ac430a1466a1a28cb1aa3d29573ccedb13641ac19e6e615f2a96e8f0950b","up":true},{"one":"18f9292fbe6e94ec3df4d0a896c71ed2b1771437e42d8475a7aae63c7992834d","other":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","up":true},{"one":"458a31b8fdbc42f5e80f01118c9dc041740212c09ddfb4b137c9e7e93ae9ec51","other":"3f3e66f7c8fc7daae8a7a84a9c90f1ea64faa43930f36fc8f11e2d7b751f8ee9","up":true},{"one":"bfeca898b1755a001eb0bcf8f5ad5713a550d0597f99ef7967ea4e7add8d3e81","other":"b60deba7c8676305b67af41d2de866bddd72edc0667d427e51fc3f2e91412b45","up":true},{"one":"06aa2f43956f978abe7b8f8f78a5a8cd59decb66ec54b7c81ae50c3752798c5e","other":"167306fb0e24ddd775c2ac0722103ce5db98bb0ef60159b91f584475d34cb5bf","up":true},{"one":"e4c3fa0700516da5650f794067eb1c90ba7eaab2b65019bf34552c26f8c1d9a6","other":"77ec3a73ce1d597d307e87ea8d026baae2a7df4d288a657f240eaaaae7fdb456","up":true},{"one":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","other":"167306fb0e24ddd775c2ac0722103ce5db98bb0ef60159b91f584475d34cb5bf","up":true},{"one":"f5dd00d94488f3387d64ccc2115cd91e5073c06de36d195a5c1a95db23cd5c68","other":"167306fb0e24ddd775c2ac0722103ce5db98bb0ef60159b91f584475d34cb5bf","up":true},{"one":"275c93677aefd92ab38f9e5b5b4d29f90e809ccaf77b088102071aba26421f3e","other":"06aa2f43956f978abe7b8f8f78a5a8cd59decb66ec54b7c81ae50c3752798c5e","up":true},{"one":"7294223c0ab27851df8c25418acf09a28efa95fc238252af8a68390db07b77e4","other":"e4c3fa0700516da5650f794067eb1c90ba7eaab2b65019bf34552c26f8c1d9a6","up":true},{"one":"9390ef61753fcf9dae5e585a85650287fe1f792538721a2cf767adc44c5e7ce0","other":"bfeca898b1755a001eb0bcf8f5ad5713a550d0597f99ef7967ea4e7add8d3e81","up":true},{"one":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","other":"06aa2f43956f978abe7b8f8f78a5a8cd59decb66ec54b7c81ae50c3752798c5e","up":true},{"one":"77ec3a73ce1d597d307e87ea8d026baae2a7df4d288a657f240eaaaae7fdb456","other":"43afd835930eac91c0c8947001dd60d8da9112c19cf3f1356589b58b48962166","up":true},{"one":"e4c3fa0700516da5650f794067eb1c90ba7eaab2b65019bf34552c26f8c1d9a6","other":"ed6570e63fc0e2e2d6290645c4339b70a0f1c14064d286b4e0ba143607bd70c8","up":true},{"one":"43afd835930eac91c0c8947001dd60d8da9112c19cf3f1356589b58b48962166","other":"670d1c31407b4f34bb955031e9a3ab7980273155080b459a082687d53a3b42a6","up":true},{"one":"9390ef61753fcf9dae5e585a85650287fe1f792538721a2cf767adc44c5e7ce0","other":"b60deba7c8676305b67af41d2de866bddd72edc0667d427e51fc3f2e91412b45","up":true},{"one":"7294223c0ab27851df8c25418acf09a28efa95fc238252af8a68390db07b77e4","other":"ed6570e63fc0e2e2d6290645c4339b70a0f1c14064d286b4e0ba143607bd70c8","up":true},{"one":"ad36b802e9b01727e516879c0055e3c46b810845794f7772ad85e8062111b167","other":"c3f356325eb1e61f2cfd8d80d085fb144c6887e1d710c9d133109b84b823425d","up":true},{"one":"bfeca898b1755a001eb0bcf8f5ad5713a550d0597f99ef7967ea4e7add8d3e81","other":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","up":true},{"one":"ed6570e63fc0e2e2d6290645c4339b70a0f1c14064d286b4e0ba143607bd70c8","other":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","up":true},{"one":"7ffed78e11c71eccfdddf6a8e5e367831b26a6b11e4d3b60c5b9fc50f4bcdf33","other":"458a31b8fdbc42f5e80f01118c9dc041740212c09ddfb4b137c9e7e93ae9ec51","up":true},{"one":"43afd835930eac91c0c8947001dd60d8da9112c19cf3f1356589b58b48962166","other":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","up":true},{"one":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","other":"2f9f266a3b660fe685de2932de82979bd221706db1e2b954a93edc6609c378d9","up":true},{"one":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","other":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","up":true},{"one":"ed1348b4037004b61bd4d6f9be1648112f835943a3b997b91ac931301311e007","other":"f048e37427d68c4134a115cc7df6a8924ea95f24e7f2435372ae331e24b5b7fd","up":true},{"one":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","other":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","up":true},{"one":"4a670fe61711307f7781b3b5a8014fc0e63866efe356a25e61b2e768a7389980","other":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","up":true},{"one":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","other":"9fee945c89d6bb59e77a24183c2855f4eece0b5d3266f750ea95483eae1e1871","up":true},{"one":"0f5e34b25f6851d0f3b572c64420de0aec01754bea0854bd7e9910b4b1cc66c3","other":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","up":true},{"one":"290fca7596740129fd1a3075bbf56885d8c83381f981adb0fb283799375a3bd5","other":"259d3874697a0c3192f973c620880b2a86567289f5222d07eb91a51867352559","up":true},{"one":"bf5ac6f9b651895c8d75ab3ae7994f91981dbbcdce25a7cd718fc996da96946d","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"4cf655b03778bcb41c32664f13168dc32968354468c8aa699de52b43a2e0dc7d","other":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","up":true},{"one":"d3d254a3024a7aac01e97a5f0aff13756741867e599bc94c068f0d1d19ca8176","other":"df5ea72c0c257bbea9b43901a1e406bf6055a307d083367e7f1d944e65c2b3cd","up":true},{"one":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","other":"e8392bdce92a7dcfa5e3196a6e1d4e5dc4814c72e104d3e2aa1f1ab99e2a47a6","up":true},{"one":"ad36b802e9b01727e516879c0055e3c46b810845794f7772ad85e8062111b167","other":"ed6570e63fc0e2e2d6290645c4339b70a0f1c14064d286b4e0ba143607bd70c8","up":true},{"one":"7ffed78e11c71eccfdddf6a8e5e367831b26a6b11e4d3b60c5b9fc50f4bcdf33","other":"487edf32027381256a864bc2e9bca175f7216cc1234731bd4dc90d2cbad6975d","up":true},{"one":"9294e5b39a1cd4230400e033da1ac1f79a4025adab41ce8c0e0e47dda2418cb5","other":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","up":true},{"one":"74062d9dd73e26066fd44e4f6c696f271c9dc6860bdc5ba3fcf447f455c8cafa","other":"7d45f1e91bcd683d870422b2f591c76f6ab07ff4b079f35c280ce46f7fc364e1","up":true},{"one":"6ea59b849a9754c022219146efc27fa6561e5aca8338a111847259695198267b","other":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","up":true},{"one":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","other":"f4e0d91ca58ed616918bf268565c2edb4176293e851b8d29cbbda55bdc608534","up":true},{"one":"18f9292fbe6e94ec3df4d0a896c71ed2b1771437e42d8475a7aae63c7992834d","other":"167306fb0e24ddd775c2ac0722103ce5db98bb0ef60159b91f584475d34cb5bf","up":true},{"one":"06aa2f43956f978abe7b8f8f78a5a8cd59decb66ec54b7c81ae50c3752798c5e","other":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","up":true},{"one":"2279d612454ff034032a5acf5a039477a11479b9b9a93edc26b61312e8d9b156","other":"3f3e66f7c8fc7daae8a7a84a9c90f1ea64faa43930f36fc8f11e2d7b751f8ee9","up":true},{"one":"bfeca898b1755a001eb0bcf8f5ad5713a550d0597f99ef7967ea4e7add8d3e81","other":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","up":true},{"one":"e4c3fa0700516da5650f794067eb1c90ba7eaab2b65019bf34552c26f8c1d9a6","other":"f5dd00d94488f3387d64ccc2115cd91e5073c06de36d195a5c1a95db23cd5c68","up":true},{"one":"3f3e66f7c8fc7daae8a7a84a9c90f1ea64faa43930f36fc8f11e2d7b751f8ee9","other":"18f9292fbe6e94ec3df4d0a896c71ed2b1771437e42d8475a7aae63c7992834d","up":true},{"one":"43afd835930eac91c0c8947001dd60d8da9112c19cf3f1356589b58b48962166","other":"487edf32027381256a864bc2e9bca175f7216cc1234731bd4dc90d2cbad6975d","up":true},{"one":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","other":"d6d2d35b3819e03b39d7146c4dd1b53fb7b70681c8860464650d5eb2d09dda2c","up":true},{"one":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","other":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","up":true},{"one":"b4639751d67db0b32ad1846996fed0d5c59be76b77efaec5f66e64df35eefae8","other":"d8b0425e4cb5134ddb2de5c05e0ae9b861bd67409c1446ca228318f79f5dd1f6","up":true},{"one":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","other":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","up":true},{"one":"b4639751d67db0b32ad1846996fed0d5c59be76b77efaec5f66e64df35eefae8","other":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","up":true},{"one":"487edf32027381256a864bc2e9bca175f7216cc1234731bd4dc90d2cbad6975d","other":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","up":true},{"one":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","other":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","up":true},{"one":"d6d2d35b3819e03b39d7146c4dd1b53fb7b70681c8860464650d5eb2d09dda2c","other":"f1fcfc064446a432ac7a21efc386fa3d053d0ca4540fd4ddbbb253ec11f9ce94","up":true},{"one":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","other":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","up":true},{"one":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","other":"3af3bb6c4343a19e3c0f9a89697cd70a3c79c64b1e40afa59b188c5553eed567","up":true},{"one":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","other":"7de7eb0b73f3b02b998e592e0fc51b26c0c3ae6695d41897874d2a3d8ce3554b","up":true},{"one":"14c81fb2fb8fa5a6b47288356ab486012046d09959b9ef4814a09dc3facb4853","other":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","up":true},{"one":"e67d72c8c72bbc7bec1d4e936d65adbeab75d1b6cafdf2c39e38bfc723cceebf","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"9fee945c89d6bb59e77a24183c2855f4eece0b5d3266f750ea95483eae1e1871","other":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","up":true},{"one":"c4844b8046d1c47f699ee1c232d945829105d45af26fba722ce4d0a937c99631","other":"c7703c4697637f40a50a5d0b656c4e54010ef1277b64d251ccd0752512e246e9","up":true},{"one":"4a670fe61711307f7781b3b5a8014fc0e63866efe356a25e61b2e768a7389980","other":"4a81ee929074bd6d9932fb4d970f5207db8680bda0e951aa72a6bd64df45108d","up":true},{"one":"13d8107c669cc27f6cbc64cef6b2bd11f72db297f2857c2de7323b92632b8231","other":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","up":true},{"one":"265d7fc553fb8dc016d7c6ccc31fc372912a11e9e8da0e5822e371a91d82e857","other":"290fca7596740129fd1a3075bbf56885d8c83381f981adb0fb283799375a3bd5","up":true},{"one":"6d219b0ea4a1620a7ba051cc478da6df9ea443e87dde79e963ec789a5bad7459","other":"99aaaa7c98113955ecc0163943317f6ec08ae755e96c25c9f79ca1f529c60587","up":true},{"one":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","other":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","up":true},{"one":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","other":"adfc03118406bc5af4e3f3c6c89c3448c3fe4f9df20b15bcca9bbacf58ee5457","up":true},{"one":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","other":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","up":true},{"one":"6d219b0ea4a1620a7ba051cc478da6df9ea443e87dde79e963ec789a5bad7459","other":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","up":true},{"one":"5f056f9593be46d3eba4dc8d9e0b93262cd3f00e007c13c4c011d53e7c199628","other":"6d219b0ea4a1620a7ba051cc478da6df9ea443e87dde79e963ec789a5bad7459","up":true},{"one":"21682cd736edfb8b451dfd9c55eb9ea3b7cf2e35cad32537f1f4800644fbf9a7","other":"1b1eca7b35086d8b217431c1d6a01fc07dc8bc816196a80015c9e163bed676ff","up":true},{"one":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","other":"ae715a40e9bf50b4eb954c6b87efbf1a6e7abc3da6d84055e4591aadf5bad5c8","up":true},{"one":"f1fcfc064446a432ac7a21efc386fa3d053d0ca4540fd4ddbbb253ec11f9ce94","other":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","up":true},{"one":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","other":"a6720426faa1ea7e2d55c1c5ad05ba2e8c534d120ebb5e1375cf769d73191708","up":true},{"one":"6d219b0ea4a1620a7ba051cc478da6df9ea443e87dde79e963ec789a5bad7459","other":"6544c4a403d9809697420ff9840ea9de0c9ba6491de28dac288db6257f2dc218","up":true},{"one":"66100e4f9d103d935164f39ea3f0332708f3825ebcf31ac6f8c9714c75a50860","other":"4f90f53754ee50357491dbab7c3e6748b40133e3339920b32f4c3457f4258477","up":true},{"one":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","other":"5fa8a5a29a47694653a5e55fd9b76e9c4f717ecc9b7088a6ec7ee273b291d49e","up":true},{"one":"5f056f9593be46d3eba4dc8d9e0b93262cd3f00e007c13c4c011d53e7c199628","other":"6544c4a403d9809697420ff9840ea9de0c9ba6491de28dac288db6257f2dc218","up":true},{"one":"5f056f9593be46d3eba4dc8d9e0b93262cd3f00e007c13c4c011d53e7c199628","other":"7d941474f90981a03c95af4cf68be163d01a317e202ac0db82e25f46fc3db8aa","up":true},{"one":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","other":"cb69f673a54a75de300d1d1ab989caa04579504cbcaf78f03bc192c9578ec5a0","up":true},{"one":"f1fcfc064446a432ac7a21efc386fa3d053d0ca4540fd4ddbbb253ec11f9ce94","other":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","up":true},{"one":"a6720426faa1ea7e2d55c1c5ad05ba2e8c534d120ebb5e1375cf769d73191708","other":"aa504681c991c69015c7ef0469902c44fc4e900356b181aa252a1280ffbd2e3b","up":true},{"one":"66100e4f9d103d935164f39ea3f0332708f3825ebcf31ac6f8c9714c75a50860","other":"4d447ea80bc126e57b9fc91203a310c5f4b242df5ad70d5087d16697a626e601","up":true},{"one":"3345e03880c377fc1fe7b6205011ce3e32525cefee5e03161105f1512893f9fb","other":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","up":true},{"one":"abfabc2964ce2f7b8b3702e897a8cc83fb0c009d5af9670b889db582e57ef7c1","other":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","up":true},{"one":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","other":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","up":true},{"one":"abfabc2964ce2f7b8b3702e897a8cc83fb0c009d5af9670b889db582e57ef7c1","other":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","up":true},{"one":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","other":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","up":true},{"one":"52881f1fe0b8e9044a3bc035edbbd6343b9c41d49c0f2c2ca799c2e764489b91","other":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","up":true},{"one":"7d45f1e91bcd683d870422b2f591c76f6ab07ff4b079f35c280ce46f7fc364e1","other":"7294223c0ab27851df8c25418acf09a28efa95fc238252af8a68390db07b77e4","up":true},{"one":"32dd86f3f5c3abe55d6b1fe352e9d1d0cc63d61cfa60b8e70b2ee9db2e28e1aa","other":"74062d9dd73e26066fd44e4f6c696f271c9dc6860bdc5ba3fcf447f455c8cafa","up":true},{"one":"ecd2b502d4e9bf77369c62d1142a9fb4ab1071e751cc6971e9e381e953b0fc12","other":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","up":true},{"one":"29fd4473f1b6c37f8268d73dd5f683a0f7eb64bd793dab2c450c10d8ab0b666d","other":"670d1c31407b4f34bb955031e9a3ab7980273155080b459a082687d53a3b42a6","up":true},{"one":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","other":"c8fe626c60d6cd15912a22d2c5872f10a8f8aef5b6f6b2f1070edec6670bf8b1","up":true},{"one":"df5ea72c0c257bbea9b43901a1e406bf6055a307d083367e7f1d944e65c2b3cd","other":"dc3ec6529cec19709403e473d4546ba07f7aaaaec5be7cb0618246a4c0458944","up":true},{"one":"aa504681c991c69015c7ef0469902c44fc4e900356b181aa252a1280ffbd2e3b","other":"ae715a40e9bf50b4eb954c6b87efbf1a6e7abc3da6d84055e4591aadf5bad5c8","up":true},{"one":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","other":"7d45f1e91bcd683d870422b2f591c76f6ab07ff4b079f35c280ce46f7fc364e1","up":true},{"one":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","other":"92325e52babbf2b8ae3c37beec7e440d11efed2d103befa2c4a969315a2eab56","up":true},{"one":"52619a523050aa8caa32448e59721a76dc3321dc92cfcb1b05f1cec5b79d424a","other":"4d447ea80bc126e57b9fc91203a310c5f4b242df5ad70d5087d16697a626e601","up":true},{"one":"31edba65e539a9a3c7107a131c4fb4b5a8e2c2d4e2db7874a0116c173961e27c","other":"32dd86f3f5c3abe55d6b1fe352e9d1d0cc63d61cfa60b8e70b2ee9db2e28e1aa","up":true},{"one":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","other":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","up":true},{"one":"adfc03118406bc5af4e3f3c6c89c3448c3fe4f9df20b15bcca9bbacf58ee5457","other":"571638637156bdaf92d9c715647a1cf64e91877c2d998658b7c6cd27522271bd","up":true},{"one":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","other":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","up":true},{"one":"31edba65e539a9a3c7107a131c4fb4b5a8e2c2d4e2db7874a0116c173961e27c","other":"29fd4473f1b6c37f8268d73dd5f683a0f7eb64bd793dab2c450c10d8ab0b666d","up":true},{"one":"1d5fc65b4121cccaff0d2a853c6bbff0c10b4ec28cfc5ef5cbea8bc28bc4cbec","other":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","up":true},{"one":"a749372cccb7001a480b9416c41305bd1d3bc417e15669ae587d4375f2bbcbf7","other":"b60deba7c8676305b67af41d2de866bddd72edc0667d427e51fc3f2e91412b45","up":true},{"one":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","other":"def4f0b988942a8699f6b500cc2531c7dea065e3dc5bdcbeb26bca8f00e1d3a6","up":true},{"one":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","other":"a6720426faa1ea7e2d55c1c5ad05ba2e8c534d120ebb5e1375cf769d73191708","up":true},{"one":"29fd4473f1b6c37f8268d73dd5f683a0f7eb64bd793dab2c450c10d8ab0b666d","other":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","up":true},{"one":"adfc03118406bc5af4e3f3c6c89c3448c3fe4f9df20b15bcca9bbacf58ee5457","other":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","up":true},{"one":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","other":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","up":true},{"one":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","other":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","up":true},{"one":"1d5fc65b4121cccaff0d2a853c6bbff0c10b4ec28cfc5ef5cbea8bc28bc4cbec","other":"123fe1d442c7dc905a7206e712fbd9cea640295a6823b39b1945629ffa89e895","up":true},{"one":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","other":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","up":true},{"one":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","other":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","up":true},{"one":"89ee628583b6e0345ceb312c2e2cbf5ac410eb00531be903a4b94b60b639993b","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"7d45f1e91bcd683d870422b2f591c76f6ab07ff4b079f35c280ce46f7fc364e1","other":"79abd33d150fdbcbce6f55f3d101608b80e03ede7e4627bc06ba5c1b001538a4","up":true},{"one":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","other":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","up":true},{"one":"32dd86f3f5c3abe55d6b1fe352e9d1d0cc63d61cfa60b8e70b2ee9db2e28e1aa","other":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","up":true},{"one":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","other":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","up":true},{"one":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","other":"353875d4ae2b9c5cc6cb14059bdfb9540647100c7c3334bc60db2ca2b4ddf3f5","up":true},{"one":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","other":"a6720426faa1ea7e2d55c1c5ad05ba2e8c534d120ebb5e1375cf769d73191708","up":true},{"one":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","other":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","up":true},{"one":"2f9f266a3b660fe685de2932de82979bd221706db1e2b954a93edc6609c378d9","other":"259d3874697a0c3192f973c620880b2a86567289f5222d07eb91a51867352559","up":true},{"one":"31edba65e539a9a3c7107a131c4fb4b5a8e2c2d4e2db7874a0116c173961e27c","other":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","up":true},{"one":"abfabc2964ce2f7b8b3702e897a8cc83fb0c009d5af9670b889db582e57ef7c1","other":"aa504681c991c69015c7ef0469902c44fc4e900356b181aa252a1280ffbd2e3b","up":true},{"one":"adfc03118406bc5af4e3f3c6c89c3448c3fe4f9df20b15bcca9bbacf58ee5457","other":"af358f626738d2e5d17060abbaf24cb0124a48c5edb8f3baeda30bea7b403dfa","up":true},{"one":"52619a523050aa8caa32448e59721a76dc3321dc92cfcb1b05f1cec5b79d424a","other":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","up":true},{"one":"3345e03880c377fc1fe7b6205011ce3e32525cefee5e03161105f1512893f9fb","other":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","up":true},{"one":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","other":"95e0529227286d442cdeeed428614f99c19f9223426e6f6cefbef2d9d47ce96f","up":true},{"one":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","other":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","up":true},{"one":"f048e37427d68c4134a115cc7df6a8924ea95f24e7f2435372ae331e24b5b7fd","other":"c7703c4697637f40a50a5d0b656c4e54010ef1277b64d251ccd0752512e246e9","up":true},{"one":"aa504681c991c69015c7ef0469902c44fc4e900356b181aa252a1280ffbd2e3b","other":"af358f626738d2e5d17060abbaf24cb0124a48c5edb8f3baeda30bea7b403dfa","up":true},{"one":"6d219b0ea4a1620a7ba051cc478da6df9ea443e87dde79e963ec789a5bad7459","other":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","up":true},{"one":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","other":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","up":true},{"one":"95e0529227286d442cdeeed428614f99c19f9223426e6f6cefbef2d9d47ce96f","other":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","up":true},{"one":"c7703c4697637f40a50a5d0b656c4e54010ef1277b64d251ccd0752512e246e9","other":"c15d13aff7f5694fb8fc2a2154846ac703064133feb632e28830777bbfc7c016","up":true},{"one":"5fd02ea98977d976f5d47a4005300b9865b1dcdac77bf8a98a8920a89b57825c","other":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","up":true},{"one":"2f9f266a3b660fe685de2932de82979bd221706db1e2b954a93edc6609c378d9","other":"52881f1fe0b8e9044a3bc035edbbd6343b9c41d49c0f2c2ca799c2e764489b91","up":true},{"one":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","other":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","up":true},{"one":"7a4118d2ce16e8258cc2cdf31c5891945e3787c46c0836fc916601584609b7db","other":"7d941474f90981a03c95af4cf68be163d01a317e202ac0db82e25f46fc3db8aa","up":true},{"one":"2f9f266a3b660fe685de2932de82979bd221706db1e2b954a93edc6609c378d9","other":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","up":true},{"one":"95e0529227286d442cdeeed428614f99c19f9223426e6f6cefbef2d9d47ce96f","other":"353875d4ae2b9c5cc6cb14059bdfb9540647100c7c3334bc60db2ca2b4ddf3f5","up":true},{"one":"c4844b8046d1c47f699ee1c232d945829105d45af26fba722ce4d0a937c99631","other":"c15d13aff7f5694fb8fc2a2154846ac703064133feb632e28830777bbfc7c016","up":true},{"one":"0592048ae4e6a3000f36e8e1c0e1328c26c0d4bb6369d6cd867a08d847dacb9e","other":"0210146e650132dd0ceb8cea779cafc5485822eeeb9fcc17f292af5cef6e9632","up":true},{"one":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","other":"7854fb9f0d2f016653994b9e23033f27e21a2ef186a352c8d068b75ea20beec2","up":true},{"one":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","other":"043f24ce87fbd01f6f663f323ac6c156810a67b12efbec2f219ae9a882a4bd16","up":true},{"one":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","other":"c98d389a6d8e6eb5a1eb5a6f235857d5d939d30e51c16d80c039c9e0eb2f7eca","up":true},{"one":"34fce2b8208c6c8ed005943b3f2cb9f8923739ce262e2040f7ffc048093ef910","other":"3345e03880c377fc1fe7b6205011ce3e32525cefee5e03161105f1512893f9fb","up":true},{"one":"a485db6ec1929aaf695a84294f52905aa602584954e9ef71b4e18f7b847da31d","other":"b4639751d67db0b32ad1846996fed0d5c59be76b77efaec5f66e64df35eefae8","up":true},{"one":"ed1348b4037004b61bd4d6f9be1648112f835943a3b997b91ac931301311e007","other":"f4eee71a143722ea8288279876bf5687c1ed65eede0129e987e53f5ec76d8543","up":true},{"one":"13d8107c669cc27f6cbc64cef6b2bd11f72db297f2857c2de7323b92632b8231","other":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","up":true},{"one":"388d46d6ea3fd92aa959b04d597ff1096d6133f171181c8fb54e52935b62d33c","other":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","up":true},{"one":"34fce2b8208c6c8ed005943b3f2cb9f8923739ce262e2040f7ffc048093ef910","other":"353875d4ae2b9c5cc6cb14059bdfb9540647100c7c3334bc60db2ca2b4ddf3f5","up":true},{"one":"d68f504755b48f8d41570169dd3544ef1caea2ab1bb334f8d8c5fb27fe816f01","other":"dc3ec6529cec19709403e473d4546ba07f7aaaaec5be7cb0618246a4c0458944","up":true},{"one":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","other":"88da9dccda89c111dde40318fd1e5239e77b670f5a1b88a3e9320b7350885e31","up":true},{"one":"c0d1ac430a1466a1a28cb1aa3d29573ccedb13641ac19e6e615f2a96e8f0950b","other":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","up":true},{"one":"275c93677aefd92ab38f9e5b5b4d29f90e809ccaf77b088102071aba26421f3e","other":"3f3e66f7c8fc7daae8a7a84a9c90f1ea64faa43930f36fc8f11e2d7b751f8ee9","up":true},{"one":"458a31b8fdbc42f5e80f01118c9dc041740212c09ddfb4b137c9e7e93ae9ec51","other":"74062d9dd73e26066fd44e4f6c696f271c9dc6860bdc5ba3fcf447f455c8cafa","up":true},{"one":"985c14910d922bbd1556c30620e3ab72170da6553e658dd3806d773a734a1c93","other":"92325e52babbf2b8ae3c37beec7e440d11efed2d103befa2c4a969315a2eab56","up":true},{"one":"211a516fb95d093eb43f4716edeb1e6f76ed79d2aeee3393740e244970187840","other":"36485615576c68702382f614054494cc4a40b90fd66db86b9d86a6a131dcc6ac","up":true},{"one":"52619a523050aa8caa32448e59721a76dc3321dc92cfcb1b05f1cec5b79d424a","other":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","up":true},{"one":"f1fcfc064446a432ac7a21efc386fa3d053d0ca4540fd4ddbbb253ec11f9ce94","other":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","up":true},{"one":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","other":"42c019ddad2fde0f0c7f5041eb584996976844f8b2e08e20458437578d0257ef","up":true},{"one":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","other":"3e85d2d3d6750f84500fcf523743e0c242943e69188cb83114adad1787e6c089","up":true},{"one":"21682cd736edfb8b451dfd9c55eb9ea3b7cf2e35cad32537f1f4800644fbf9a7","other":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","up":true},{"one":"89ee628583b6e0345ceb312c2e2cbf5ac410eb00531be903a4b94b60b639993b","other":"a80b39cf280fa80b323370cfc37e8e3ff38de47d5de445c500f36c8cf2390b53","up":true},{"one":"be0ab3ddc656568900d8e1f42394cc942464596b7565db40f6c5a0c24ee942f2","other":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","up":true},{"one":"a6720426faa1ea7e2d55c1c5ad05ba2e8c534d120ebb5e1375cf769d73191708","other":"b4c7555a38d1d4e3bd1d01bcc47a84efd6128e8cc0fd803362da8b341f984a4d","up":true},{"one":"def4f0b988942a8699f6b500cc2531c7dea065e3dc5bdcbeb26bca8f00e1d3a6","other":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","up":true},{"one":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","other":"21682cd736edfb8b451dfd9c55eb9ea3b7cf2e35cad32537f1f4800644fbf9a7","up":true},{"one":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","other":"66100e4f9d103d935164f39ea3f0332708f3825ebcf31ac6f8c9714c75a50860","up":true},{"one":"def4f0b988942a8699f6b500cc2531c7dea065e3dc5bdcbeb26bca8f00e1d3a6","other":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","up":true},{"one":"3a4a300de1009d694bebce2362ca429086ade2f551989d55ba459a68efea15af","other":"0210146e650132dd0ceb8cea779cafc5485822eeeb9fcc17f292af5cef6e9632","up":true},{"one":"42c019ddad2fde0f0c7f5041eb584996976844f8b2e08e20458437578d0257ef","other":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","up":true},{"one":"34fce2b8208c6c8ed005943b3f2cb9f8923739ce262e2040f7ffc048093ef910","other":"388d46d6ea3fd92aa959b04d597ff1096d6133f171181c8fb54e52935b62d33c","up":true},{"one":"5fd02ea98977d976f5d47a4005300b9865b1dcdac77bf8a98a8920a89b57825c","other":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","up":true},{"one":"5fa8a5a29a47694653a5e55fd9b76e9c4f717ecc9b7088a6ec7ee273b291d49e","other":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","up":true},{"one":"396b256b223f346a490844895b9f1046b4790d624af1c43ecae1de318c7214a8","other":"1e4441309404b0dd8dd9e8b1fe78708b015a0b0278ce86bcdaabc7d2d691791a","up":true},{"one":"b71052b072cb98fb79151fd0c6044828e535a0547cace931183c7e4e7f227698","other":"b4639751d67db0b32ad1846996fed0d5c59be76b77efaec5f66e64df35eefae8","up":true},{"one":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","other":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","up":true},{"one":"353875d4ae2b9c5cc6cb14059bdfb9540647100c7c3334bc60db2ca2b4ddf3f5","other":"3e85d2d3d6750f84500fcf523743e0c242943e69188cb83114adad1787e6c089","up":true},{"one":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","other":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","up":true},{"one":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","other":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","up":true},{"one":"2f9f266a3b660fe685de2932de82979bd221706db1e2b954a93edc6609c378d9","other":"2f2284537629514837d5c2296d2871d8c4c147719f0ddc28ef7c629efd44dd61","up":true},{"one":"1b1eca7b35086d8b217431c1d6a01fc07dc8bc816196a80015c9e163bed676ff","other":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","up":true},{"one":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","other":"86f71d23eb4efeeed4679be7a3d0f58f98b83d23b3e0a8b73f60f1535b51f25c","up":true},{"one":"79abd33d150fdbcbce6f55f3d101608b80e03ede7e4627bc06ba5c1b001538a4","other":"487edf32027381256a864bc2e9bca175f7216cc1234731bd4dc90d2cbad6975d","up":true},{"one":"c8fe626c60d6cd15912a22d2c5872f10a8f8aef5b6f6b2f1070edec6670bf8b1","other":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","up":true},{"one":"1b7250dae1f2b07504b381d28084b751a26ac791a1ed842ea5746826ab85ba22","other":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","up":true},{"one":"4a828523731caf47f9aa2dfa20f8a8895e9a6562b3a034574639a229a733b4a6","other":"4a81ee929074bd6d9932fb4d970f5207db8680bda0e951aa72a6bd64df45108d","up":true},{"one":"353875d4ae2b9c5cc6cb14059bdfb9540647100c7c3334bc60db2ca2b4ddf3f5","other":"388d46d6ea3fd92aa959b04d597ff1096d6133f171181c8fb54e52935b62d33c","up":true},{"one":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","other":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","up":true},{"one":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","other":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","up":true},{"one":"b4639751d67db0b32ad1846996fed0d5c59be76b77efaec5f66e64df35eefae8","other":"b45d1aee940e5d2c71db22e2afbe908d5967e437fbb5bd051edea86ea4ac9d30","up":true},{"one":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","other":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","up":true},{"one":"c8fe626c60d6cd15912a22d2c5872f10a8f8aef5b6f6b2f1070edec6670bf8b1","other":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","up":true},{"one":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","other":"5fa8a5a29a47694653a5e55fd9b76e9c4f717ecc9b7088a6ec7ee273b291d49e","up":true},{"one":"4a828523731caf47f9aa2dfa20f8a8895e9a6562b3a034574639a229a733b4a6","other":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","up":true},{"one":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","other":"96b6ec2cda7c9e962bbc97e7916b92d69b112265c4456d30dade806e09fea62e","up":true},{"one":"4a81ee929074bd6d9932fb4d970f5207db8680bda0e951aa72a6bd64df45108d","other":"2013ce63483af2990b35f9ecbe5a7653c34b24ded173753575b24451787ed8b9","up":true},{"one":"265d7fc553fb8dc016d7c6ccc31fc372912a11e9e8da0e5822e371a91d82e857","other":"2fd800d224f124e47038bf140ebd486f1d0ab93169c998d2dfabfd1d3ced96ed","up":true},{"one":"004e812995bc04f22993466df9889ca1b650bba242cf80bb070ce9564e7bedd8","other":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","up":true},{"one":"a485db6ec1929aaf695a84294f52905aa602584954e9ef71b4e18f7b847da31d","other":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","up":true},{"one":"ed1348b4037004b61bd4d6f9be1648112f835943a3b997b91ac931301311e007","other":"edca23c0fccfd1ec66f4d39bb129462776b9091c2337f6afbfe8737a0ebc0d7d","up":true},{"one":"0f81caba7d2f752fc670829f74565114bcf10c5b97d7f93252b467d0eee278b3","other":"237459ba37141f18b3cd40f5ec9cdaf6789fb87aa4511d489fdd98a960ef2883","up":true},{"one":"4a81ee929074bd6d9932fb4d970f5207db8680bda0e951aa72a6bd64df45108d","other":"0f81caba7d2f752fc670829f74565114bcf10c5b97d7f93252b467d0eee278b3","up":true},{"one":"4a828523731caf47f9aa2dfa20f8a8895e9a6562b3a034574639a229a733b4a6","other":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","up":true},{"one":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","other":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","up":true},{"one":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","other":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","up":true},{"one":"e67d72c8c72bbc7bec1d4e936d65adbeab75d1b6cafdf2c39e38bfc723cceebf","other":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","up":true},{"one":"92325e52babbf2b8ae3c37beec7e440d11efed2d103befa2c4a969315a2eab56","other":"88da9dccda89c111dde40318fd1e5239e77b670f5a1b88a3e9320b7350885e31","up":true},{"one":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","other":"1b7250dae1f2b07504b381d28084b751a26ac791a1ed842ea5746826ab85ba22","up":true},{"one":"1d942c85568f640497c46a9bd5a9825bc6fb05571203a3f0f14a3032602d81fc","other":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","up":true},{"one":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","other":"b310299bef4f4e84a1ecc960143e4b1bbe98cd4eb8352458c477569ca9994c20","up":true},{"one":"1a02108d9149739be6ba85ddd1e8a3961adeec38f77fcef95ab57f8f2f39f4bb","other":"1b7250dae1f2b07504b381d28084b751a26ac791a1ed842ea5746826ab85ba22","up":true},{"one":"458a31b8fdbc42f5e80f01118c9dc041740212c09ddfb4b137c9e7e93ae9ec51","other":"571638637156bdaf92d9c715647a1cf64e91877c2d998658b7c6cd27522271bd","up":true},{"one":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","other":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","up":true},{"one":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"29fd4473f1b6c37f8268d73dd5f683a0f7eb64bd793dab2c450c10d8ab0b666d","other":"2279d612454ff034032a5acf5a039477a11479b9b9a93edc26b61312e8d9b156","up":true},{"one":"f1fcfc064446a432ac7a21efc386fa3d053d0ca4540fd4ddbbb253ec11f9ce94","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"3e85d2d3d6750f84500fcf523743e0c242943e69188cb83114adad1787e6c089","other":"043f24ce87fbd01f6f663f323ac6c156810a67b12efbec2f219ae9a882a4bd16","up":true},{"one":"31edba65e539a9a3c7107a131c4fb4b5a8e2c2d4e2db7874a0116c173961e27c","other":"3f3e66f7c8fc7daae8a7a84a9c90f1ea64faa43930f36fc8f11e2d7b751f8ee9","up":true},{"one":"1a02108d9149739be6ba85ddd1e8a3961adeec38f77fcef95ab57f8f2f39f4bb","other":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","up":true},{"one":"79abd33d150fdbcbce6f55f3d101608b80e03ede7e4627bc06ba5c1b001538a4","other":"7294223c0ab27851df8c25418acf09a28efa95fc238252af8a68390db07b77e4","up":true},{"one":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","other":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","up":true},{"one":"aa504681c991c69015c7ef0469902c44fc4e900356b181aa252a1280ffbd2e3b","other":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","up":true},{"one":"a6720426faa1ea7e2d55c1c5ad05ba2e8c534d120ebb5e1375cf769d73191708","other":"abfabc2964ce2f7b8b3702e897a8cc83fb0c009d5af9670b889db582e57ef7c1","up":true},{"one":"043f24ce87fbd01f6f663f323ac6c156810a67b12efbec2f219ae9a882a4bd16","other":"388d46d6ea3fd92aa959b04d597ff1096d6133f171181c8fb54e52935b62d33c","up":true},{"one":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","other":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","up":true},{"one":"9c01d016aaf11f9e7b048ba10cea3c351d9ab5c8c66e8605f78c06772708985c","other":"95e0529227286d442cdeeed428614f99c19f9223426e6f6cefbef2d9d47ce96f","up":true},{"one":"604c6bfab4f5d16d5b786318cf8f9e0197fc18e5d8a8e6f4fd345e653d2abe17","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","other":"14418557af44e33fc8d46369426c3e2fc91622cb08779b3fefce706969ec2163","up":true},{"one":"67831469d7f9b779df4318cda9d4310c53fdd66332af3740e654233ceb3fbcbc","other":"7294223c0ab27851df8c25418acf09a28efa95fc238252af8a68390db07b77e4","up":true},{"one":"89ee628583b6e0345ceb312c2e2cbf5ac410eb00531be903a4b94b60b639993b","other":"86f71d23eb4efeeed4679be7a3d0f58f98b83d23b3e0a8b73f60f1535b51f25c","up":true},{"one":"95e0529227286d442cdeeed428614f99c19f9223426e6f6cefbef2d9d47ce96f","other":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","up":true},{"one":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","other":"5f056f9593be46d3eba4dc8d9e0b93262cd3f00e007c13c4c011d53e7c199628","up":true},{"one":"004e812995bc04f22993466df9889ca1b650bba242cf80bb070ce9564e7bedd8","other":"123fe1d442c7dc905a7206e712fbd9cea640295a6823b39b1945629ffa89e895","up":true},{"one":"c7230d8d56b65a66ab721a8268ed436af78210aae7d068cc2b8afe39b6e61c81","other":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","up":true},{"one":"6544c4a403d9809697420ff9840ea9de0c9ba6491de28dac288db6257f2dc218","other":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","up":true},{"one":"1b869ca5aec1ff0c9980b06b79084019752fd4557054e251c0091ee5d2c7d478","other":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","up":true},{"one":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","other":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","up":true},{"one":"3a4a300de1009d694bebce2362ca429086ade2f551989d55ba459a68efea15af","other":"2af02e62ff3e40be25d22c2f745a505b895b3804a80663cb53db4edd5365c568","up":true},{"one":"6544c4a403d9809697420ff9840ea9de0c9ba6491de28dac288db6257f2dc218","other":"7a4118d2ce16e8258cc2cdf31c5891945e3787c46c0836fc916601584609b7db","up":true},{"one":"67831469d7f9b779df4318cda9d4310c53fdd66332af3740e654233ceb3fbcbc","other":"79abd33d150fdbcbce6f55f3d101608b80e03ede7e4627bc06ba5c1b001538a4","up":true},{"one":"043f24ce87fbd01f6f663f323ac6c156810a67b12efbec2f219ae9a882a4bd16","other":"4d447ea80bc126e57b9fc91203a310c5f4b242df5ad70d5087d16697a626e601","up":true},{"one":"9eec5d5357732a7acd26cc8aa0bc65f6c6069aedf4f3ab4a6790c7fff6bc2695","other":"86128689b03706f9a86baf4753154afbb0c535f84089039ef5610d82fb969a6e","up":true},{"one":"1b869ca5aec1ff0c9980b06b79084019752fd4557054e251c0091ee5d2c7d478","other":"b310299bef4f4e84a1ecc960143e4b1bbe98cd4eb8352458c477569ca9994c20","up":true},{"one":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","other":"0f81caba7d2f752fc670829f74565114bcf10c5b97d7f93252b467d0eee278b3","up":true},{"one":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","other":"99aaaa7c98113955ecc0163943317f6ec08ae755e96c25c9f79ca1f529c60587","up":true},{"one":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","other":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","up":true},{"one":"b45d1aee940e5d2c71db22e2afbe908d5967e437fbb5bd051edea86ea4ac9d30","other":"b71052b072cb98fb79151fd0c6044828e535a0547cace931183c7e4e7f227698","up":true},{"one":"1b1eca7b35086d8b217431c1d6a01fc07dc8bc816196a80015c9e163bed676ff","other":"1835f62fe3889f43ba7b4b577869ec764e96fa9a051f1301fb96795daa8b0755","up":true},{"one":"96b6ec2cda7c9e962bbc97e7916b92d69b112265c4456d30dade806e09fea62e","other":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","up":true},{"one":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","other":"bc08af284ab661dcc1cb23859271590e50a09ff8af8073769c812901ea0b9080","up":true},{"one":"daa2c427e8ecc566e634908fb0d47077d8e79854bf884342c06c4bcb355e80e6","other":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","up":true},{"one":"b71052b072cb98fb79151fd0c6044828e535a0547cace931183c7e4e7f227698","other":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","up":true},{"one":"e67d72c8c72bbc7bec1d4e936d65adbeab75d1b6cafdf2c39e38bfc723cceebf","other":"ed1348b4037004b61bd4d6f9be1648112f835943a3b997b91ac931301311e007","up":true},{"one":"bc08af284ab661dcc1cb23859271590e50a09ff8af8073769c812901ea0b9080","other":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","up":true},{"one":"4019ce1dc667d90a85021b5fbf49ad77c1de7fd3443a40a17bdfaf5175efa1e1","other":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","up":true},{"one":"8163eebe696123817a6d998d7781612931df6fa2fd0293c0d0dd395d031c53f6","other":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","up":true},{"one":"2f2284537629514837d5c2296d2871d8c4c147719f0ddc28ef7c629efd44dd61","other":"2fd800d224f124e47038bf140ebd486f1d0ab93169c998d2dfabfd1d3ced96ed","up":true},{"one":"2e9f7d37106b59d6f411dfa0392325d1fa279fe45c18a101e32ece6716e88946","other":"2f2284537629514837d5c2296d2871d8c4c147719f0ddc28ef7c629efd44dd61","up":true},{"one":"d8b0425e4cb5134ddb2de5c05e0ae9b861bd67409c1446ca228318f79f5dd1f6","other":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","up":true},{"one":"4a81ee929074bd6d9932fb4d970f5207db8680bda0e951aa72a6bd64df45108d","other":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","up":true},{"one":"2af02e62ff3e40be25d22c2f745a505b895b3804a80663cb53db4edd5365c568","other":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","up":true},{"one":"0f81caba7d2f752fc670829f74565114bcf10c5b97d7f93252b467d0eee278b3","other":"0210146e650132dd0ceb8cea779cafc5485822eeeb9fcc17f292af5cef6e9632","up":true},{"one":"e787fd35b438dbae3d0283f7181fc4afbeb6f333201ad4cd1b46bf7094a6a85f","other":"2013ce63483af2990b35f9ecbe5a7653c34b24ded173753575b24451787ed8b9","up":true},{"one":"985c14910d922bbd1556c30620e3ab72170da6553e658dd3806d773a734a1c93","other":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","up":true},{"one":"0210146e650132dd0ceb8cea779cafc5485822eeeb9fcc17f292af5cef6e9632","other":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","up":true},{"one":"458a31b8fdbc42f5e80f01118c9dc041740212c09ddfb4b137c9e7e93ae9ec51","other":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","up":true},{"one":"2af02e62ff3e40be25d22c2f745a505b895b3804a80663cb53db4edd5365c568","other":"1b1eca7b35086d8b217431c1d6a01fc07dc8bc816196a80015c9e163bed676ff","up":true},{"one":"c3f356325eb1e61f2cfd8d80d085fb144c6887e1d710c9d133109b84b823425d","other":"c0d1ac430a1466a1a28cb1aa3d29573ccedb13641ac19e6e615f2a96e8f0950b","up":true},{"one":"67831469d7f9b779df4318cda9d4310c53fdd66332af3740e654233ceb3fbcbc","other":"77ec3a73ce1d597d307e87ea8d026baae2a7df4d288a657f240eaaaae7fdb456","up":true},{"one":"79abd33d150fdbcbce6f55f3d101608b80e03ede7e4627bc06ba5c1b001538a4","other":"7ffed78e11c71eccfdddf6a8e5e367831b26a6b11e4d3b60c5b9fc50f4bcdf33","up":true},{"one":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","other":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","up":true},{"one":"e787fd35b438dbae3d0283f7181fc4afbeb6f333201ad4cd1b46bf7094a6a85f","other":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","up":true},{"one":"34fce2b8208c6c8ed005943b3f2cb9f8923739ce262e2040f7ffc048093ef910","other":"a80b39cf280fa80b323370cfc37e8e3ff38de47d5de445c500f36c8cf2390b53","up":true},{"one":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","other":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","up":true},{"one":"730749922ce3dcd8e249b988dbfb950c15473a2f6fd5cfe2a8126995fb1dc186","other":"4cf655b03778bcb41c32664f13168dc32968354468c8aa699de52b43a2e0dc7d","up":true},{"one":"e787fd35b438dbae3d0283f7181fc4afbeb6f333201ad4cd1b46bf7094a6a85f","other":"03f5f1be5c1ae35469333a5b08622372f65ef8f233746afde56d2647c5273630","up":true},{"one":"5110c7e54a18df6963919866e7f6c0c3a483045d5ac20fc0dfa5e1926681be7f","other":"2f2284537629514837d5c2296d2871d8c4c147719f0ddc28ef7c629efd44dd61","up":true},{"one":"a485db6ec1929aaf695a84294f52905aa602584954e9ef71b4e18f7b847da31d","other":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","up":true},{"one":"72acf53ab7dbda90ea70e78d001fe21e7ab88e4054ff0453212e33a26fbbf7f1","other":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","up":true},{"one":"ed1348b4037004b61bd4d6f9be1648112f835943a3b997b91ac931301311e007","other":"ecd2b502d4e9bf77369c62d1142a9fb4ab1071e751cc6971e9e381e953b0fc12","up":true},{"one":"c4632e164170d35e91fce46f660265f73795f1d840effce5ab2aae18c73003d7","other":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","up":true},{"one":"f4eee71a143722ea8288279876bf5687c1ed65eede0129e987e53f5ec76d8543","other":"fed15b57e749c5f8f5fdb382eb95abe2ce0dee184c817e557122207f3729b05c","up":true},{"one":"5110c7e54a18df6963919866e7f6c0c3a483045d5ac20fc0dfa5e1926681be7f","other":"0f5e34b25f6851d0f3b572c64420de0aec01754bea0854bd7e9910b4b1cc66c3","up":true},{"one":"72acf53ab7dbda90ea70e78d001fe21e7ab88e4054ff0453212e33a26fbbf7f1","other":"5110c7e54a18df6963919866e7f6c0c3a483045d5ac20fc0dfa5e1926681be7f","up":true},{"one":"5110c7e54a18df6963919866e7f6c0c3a483045d5ac20fc0dfa5e1926681be7f","other":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","up":true},{"one":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","other":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","up":true},{"one":"c0d1ac430a1466a1a28cb1aa3d29573ccedb13641ac19e6e615f2a96e8f0950b","other":"c4844b8046d1c47f699ee1c232d945829105d45af26fba722ce4d0a937c99631","up":true},{"one":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","other":"46c5a266dd46041a62f21946290aad736d2298277bbbb16b1efb4490d9d31fa8","up":true},{"one":"2e9f7d37106b59d6f411dfa0392325d1fa279fe45c18a101e32ece6716e88946","other":"29fd4473f1b6c37f8268d73dd5f683a0f7eb64bd793dab2c450c10d8ab0b666d","up":true},{"one":"c63e796efdc44712403199c11c3e5ff28e5d6a4d9ecdf3e95620fbd6afb1bc2d","other":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","up":true},{"one":"a80b39cf280fa80b323370cfc37e8e3ff38de47d5de445c500f36c8cf2390b53","other":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","up":true},{"one":"c63e796efdc44712403199c11c3e5ff28e5d6a4d9ecdf3e95620fbd6afb1bc2d","other":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","up":true},{"one":"c4632e164170d35e91fce46f660265f73795f1d840effce5ab2aae18c73003d7","other":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","up":true},{"one":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","other":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","up":true},{"one":"86f71d23eb4efeeed4679be7a3d0f58f98b83d23b3e0a8b73f60f1535b51f25c","other":"95e0529227286d442cdeeed428614f99c19f9223426e6f6cefbef2d9d47ce96f","up":true},{"one":"9c0c7b6da33520440b181a5fcc30aa8a7576ec9c6f64acef0c9525d05a2ee393","other":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","up":true},{"one":"47f95708475c9d61ff2016e7707c4a3796f6c87313bd1d2bf91aa620113ba5f8","other":"5fa8a5a29a47694653a5e55fd9b76e9c4f717ecc9b7088a6ec7ee273b291d49e","up":true},{"one":"670d1c31407b4f34bb955031e9a3ab7980273155080b459a082687d53a3b42a6","other":"7d45f1e91bcd683d870422b2f591c76f6ab07ff4b079f35c280ce46f7fc364e1","up":true},{"one":"52881f1fe0b8e9044a3bc035edbbd6343b9c41d49c0f2c2ca799c2e764489b91","other":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","up":true},{"one":"bf5ac6f9b651895c8d75ab3ae7994f91981dbbcdce25a7cd718fc996da96946d","other":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","up":true},{"one":"36485615576c68702382f614054494cc4a40b90fd66db86b9d86a6a131dcc6ac","other":"3af3bb6c4343a19e3c0f9a89697cd70a3c79c64b1e40afa59b188c5553eed567","up":true},{"one":"d6f37555e9f35a3ef3b8b4d5c18cf0702ed99c1c8b5b0a8554deddcbe542b88c","other":"c15d13aff7f5694fb8fc2a2154846ac703064133feb632e28830777bbfc7c016","up":true},{"one":"fed15b57e749c5f8f5fdb382eb95abe2ce0dee184c817e557122207f3729b05c","other":"e8392bdce92a7dcfa5e3196a6e1d4e5dc4814c72e104d3e2aa1f1ab99e2a47a6","up":true},{"one":"d6f37555e9f35a3ef3b8b4d5c18cf0702ed99c1c8b5b0a8554deddcbe542b88c","other":"e67d72c8c72bbc7bec1d4e936d65adbeab75d1b6cafdf2c39e38bfc723cceebf","up":true},{"one":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","other":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","up":true},{"one":"4a828523731caf47f9aa2dfa20f8a8895e9a6562b3a034574639a229a733b4a6","other":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","up":true},{"one":"f4eee71a143722ea8288279876bf5687c1ed65eede0129e987e53f5ec76d8543","other":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","up":true},{"one":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","other":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","up":true},{"one":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","other":"ecd2b502d4e9bf77369c62d1142a9fb4ab1071e751cc6971e9e381e953b0fc12","up":true},{"one":"dc3ec6529cec19709403e473d4546ba07f7aaaaec5be7cb0618246a4c0458944","other":"daa2c427e8ecc566e634908fb0d47077d8e79854bf884342c06c4bcb355e80e6","up":true},{"one":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","other":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","up":true},{"one":"9390ef61753fcf9dae5e585a85650287fe1f792538721a2cf767adc44c5e7ce0","other":"ad36b802e9b01727e516879c0055e3c46b810845794f7772ad85e8062111b167","up":true},{"one":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","other":"2f2284537629514837d5c2296d2871d8c4c147719f0ddc28ef7c629efd44dd61","up":true},{"one":"4019ce1dc667d90a85021b5fbf49ad77c1de7fd3443a40a17bdfaf5175efa1e1","other":"4a81ee929074bd6d9932fb4d970f5207db8680bda0e951aa72a6bd64df45108d","up":true},{"one":"e4c3fa0700516da5650f794067eb1c90ba7eaab2b65019bf34552c26f8c1d9a6","other":"ad36b802e9b01727e516879c0055e3c46b810845794f7772ad85e8062111b167","up":true},{"one":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","other":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","up":true},{"one":"5fab772fff883d04cf7e4713266fb1e98fa1746e12faeb04e87bcb37b8b61187","other":"5fd02ea98977d976f5d47a4005300b9865b1dcdac77bf8a98a8920a89b57825c","up":true},{"one":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","other":"c4844b8046d1c47f699ee1c232d945829105d45af26fba722ce4d0a937c99631","up":true},{"one":"89ee628583b6e0345ceb312c2e2cbf5ac410eb00531be903a4b94b60b639993b","other":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","up":true},{"one":"99aaaa7c98113955ecc0163943317f6ec08ae755e96c25c9f79ca1f529c60587","other":"99fba7334f47bfcf21331d6179bfb2eee2ecebd1ea23fd829c542844b10ed974","up":true},{"one":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","other":"f048e37427d68c4134a115cc7df6a8924ea95f24e7f2435372ae331e24b5b7fd","up":true},{"one":"99fba7334f47bfcf21331d6179bfb2eee2ecebd1ea23fd829c542844b10ed974","other":"9eec5d5357732a7acd26cc8aa0bc65f6c6069aedf4f3ab4a6790c7fff6bc2695","up":true},{"one":"487edf32027381256a864bc2e9bca175f7216cc1234731bd4dc90d2cbad6975d","other":"74062d9dd73e26066fd44e4f6c696f271c9dc6860bdc5ba3fcf447f455c8cafa","up":true},{"one":"e4c3fa0700516da5650f794067eb1c90ba7eaab2b65019bf34552c26f8c1d9a6","other":"b4c7555a38d1d4e3bd1d01bcc47a84efd6128e8cc0fd803362da8b341f984a4d","up":true},{"one":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","other":"ed1348b4037004b61bd4d6f9be1648112f835943a3b997b91ac931301311e007","up":true},{"one":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","other":"6544c4a403d9809697420ff9840ea9de0c9ba6491de28dac288db6257f2dc218","up":true},{"one":"c98d389a6d8e6eb5a1eb5a6f235857d5d939d30e51c16d80c039c9e0eb2f7eca","other":"ceee9ac63786ad68d884f5d018248ae1b7b60f0ad434776d29bd4dfcdc0f04c1","up":true},{"one":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","other":"dc3ec6529cec19709403e473d4546ba07f7aaaaec5be7cb0618246a4c0458944","up":true},{"one":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","other":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","up":true},{"one":"3f3e66f7c8fc7daae8a7a84a9c90f1ea64faa43930f36fc8f11e2d7b751f8ee9","other":"211a516fb95d093eb43f4716edeb1e6f76ed79d2aeee3393740e244970187840","up":true},{"one":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","other":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","up":true},{"one":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","other":"7ffed78e11c71eccfdddf6a8e5e367831b26a6b11e4d3b60c5b9fc50f4bcdf33","up":true},{"one":"74062d9dd73e26066fd44e4f6c696f271c9dc6860bdc5ba3fcf447f455c8cafa","other":"77ec3a73ce1d597d307e87ea8d026baae2a7df4d288a657f240eaaaae7fdb456","up":true},{"one":"7471be56a2f617783d5a1ae4390794ad24f453259b7c1f8f65be8f71c8bf8bf7","other":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","up":true},{"one":"ad36b802e9b01727e516879c0055e3c46b810845794f7772ad85e8062111b167","other":"bfeca898b1755a001eb0bcf8f5ad5713a550d0597f99ef7967ea4e7add8d3e81","up":true},{"one":"d6f37555e9f35a3ef3b8b4d5c18cf0702ed99c1c8b5b0a8554deddcbe542b88c","other":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","up":true},{"one":"b79fd26ce97e98e7432029dea6bf11e48ea9e8f2385dec5c60be0cbd51bbef54","other":"b60deba7c8676305b67af41d2de866bddd72edc0667d427e51fc3f2e91412b45","up":true},{"one":"6ea59b849a9754c022219146efc27fa6561e5aca8338a111847259695198267b","other":"7ffed78e11c71eccfdddf6a8e5e367831b26a6b11e4d3b60c5b9fc50f4bcdf33","up":true},{"one":"604c6bfab4f5d16d5b786318cf8f9e0197fc18e5d8a8e6f4fd345e653d2abe17","other":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","up":true},{"one":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","other":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","up":true},{"one":"9fee945c89d6bb59e77a24183c2855f4eece0b5d3266f750ea95483eae1e1871","other":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","up":true},{"one":"ad36b802e9b01727e516879c0055e3c46b810845794f7772ad85e8062111b167","other":"a6720426faa1ea7e2d55c1c5ad05ba2e8c534d120ebb5e1375cf769d73191708","up":true},{"one":"7471be56a2f617783d5a1ae4390794ad24f453259b7c1f8f65be8f71c8bf8bf7","other":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","up":true},{"one":"487edf32027381256a864bc2e9bca175f7216cc1234731bd4dc90d2cbad6975d","other":"4f90f53754ee50357491dbab7c3e6748b40133e3339920b32f4c3457f4258477","up":true},{"one":"06aa2f43956f978abe7b8f8f78a5a8cd59decb66ec54b7c81ae50c3752798c5e","other":"0f5e34b25f6851d0f3b572c64420de0aec01754bea0854bd7e9910b4b1cc66c3","up":true},{"one":"9c0c7b6da33520440b181a5fcc30aa8a7576ec9c6f64acef0c9525d05a2ee393","other":"985c14910d922bbd1556c30620e3ab72170da6553e658dd3806d773a734a1c93","up":true},{"one":"9fee945c89d6bb59e77a24183c2855f4eece0b5d3266f750ea95483eae1e1871","other":"c7230d8d56b65a66ab721a8268ed436af78210aae7d068cc2b8afe39b6e61c81","up":true},{"one":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","other":"f5dd00d94488f3387d64ccc2115cd91e5073c06de36d195a5c1a95db23cd5c68","up":true},{"one":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","other":"66100e4f9d103d935164f39ea3f0332708f3825ebcf31ac6f8c9714c75a50860","up":true},{"one":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","other":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","up":true},{"one":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","other":"def4f0b988942a8699f6b500cc2531c7dea065e3dc5bdcbeb26bca8f00e1d3a6","up":true},{"one":"06aa2f43956f978abe7b8f8f78a5a8cd59decb66ec54b7c81ae50c3752798c5e","other":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","up":true},{"one":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","other":"def4f0b988942a8699f6b500cc2531c7dea065e3dc5bdcbeb26bca8f00e1d3a6","up":true},{"one":"4a670fe61711307f7781b3b5a8014fc0e63866efe356a25e61b2e768a7389980","other":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","up":true},{"one":"4cf655b03778bcb41c32664f13168dc32968354468c8aa699de52b43a2e0dc7d","other":"4a81ee929074bd6d9932fb4d970f5207db8680bda0e951aa72a6bd64df45108d","up":true},{"one":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","other":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","up":true},{"one":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","other":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","up":true},{"one":"86128689b03706f9a86baf4753154afbb0c535f84089039ef5610d82fb969a6e","other":"123fe1d442c7dc905a7206e712fbd9cea640295a6823b39b1945629ffa89e895","up":true},{"one":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","other":"2f2284537629514837d5c2296d2871d8c4c147719f0ddc28ef7c629efd44dd61","up":true},{"one":"f048e37427d68c4134a115cc7df6a8924ea95f24e7f2435372ae331e24b5b7fd","other":"c8fe626c60d6cd15912a22d2c5872f10a8f8aef5b6f6b2f1070edec6670bf8b1","up":true},{"one":"abfabc2964ce2f7b8b3702e897a8cc83fb0c009d5af9670b889db582e57ef7c1","other":"ae715a40e9bf50b4eb954c6b87efbf1a6e7abc3da6d84055e4591aadf5bad5c8","up":true},{"one":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","other":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","up":true},{"one":"3af3bb6c4343a19e3c0f9a89697cd70a3c79c64b1e40afa59b188c5553eed567","other":"259d3874697a0c3192f973c620880b2a86567289f5222d07eb91a51867352559","up":true},{"one":"237459ba37141f18b3cd40f5ec9cdaf6789fb87aa4511d489fdd98a960ef2883","other":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","up":true},{"one":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"abfabc2964ce2f7b8b3702e897a8cc83fb0c009d5af9670b889db582e57ef7c1","other":"af358f626738d2e5d17060abbaf24cb0124a48c5edb8f3baeda30bea7b403dfa","up":true},{"one":"03f5f1be5c1ae35469333a5b08622372f65ef8f233746afde56d2647c5273630","other":"0210146e650132dd0ceb8cea779cafc5485822eeeb9fcc17f292af5cef6e9632","up":true},{"one":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","other":"2af02e62ff3e40be25d22c2f745a505b895b3804a80663cb53db4edd5365c568","up":true},{"one":"3af3bb6c4343a19e3c0f9a89697cd70a3c79c64b1e40afa59b188c5553eed567","other":"2f9f266a3b660fe685de2932de82979bd221706db1e2b954a93edc6609c378d9","up":true},{"one":"0ea201515867c6793cbe48350f209e69acd3829b9b16b6016d965d84fb6379c5","other":"0210146e650132dd0ceb8cea779cafc5485822eeeb9fcc17f292af5cef6e9632","up":true},{"one":"237459ba37141f18b3cd40f5ec9cdaf6789fb87aa4511d489fdd98a960ef2883","other":"4a81ee929074bd6d9932fb4d970f5207db8680bda0e951aa72a6bd64df45108d","up":true},{"one":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","other":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","up":true},{"one":"ae715a40e9bf50b4eb954c6b87efbf1a6e7abc3da6d84055e4591aadf5bad5c8","other":"b4c7555a38d1d4e3bd1d01bcc47a84efd6128e8cc0fd803362da8b341f984a4d","up":true},{"one":"ad36b802e9b01727e516879c0055e3c46b810845794f7772ad85e8062111b167","other":"ae715a40e9bf50b4eb954c6b87efbf1a6e7abc3da6d84055e4591aadf5bad5c8","up":true},{"one":"1d942c85568f640497c46a9bd5a9825bc6fb05571203a3f0f14a3032602d81fc","other":"3af3bb6c4343a19e3c0f9a89697cd70a3c79c64b1e40afa59b188c5553eed567","up":true},{"one":"d3d254a3024a7aac01e97a5f0aff13756741867e599bc94c068f0d1d19ca8176","other":"dc3ec6529cec19709403e473d4546ba07f7aaaaec5be7cb0618246a4c0458944","up":true},{"one":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","other":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","up":true},{"one":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","other":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","up":true},{"one":"cb69f673a54a75de300d1d1ab989caa04579504cbcaf78f03bc192c9578ec5a0","other":"c8fe626c60d6cd15912a22d2c5872f10a8f8aef5b6f6b2f1070edec6670bf8b1","up":true},{"one":"3af3bb6c4343a19e3c0f9a89697cd70a3c79c64b1e40afa59b188c5553eed567","other":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","up":true},{"one":"1d942c85568f640497c46a9bd5a9825bc6fb05571203a3f0f14a3032602d81fc","other":"3dca1ae488cc497fe738c630465f987b419a5bb259fdfa17fe7130e238ca3022","up":true},{"one":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","other":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","up":true},{"one":"99aaaa7c98113955ecc0163943317f6ec08ae755e96c25c9f79ca1f529c60587","other":"9a82d22b29c88eaca2f20a62e9e63675904065e72c70ce2fbbefc0c9d2e53e90","up":true},{"one":"ceee9ac63786ad68d884f5d018248ae1b7b60f0ad434776d29bd4dfcdc0f04c1","other":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","up":true},{"one":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","other":"2f2284537629514837d5c2296d2871d8c4c147719f0ddc28ef7c629efd44dd61","up":true},{"one":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","other":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","up":true},{"one":"237459ba37141f18b3cd40f5ec9cdaf6789fb87aa4511d489fdd98a960ef2883","other":"4a828523731caf47f9aa2dfa20f8a8895e9a6562b3a034574639a229a733b4a6","up":true},{"one":"9eec5d5357732a7acd26cc8aa0bc65f6c6069aedf4f3ab4a6790c7fff6bc2695","other":"86f71d23eb4efeeed4679be7a3d0f58f98b83d23b3e0a8b73f60f1535b51f25c","up":true},{"one":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","other":"d8b0425e4cb5134ddb2de5c05e0ae9b861bd67409c1446ca228318f79f5dd1f6","up":true},{"one":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","other":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","up":true},{"one":"6d219b0ea4a1620a7ba051cc478da6df9ea443e87dde79e963ec789a5bad7459","other":"7d941474f90981a03c95af4cf68be163d01a317e202ac0db82e25f46fc3db8aa","up":true},{"one":"7a4118d2ce16e8258cc2cdf31c5891945e3787c46c0836fc916601584609b7db","other":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","up":true},{"one":"9a82d22b29c88eaca2f20a62e9e63675904065e72c70ce2fbbefc0c9d2e53e90","other":"96b6ec2cda7c9e962bbc97e7916b92d69b112265c4456d30dade806e09fea62e","up":true},{"one":"42c019ddad2fde0f0c7f5041eb584996976844f8b2e08e20458437578d0257ef","other":"5fa8a5a29a47694653a5e55fd9b76e9c4f717ecc9b7088a6ec7ee273b291d49e","up":true},{"one":"f048e37427d68c4134a115cc7df6a8924ea95f24e7f2435372ae331e24b5b7fd","other":"edca23c0fccfd1ec66f4d39bb129462776b9091c2337f6afbfe8737a0ebc0d7d","up":true},{"one":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","other":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","up":true},{"one":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","other":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","up":true},{"one":"42c019ddad2fde0f0c7f5041eb584996976844f8b2e08e20458437578d0257ef","other":"5f056f9593be46d3eba4dc8d9e0b93262cd3f00e007c13c4c011d53e7c199628","up":true},{"one":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","other":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","up":true},{"one":"bc08af284ab661dcc1cb23859271590e50a09ff8af8073769c812901ea0b9080","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"c7703c4697637f40a50a5d0b656c4e54010ef1277b64d251ccd0752512e246e9","other":"cb69f673a54a75de300d1d1ab989caa04579504cbcaf78f03bc192c9578ec5a0","up":true},{"one":"daa2c427e8ecc566e634908fb0d47077d8e79854bf884342c06c4bcb355e80e6","other":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","up":true},{"one":"237459ba37141f18b3cd40f5ec9cdaf6789fb87aa4511d489fdd98a960ef2883","other":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","up":true},{"one":"3dca1ae488cc497fe738c630465f987b419a5bb259fdfa17fe7130e238ca3022","other":"237459ba37141f18b3cd40f5ec9cdaf6789fb87aa4511d489fdd98a960ef2883","up":true},{"one":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","other":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","up":true},{"one":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","other":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","up":true},{"one":"167306fb0e24ddd775c2ac0722103ce5db98bb0ef60159b91f584475d34cb5bf","other":"13d8107c669cc27f6cbc64cef6b2bd11f72db297f2857c2de7323b92632b8231","up":true},{"one":"43afd835930eac91c0c8947001dd60d8da9112c19cf3f1356589b58b48962166","other":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","up":true},{"one":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","other":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","up":true},{"one":"3dca1ae488cc497fe738c630465f987b419a5bb259fdfa17fe7130e238ca3022","other":"2013ce63483af2990b35f9ecbe5a7653c34b24ded173753575b24451787ed8b9","up":true},{"one":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","other":"af358f626738d2e5d17060abbaf24cb0124a48c5edb8f3baeda30bea7b403dfa","up":true},{"one":"31edba65e539a9a3c7107a131c4fb4b5a8e2c2d4e2db7874a0116c173961e27c","other":"36485615576c68702382f614054494cc4a40b90fd66db86b9d86a6a131dcc6ac","up":true},{"one":"2af02e62ff3e40be25d22c2f745a505b895b3804a80663cb53db4edd5365c568","other":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","up":true},{"one":"daa2c427e8ecc566e634908fb0d47077d8e79854bf884342c06c4bcb355e80e6","other":"ed1348b4037004b61bd4d6f9be1648112f835943a3b997b91ac931301311e007","up":true},{"one":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","other":"03f5f1be5c1ae35469333a5b08622372f65ef8f233746afde56d2647c5273630","up":true},{"one":"86128689b03706f9a86baf4753154afbb0c535f84089039ef5610d82fb969a6e","other":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","up":true},{"one":"2af02e62ff3e40be25d22c2f745a505b895b3804a80663cb53db4edd5365c568","other":"c4632e164170d35e91fce46f660265f73795f1d840effce5ab2aae18c73003d7","up":true},{"one":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","other":"4fd6a4b99b1ba76775594673421c8eea175c114519c8554798c2c364175bbaae","up":true},{"one":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","other":"99fba7334f47bfcf21331d6179bfb2eee2ecebd1ea23fd829c542844b10ed974","up":true},{"one":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","other":"0592048ae4e6a3000f36e8e1c0e1328c26c0d4bb6369d6cd867a08d847dacb9e","up":true},{"one":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","other":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","up":true},{"one":"3dca1ae488cc497fe738c630465f987b419a5bb259fdfa17fe7130e238ca3022","other":"290fca7596740129fd1a3075bbf56885d8c83381f981adb0fb283799375a3bd5","up":true},{"one":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","other":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","up":true},{"one":"1b869ca5aec1ff0c9980b06b79084019752fd4557054e251c0091ee5d2c7d478","other":"03f5f1be5c1ae35469333a5b08622372f65ef8f233746afde56d2647c5273630","up":true},{"one":"2af02e62ff3e40be25d22c2f745a505b895b3804a80663cb53db4edd5365c568","other":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","up":true},{"one":"5fa8a5a29a47694653a5e55fd9b76e9c4f717ecc9b7088a6ec7ee273b291d49e","other":"5fd02ea98977d976f5d47a4005300b9865b1dcdac77bf8a98a8920a89b57825c","up":true},{"one":"96b6ec2cda7c9e962bbc97e7916b92d69b112265c4456d30dade806e09fea62e","other":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","up":true},{"one":"7dc63c8146ed092aa4c9539abd112dc3a703631e364a62fd74b192eee5778123","other":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","up":true},{"one":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","other":"b71052b072cb98fb79151fd0c6044828e535a0547cace931183c7e4e7f227698","up":true},{"one":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","other":"353875d4ae2b9c5cc6cb14059bdfb9540647100c7c3334bc60db2ca2b4ddf3f5","up":true},{"one":"ceee9ac63786ad68d884f5d018248ae1b7b60f0ad434776d29bd4dfcdc0f04c1","other":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","up":true},{"one":"52619a523050aa8caa32448e59721a76dc3321dc92cfcb1b05f1cec5b79d424a","other":"5f056f9593be46d3eba4dc8d9e0b93262cd3f00e007c13c4c011d53e7c199628","up":true},{"one":"99dbf361b67d6c370fd4f201b059e93da50dd88cde2e25d0bcf3c73afbf13535","other":"99fba7334f47bfcf21331d6179bfb2eee2ecebd1ea23fd829c542844b10ed974","up":true},{"one":"66100e4f9d103d935164f39ea3f0332708f3825ebcf31ac6f8c9714c75a50860","other":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","up":true},{"one":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","other":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","up":true},{"one":"396b256b223f346a490844895b9f1046b4790d624af1c43ecae1de318c7214a8","other":"3a4a300de1009d694bebce2362ca429086ade2f551989d55ba459a68efea15af","up":true},{"one":"1b1eca7b35086d8b217431c1d6a01fc07dc8bc816196a80015c9e163bed676ff","other":"1b869ca5aec1ff0c9980b06b79084019752fd4557054e251c0091ee5d2c7d478","up":true},{"one":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","other":"95e0529227286d442cdeeed428614f99c19f9223426e6f6cefbef2d9d47ce96f","up":true},{"one":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","other":"def4f0b988942a8699f6b500cc2531c7dea065e3dc5bdcbeb26bca8f00e1d3a6","up":true},{"one":"043f24ce87fbd01f6f663f323ac6c156810a67b12efbec2f219ae9a882a4bd16","other":"14418557af44e33fc8d46369426c3e2fc91622cb08779b3fefce706969ec2163","up":true},{"one":"4a81ee929074bd6d9932fb4d970f5207db8680bda0e951aa72a6bd64df45108d","other":"7a4118d2ce16e8258cc2cdf31c5891945e3787c46c0836fc916601584609b7db","up":true},{"one":"9c01d016aaf11f9e7b048ba10cea3c351d9ab5c8c66e8605f78c06772708985c","other":"89ee628583b6e0345ceb312c2e2cbf5ac410eb00531be903a4b94b60b639993b","up":true},{"one":"ceee9ac63786ad68d884f5d018248ae1b7b60f0ad434776d29bd4dfcdc0f04c1","other":"c8fe626c60d6cd15912a22d2c5872f10a8f8aef5b6f6b2f1070edec6670bf8b1","up":true},{"one":"9c01d016aaf11f9e7b048ba10cea3c351d9ab5c8c66e8605f78c06772708985c","other":"86f71d23eb4efeeed4679be7a3d0f58f98b83d23b3e0a8b73f60f1535b51f25c","up":true},{"one":"396b256b223f346a490844895b9f1046b4790d624af1c43ecae1de318c7214a8","other":"2af02e62ff3e40be25d22c2f745a505b895b3804a80663cb53db4edd5365c568","up":true},{"one":"1b1eca7b35086d8b217431c1d6a01fc07dc8bc816196a80015c9e163bed676ff","other":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","up":true},{"one":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","other":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","up":true},{"one":"9a82d22b29c88eaca2f20a62e9e63675904065e72c70ce2fbbefc0c9d2e53e90","other":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","up":true},{"one":"353875d4ae2b9c5cc6cb14059bdfb9540647100c7c3334bc60db2ca2b4ddf3f5","other":"66100e4f9d103d935164f39ea3f0332708f3825ebcf31ac6f8c9714c75a50860","up":true},{"one":"bc08af284ab661dcc1cb23859271590e50a09ff8af8073769c812901ea0b9080","other":"b45d1aee940e5d2c71db22e2afbe908d5967e437fbb5bd051edea86ea4ac9d30","up":true},{"one":"d8b0425e4cb5134ddb2de5c05e0ae9b861bd67409c1446ca228318f79f5dd1f6","other":"daa2c427e8ecc566e634908fb0d47077d8e79854bf884342c06c4bcb355e80e6","up":true},{"one":"004e812995bc04f22993466df9889ca1b650bba242cf80bb070ce9564e7bedd8","other":"043f24ce87fbd01f6f663f323ac6c156810a67b12efbec2f219ae9a882a4bd16","up":true},{"one":"4a81ee929074bd6d9932fb4d970f5207db8680bda0e951aa72a6bd64df45108d","other":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","up":true},{"one":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","other":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","up":true},{"one":"3dca1ae488cc497fe738c630465f987b419a5bb259fdfa17fe7130e238ca3022","other":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","up":true},{"one":"123fe1d442c7dc905a7206e712fbd9cea640295a6823b39b1945629ffa89e895","other":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","up":true},{"one":"be0ab3ddc656568900d8e1f42394cc942464596b7565db40f6c5a0c24ee942f2","other":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","up":true},{"one":"21682cd736edfb8b451dfd9c55eb9ea3b7cf2e35cad32537f1f4800644fbf9a7","other":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","up":true},{"one":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","other":"5110c7e54a18df6963919866e7f6c0c3a483045d5ac20fc0dfa5e1926681be7f","up":true},{"one":"237459ba37141f18b3cd40f5ec9cdaf6789fb87aa4511d489fdd98a960ef2883","other":"265d7fc553fb8dc016d7c6ccc31fc372912a11e9e8da0e5822e371a91d82e857","up":true},{"one":"adfc03118406bc5af4e3f3c6c89c3448c3fe4f9df20b15bcca9bbacf58ee5457","other":"ae715a40e9bf50b4eb954c6b87efbf1a6e7abc3da6d84055e4591aadf5bad5c8","up":true},{"one":"79abd33d150fdbcbce6f55f3d101608b80e03ede7e4627bc06ba5c1b001538a4","other":"77ec3a73ce1d597d307e87ea8d026baae2a7df4d288a657f240eaaaae7fdb456","up":true},{"one":"aa504681c991c69015c7ef0469902c44fc4e900356b181aa252a1280ffbd2e3b","other":"b4c7555a38d1d4e3bd1d01bcc47a84efd6128e8cc0fd803362da8b341f984a4d","up":true},{"one":"79abd33d150fdbcbce6f55f3d101608b80e03ede7e4627bc06ba5c1b001538a4","other":"74062d9dd73e26066fd44e4f6c696f271c9dc6860bdc5ba3fcf447f455c8cafa","up":true},{"one":"1b7250dae1f2b07504b381d28084b751a26ac791a1ed842ea5746826ab85ba22","other":"31edba65e539a9a3c7107a131c4fb4b5a8e2c2d4e2db7874a0116c173961e27c","up":true},{"one":"1b7250dae1f2b07504b381d28084b751a26ac791a1ed842ea5746826ab85ba22","other":"353875d4ae2b9c5cc6cb14059bdfb9540647100c7c3334bc60db2ca2b4ddf3f5","up":true},{"one":"1a02108d9149739be6ba85ddd1e8a3961adeec38f77fcef95ab57f8f2f39f4bb","other":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","up":true},{"one":"458a31b8fdbc42f5e80f01118c9dc041740212c09ddfb4b137c9e7e93ae9ec51","other":"487edf32027381256a864bc2e9bca175f7216cc1234731bd4dc90d2cbad6975d","up":true},{"one":"c3f356325eb1e61f2cfd8d80d085fb144c6887e1d710c9d133109b84b823425d","other":"d68f504755b48f8d41570169dd3544ef1caea2ab1bb334f8d8c5fb27fe816f01","up":true},{"one":"29fd4473f1b6c37f8268d73dd5f683a0f7eb64bd793dab2c450c10d8ab0b666d","other":"3f3e66f7c8fc7daae8a7a84a9c90f1ea64faa43930f36fc8f11e2d7b751f8ee9","up":true},{"one":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","other":"2279d612454ff034032a5acf5a039477a11479b9b9a93edc26b61312e8d9b156","up":true},{"one":"67831469d7f9b779df4318cda9d4310c53fdd66332af3740e654233ceb3fbcbc","other":"7d45f1e91bcd683d870422b2f591c76f6ab07ff4b079f35c280ce46f7fc364e1","up":true},{"one":"e787fd35b438dbae3d0283f7181fc4afbeb6f333201ad4cd1b46bf7094a6a85f","other":"ed1348b4037004b61bd4d6f9be1648112f835943a3b997b91ac931301311e007","up":true},{"one":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","other":"3f3e66f7c8fc7daae8a7a84a9c90f1ea64faa43930f36fc8f11e2d7b751f8ee9","up":true},{"one":"29fd4473f1b6c37f8268d73dd5f683a0f7eb64bd793dab2c450c10d8ab0b666d","other":"275c93677aefd92ab38f9e5b5b4d29f90e809ccaf77b088102071aba26421f3e","up":true},{"one":"458a31b8fdbc42f5e80f01118c9dc041740212c09ddfb4b137c9e7e93ae9ec51","other":"46c5a266dd46041a62f21946290aad736d2298277bbbb16b1efb4490d9d31fa8","up":true},{"one":"c3f356325eb1e61f2cfd8d80d085fb144c6887e1d710c9d133109b84b823425d","other":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","up":true},{"one":"67831469d7f9b779df4318cda9d4310c53fdd66332af3740e654233ceb3fbcbc","other":"571638637156bdaf92d9c715647a1cf64e91877c2d998658b7c6cd27522271bd","up":true},{"one":"b4c7555a38d1d4e3bd1d01bcc47a84efd6128e8cc0fd803362da8b341f984a4d","other":"b79fd26ce97e98e7432029dea6bf11e48ea9e8f2385dec5c60be0cbd51bbef54","up":true},{"one":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","other":"36485615576c68702382f614054494cc4a40b90fd66db86b9d86a6a131dcc6ac","up":true},{"one":"c7230d8d56b65a66ab721a8268ed436af78210aae7d068cc2b8afe39b6e61c81","other":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","up":true},{"one":"bfeca898b1755a001eb0bcf8f5ad5713a550d0597f99ef7967ea4e7add8d3e81","other":"167306fb0e24ddd775c2ac0722103ce5db98bb0ef60159b91f584475d34cb5bf","up":true},{"one":"1b7250dae1f2b07504b381d28084b751a26ac791a1ed842ea5746826ab85ba22","other":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","up":true},{"one":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","other":"4d447ea80bc126e57b9fc91203a310c5f4b242df5ad70d5087d16697a626e601","up":true},{"one":"36485615576c68702382f614054494cc4a40b90fd66db86b9d86a6a131dcc6ac","other":"2f9f266a3b660fe685de2932de82979bd221706db1e2b954a93edc6609c378d9","up":true},{"one":"bfeca898b1755a001eb0bcf8f5ad5713a550d0597f99ef7967ea4e7add8d3e81","other":"18f9292fbe6e94ec3df4d0a896c71ed2b1771437e42d8475a7aae63c7992834d","up":true},{"one":"9eec5d5357732a7acd26cc8aa0bc65f6c6069aedf4f3ab4a6790c7fff6bc2695","other":"9a82d22b29c88eaca2f20a62e9e63675904065e72c70ce2fbbefc0c9d2e53e90","up":true},{"one":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","other":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","up":true},{"one":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","other":"123fe1d442c7dc905a7206e712fbd9cea640295a6823b39b1945629ffa89e895","up":true},{"one":"259d3874697a0c3192f973c620880b2a86567289f5222d07eb91a51867352559","other":"265d7fc553fb8dc016d7c6ccc31fc372912a11e9e8da0e5822e371a91d82e857","up":true},{"one":"88da9dccda89c111dde40318fd1e5239e77b670f5a1b88a3e9320b7350885e31","other":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","up":true},{"one":"14418557af44e33fc8d46369426c3e2fc91622cb08779b3fefce706969ec2163","other":"1b1eca7b35086d8b217431c1d6a01fc07dc8bc816196a80015c9e163bed676ff","up":true},{"one":"a80b39cf280fa80b323370cfc37e8e3ff38de47d5de445c500f36c8cf2390b53","other":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","up":true},{"one":"13d8107c669cc27f6cbc64cef6b2bd11f72db297f2857c2de7323b92632b8231","other":"15f6bb0640c3ac8ae8ecf10d0d46cc739bb6f86b0c0cf706103aad9f3785219b","up":true},{"one":"2f2284537629514837d5c2296d2871d8c4c147719f0ddc28ef7c629efd44dd61","other":"290fca7596740129fd1a3075bbf56885d8c83381f981adb0fb283799375a3bd5","up":true},{"one":"36485615576c68702382f614054494cc4a40b90fd66db86b9d86a6a131dcc6ac","other":"3f3e66f7c8fc7daae8a7a84a9c90f1ea64faa43930f36fc8f11e2d7b751f8ee9","up":true},{"one":"13d8107c669cc27f6cbc64cef6b2bd11f72db297f2857c2de7323b92632b8231","other":"1b869ca5aec1ff0c9980b06b79084019752fd4557054e251c0091ee5d2c7d478","up":true},{"one":"ed6570e63fc0e2e2d6290645c4339b70a0f1c14064d286b4e0ba143607bd70c8","other":"c7230d8d56b65a66ab721a8268ed436af78210aae7d068cc2b8afe39b6e61c81","up":true},{"one":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","other":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","up":true},{"one":"a749372cccb7001a480b9416c41305bd1d3bc417e15669ae587d4375f2bbcbf7","other":"bfeca898b1755a001eb0bcf8f5ad5713a550d0597f99ef7967ea4e7add8d3e81","up":true},{"one":"211a516fb95d093eb43f4716edeb1e6f76ed79d2aeee3393740e244970187840","other":"265d7fc553fb8dc016d7c6ccc31fc372912a11e9e8da0e5822e371a91d82e857","up":true},{"one":"c99c6a2a2fe7f4613c8d918404c956eae0a00fce544c9eeba5da640e5e958874","other":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","up":true},{"one":"2279d612454ff034032a5acf5a039477a11479b9b9a93edc26b61312e8d9b156","other":"265d7fc553fb8dc016d7c6ccc31fc372912a11e9e8da0e5822e371a91d82e857","up":true},{"one":"a749372cccb7001a480b9416c41305bd1d3bc417e15669ae587d4375f2bbcbf7","other":"ad36b802e9b01727e516879c0055e3c46b810845794f7772ad85e8062111b167","up":true},{"one":"c3f356325eb1e61f2cfd8d80d085fb144c6887e1d710c9d133109b84b823425d","other":"c63e796efdc44712403199c11c3e5ff28e5d6a4d9ecdf3e95620fbd6afb1bc2d","up":true},{"one":"7ffed78e11c71eccfdddf6a8e5e367831b26a6b11e4d3b60c5b9fc50f4bcdf33","other":"77ec3a73ce1d597d307e87ea8d026baae2a7df4d288a657f240eaaaae7fdb456","up":true},{"one":"9390ef61753fcf9dae5e585a85650287fe1f792538721a2cf767adc44c5e7ce0","other":"985c14910d922bbd1556c30620e3ab72170da6553e658dd3806d773a734a1c93","up":true},{"one":"ed6570e63fc0e2e2d6290645c4339b70a0f1c14064d286b4e0ba143607bd70c8","other":"f5dd00d94488f3387d64ccc2115cd91e5073c06de36d195a5c1a95db23cd5c68","up":true},{"one":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","other":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","up":true},{"one":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","other":"46c5a266dd46041a62f21946290aad736d2298277bbbb16b1efb4490d9d31fa8","up":true},{"one":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","other":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","up":true},{"one":"670d1c31407b4f34bb955031e9a3ab7980273155080b459a082687d53a3b42a6","other":"7ffed78e11c71eccfdddf6a8e5e367831b26a6b11e4d3b60c5b9fc50f4bcdf33","up":true},{"one":"2fd800d224f124e47038bf140ebd486f1d0ab93169c998d2dfabfd1d3ced96ed","other":"2e9f7d37106b59d6f411dfa0392325d1fa279fe45c18a101e32ece6716e88946","up":true},{"one":"275c93677aefd92ab38f9e5b5b4d29f90e809ccaf77b088102071aba26421f3e","other":"265d7fc553fb8dc016d7c6ccc31fc372912a11e9e8da0e5822e371a91d82e857","up":true},{"one":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","other":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","up":true},{"one":"4a828523731caf47f9aa2dfa20f8a8895e9a6562b3a034574639a229a733b4a6","other":"5fd02ea98977d976f5d47a4005300b9865b1dcdac77bf8a98a8920a89b57825c","up":true},{"one":"a749372cccb7001a480b9416c41305bd1d3bc417e15669ae587d4375f2bbcbf7","other":"b4c7555a38d1d4e3bd1d01bcc47a84efd6128e8cc0fd803362da8b341f984a4d","up":true},{"one":"79abd33d150fdbcbce6f55f3d101608b80e03ede7e4627bc06ba5c1b001538a4","other":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","up":true},{"one":"bf5ac6f9b651895c8d75ab3ae7994f91981dbbcdce25a7cd718fc996da96946d","other":"a749372cccb7001a480b9416c41305bd1d3bc417e15669ae587d4375f2bbcbf7","up":true},{"one":"c99c6a2a2fe7f4613c8d918404c956eae0a00fce544c9eeba5da640e5e958874","other":"c7230d8d56b65a66ab721a8268ed436af78210aae7d068cc2b8afe39b6e61c81","up":true},{"one":"670d1c31407b4f34bb955031e9a3ab7980273155080b459a082687d53a3b42a6","other":"77ec3a73ce1d597d307e87ea8d026baae2a7df4d288a657f240eaaaae7fdb456","up":true},{"one":"fed15b57e749c5f8f5fdb382eb95abe2ce0dee184c817e557122207f3729b05c","other":"f4e0d91ca58ed616918bf268565c2edb4176293e851b8d29cbbda55bdc608534","up":true},{"one":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","other":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","up":true},{"one":"29fd4473f1b6c37f8268d73dd5f683a0f7eb64bd793dab2c450c10d8ab0b666d","other":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","up":true},{"one":"67831469d7f9b779df4318cda9d4310c53fdd66332af3740e654233ceb3fbcbc","other":"6ea59b849a9754c022219146efc27fa6561e5aca8338a111847259695198267b","up":true},{"one":"14418557af44e33fc8d46369426c3e2fc91622cb08779b3fefce706969ec2163","other":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","up":true},{"one":"e8392bdce92a7dcfa5e3196a6e1d4e5dc4814c72e104d3e2aa1f1ab99e2a47a6","other":"ed1348b4037004b61bd4d6f9be1648112f835943a3b997b91ac931301311e007","up":true},{"one":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","other":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","up":true},{"one":"e8392bdce92a7dcfa5e3196a6e1d4e5dc4814c72e104d3e2aa1f1ab99e2a47a6","other":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","up":true},{"one":"5110c7e54a18df6963919866e7f6c0c3a483045d5ac20fc0dfa5e1926681be7f","other":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","up":true},{"one":"46c5a266dd46041a62f21946290aad736d2298277bbbb16b1efb4490d9d31fa8","other":"4a670fe61711307f7781b3b5a8014fc0e63866efe356a25e61b2e768a7389980","up":true},{"one":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","other":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","up":true},{"one":"21682cd736edfb8b451dfd9c55eb9ea3b7cf2e35cad32537f1f4800644fbf9a7","other":"245495250d38095106022d3fc905eb7eb6f54adb2cb0bacdda0b305974eaa50a","up":true},{"one":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","other":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","up":true},{"one":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","other":"730749922ce3dcd8e249b988dbfb950c15473a2f6fd5cfe2a8126995fb1dc186","up":true},{"one":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","other":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","up":true},{"one":"2e9f7d37106b59d6f411dfa0392325d1fa279fe45c18a101e32ece6716e88946","other":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","up":true},{"one":"5fab772fff883d04cf7e4713266fb1e98fa1746e12faeb04e87bcb37b8b61187","other":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","up":true},{"one":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","other":"29fd4473f1b6c37f8268d73dd5f683a0f7eb64bd793dab2c450c10d8ab0b666d","up":true},{"one":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","other":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","up":true},{"one":"2fd800d224f124e47038bf140ebd486f1d0ab93169c998d2dfabfd1d3ced96ed","other":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","up":true},{"one":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","other":"abfabc2964ce2f7b8b3702e897a8cc83fb0c009d5af9670b889db582e57ef7c1","up":true},{"one":"47f95708475c9d61ff2016e7707c4a3796f6c87313bd1d2bf91aa620113ba5f8","other":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","up":true},{"one":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","other":"9a82d22b29c88eaca2f20a62e9e63675904065e72c70ce2fbbefc0c9d2e53e90","up":true},{"one":"99fba7334f47bfcf21331d6179bfb2eee2ecebd1ea23fd829c542844b10ed974","other":"9c01d016aaf11f9e7b048ba10cea3c351d9ab5c8c66e8605f78c06772708985c","up":true},{"one":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","other":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","up":true},{"one":"47f95708475c9d61ff2016e7707c4a3796f6c87313bd1d2bf91aa620113ba5f8","other":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","up":true},{"one":"47f95708475c9d61ff2016e7707c4a3796f6c87313bd1d2bf91aa620113ba5f8","other":"6544c4a403d9809697420ff9840ea9de0c9ba6491de28dac288db6257f2dc218","up":true},{"one":"1d5fc65b4121cccaff0d2a853c6bbff0c10b4ec28cfc5ef5cbea8bc28bc4cbec","other":"043f24ce87fbd01f6f663f323ac6c156810a67b12efbec2f219ae9a882a4bd16","up":true},{"one":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","other":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","up":true},{"one":"7854fb9f0d2f016653994b9e23033f27e21a2ef186a352c8d068b75ea20beec2","other":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","up":true},{"one":"def4f0b988942a8699f6b500cc2531c7dea065e3dc5bdcbeb26bca8f00e1d3a6","other":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","up":true},{"one":"290fca7596740129fd1a3075bbf56885d8c83381f981adb0fb283799375a3bd5","other":"2af02e62ff3e40be25d22c2f745a505b895b3804a80663cb53db4edd5365c568","up":true},{"one":"7471be56a2f617783d5a1ae4390794ad24f453259b7c1f8f65be8f71c8bf8bf7","other":"72acf53ab7dbda90ea70e78d001fe21e7ab88e4054ff0453212e33a26fbbf7f1","up":true},{"one":"7de7eb0b73f3b02b998e592e0fc51b26c0c3ae6695d41897874d2a3d8ce3554b","other":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","up":true},{"one":"7471be56a2f617783d5a1ae4390794ad24f453259b7c1f8f65be8f71c8bf8bf7","other":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","up":true},{"one":"a485db6ec1929aaf695a84294f52905aa602584954e9ef71b4e18f7b847da31d","other":"a80b39cf280fa80b323370cfc37e8e3ff38de47d5de445c500f36c8cf2390b53","up":true},{"one":"9fee945c89d6bb59e77a24183c2855f4eece0b5d3266f750ea95483eae1e1871","other":"a6720426faa1ea7e2d55c1c5ad05ba2e8c534d120ebb5e1375cf769d73191708","up":true},{"one":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","other":"c3f356325eb1e61f2cfd8d80d085fb144c6887e1d710c9d133109b84b823425d","up":true},{"one":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","other":"52881f1fe0b8e9044a3bc035edbbd6343b9c41d49c0f2c2ca799c2e764489b91","up":true},{"one":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","other":"ae715a40e9bf50b4eb954c6b87efbf1a6e7abc3da6d84055e4591aadf5bad5c8","up":true},{"one":"4f90f53754ee50357491dbab7c3e6748b40133e3339920b32f4c3457f4258477","other":"47f95708475c9d61ff2016e7707c4a3796f6c87313bd1d2bf91aa620113ba5f8","up":true},{"one":"4fd6a4b99b1ba76775594673421c8eea175c114519c8554798c2c364175bbaae","other":"47f95708475c9d61ff2016e7707c4a3796f6c87313bd1d2bf91aa620113ba5f8","up":true},{"one":"4019ce1dc667d90a85021b5fbf49ad77c1de7fd3443a40a17bdfaf5175efa1e1","other":"4a828523731caf47f9aa2dfa20f8a8895e9a6562b3a034574639a229a733b4a6","up":true},{"one":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","other":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","up":true},{"one":"bfeca898b1755a001eb0bcf8f5ad5713a550d0597f99ef7967ea4e7add8d3e81","other":"b79fd26ce97e98e7432029dea6bf11e48ea9e8f2385dec5c60be0cbd51bbef54","up":true},{"one":"245495250d38095106022d3fc905eb7eb6f54adb2cb0bacdda0b305974eaa50a","other":"353875d4ae2b9c5cc6cb14059bdfb9540647100c7c3334bc60db2ca2b4ddf3f5","up":true},{"one":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","other":"fb93417c597050f8deeb3098475f6cf0d213926332b59c25a538c1b4e6af89c0","up":true},{"one":"6ea59b849a9754c022219146efc27fa6561e5aca8338a111847259695198267b","other":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","up":true},{"one":"7d45f1e91bcd683d870422b2f591c76f6ab07ff4b079f35c280ce46f7fc364e1","other":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","up":true},{"one":"487edf32027381256a864bc2e9bca175f7216cc1234731bd4dc90d2cbad6975d","other":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","up":true},{"one":"9fee945c89d6bb59e77a24183c2855f4eece0b5d3266f750ea95483eae1e1871","other":"af358f626738d2e5d17060abbaf24cb0124a48c5edb8f3baeda30bea7b403dfa","up":true},{"one":"af358f626738d2e5d17060abbaf24cb0124a48c5edb8f3baeda30bea7b403dfa","other":"ae715a40e9bf50b4eb954c6b87efbf1a6e7abc3da6d84055e4591aadf5bad5c8","up":true},{"one":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","other":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","up":true},{"one":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","other":"9fee945c89d6bb59e77a24183c2855f4eece0b5d3266f750ea95483eae1e1871","up":true},{"one":"18f9292fbe6e94ec3df4d0a896c71ed2b1771437e42d8475a7aae63c7992834d","other":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","up":true},{"one":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","other":"fb93417c597050f8deeb3098475f6cf0d213926332b59c25a538c1b4e6af89c0","up":true},{"one":"6ea59b849a9754c022219146efc27fa6561e5aca8338a111847259695198267b","other":"730749922ce3dcd8e249b988dbfb950c15473a2f6fd5cfe2a8126995fb1dc186","up":true},{"one":"6544c4a403d9809697420ff9840ea9de0c9ba6491de28dac288db6257f2dc218","other":"66100e4f9d103d935164f39ea3f0332708f3825ebcf31ac6f8c9714c75a50860","up":true},{"one":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","other":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","up":true},{"one":"6d219b0ea4a1620a7ba051cc478da6df9ea443e87dde79e963ec789a5bad7459","other":"66100e4f9d103d935164f39ea3f0332708f3825ebcf31ac6f8c9714c75a50860","up":true},{"one":"4f90f53754ee50357491dbab7c3e6748b40133e3339920b32f4c3457f4258477","other":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","up":true},{"one":"4fd6a4b99b1ba76775594673421c8eea175c114519c8554798c2c364175bbaae","other":"487edf32027381256a864bc2e9bca175f7216cc1234731bd4dc90d2cbad6975d","up":true},{"one":"3e85d2d3d6750f84500fcf523743e0c242943e69188cb83114adad1787e6c089","other":"3a4a300de1009d694bebce2362ca429086ade2f551989d55ba459a68efea15af","up":true},{"one":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","other":"3a4a300de1009d694bebce2362ca429086ade2f551989d55ba459a68efea15af","up":true},{"one":"0210146e650132dd0ceb8cea779cafc5485822eeeb9fcc17f292af5cef6e9632","other":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","up":true},{"one":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","other":"5fd02ea98977d976f5d47a4005300b9865b1dcdac77bf8a98a8920a89b57825c","up":true},{"one":"b4639751d67db0b32ad1846996fed0d5c59be76b77efaec5f66e64df35eefae8","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"f048e37427d68c4134a115cc7df6a8924ea95f24e7f2435372ae331e24b5b7fd","other":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","up":true},{"one":"03f5f1be5c1ae35469333a5b08622372f65ef8f233746afde56d2647c5273630","other":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","up":true},{"one":"0ea201515867c6793cbe48350f209e69acd3829b9b16b6016d965d84fb6379c5","other":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","up":true},{"one":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","other":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","up":true},{"one":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","other":"043f24ce87fbd01f6f663f323ac6c156810a67b12efbec2f219ae9a882a4bd16","up":true},{"one":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","other":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","up":true},{"one":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","other":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","up":true},{"one":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","other":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","up":true},{"one":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","other":"72acf53ab7dbda90ea70e78d001fe21e7ab88e4054ff0453212e33a26fbbf7f1","up":true},{"one":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","other":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","up":true},{"one":"d6f37555e9f35a3ef3b8b4d5c18cf0702ed99c1c8b5b0a8554deddcbe542b88c","other":"daa2c427e8ecc566e634908fb0d47077d8e79854bf884342c06c4bcb355e80e6","up":true},{"one":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","other":"8163eebe696123817a6d998d7781612931df6fa2fd0293c0d0dd395d031c53f6","up":true},{"one":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","other":"e787fd35b438dbae3d0283f7181fc4afbeb6f333201ad4cd1b46bf7094a6a85f","up":true},{"one":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","other":"06aa2f43956f978abe7b8f8f78a5a8cd59decb66ec54b7c81ae50c3752798c5e","up":true},{"one":"92325e52babbf2b8ae3c37beec7e440d11efed2d103befa2c4a969315a2eab56","other":"96b6ec2cda7c9e962bbc97e7916b92d69b112265c4456d30dade806e09fea62e","up":true},{"one":"9294e5b39a1cd4230400e033da1ac1f79a4025adab41ce8c0e0e47dda2418cb5","other":"96b6ec2cda7c9e962bbc97e7916b92d69b112265c4456d30dade806e09fea62e","up":true},{"one":"43afd835930eac91c0c8947001dd60d8da9112c19cf3f1356589b58b48962166","other":"46c5a266dd46041a62f21946290aad736d2298277bbbb16b1efb4490d9d31fa8","up":true},{"one":"c0d1ac430a1466a1a28cb1aa3d29573ccedb13641ac19e6e615f2a96e8f0950b","other":"c15d13aff7f5694fb8fc2a2154846ac703064133feb632e28830777bbfc7c016","up":true},{"one":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","other":"7de7eb0b73f3b02b998e592e0fc51b26c0c3ae6695d41897874d2a3d8ce3554b","up":true},{"one":"43afd835930eac91c0c8947001dd60d8da9112c19cf3f1356589b58b48962166","other":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","up":true},{"one":"9c0c7b6da33520440b181a5fcc30aa8a7576ec9c6f64acef0c9525d05a2ee393","other":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","up":true},{"one":"ad36b802e9b01727e516879c0055e3c46b810845794f7772ad85e8062111b167","other":"af358f626738d2e5d17060abbaf24cb0124a48c5edb8f3baeda30bea7b403dfa","up":true},{"one":"ad36b802e9b01727e516879c0055e3c46b810845794f7772ad85e8062111b167","other":"aa504681c991c69015c7ef0469902c44fc4e900356b181aa252a1280ffbd2e3b","up":true},{"one":"b4c7555a38d1d4e3bd1d01bcc47a84efd6128e8cc0fd803362da8b341f984a4d","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"32dd86f3f5c3abe55d6b1fe352e9d1d0cc63d61cfa60b8e70b2ee9db2e28e1aa","other":"29fd4473f1b6c37f8268d73dd5f683a0f7eb64bd793dab2c450c10d8ab0b666d","up":true},{"one":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","other":"123fe1d442c7dc905a7206e712fbd9cea640295a6823b39b1945629ffa89e895","up":true},{"one":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","other":"d3d254a3024a7aac01e97a5f0aff13756741867e599bc94c068f0d1d19ca8176","up":true},{"one":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","other":"b4639751d67db0b32ad1846996fed0d5c59be76b77efaec5f66e64df35eefae8","up":true},{"one":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","other":"29fd4473f1b6c37f8268d73dd5f683a0f7eb64bd793dab2c450c10d8ab0b666d","up":true},{"one":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","other":"ceee9ac63786ad68d884f5d018248ae1b7b60f0ad434776d29bd4dfcdc0f04c1","up":true},{"one":"237459ba37141f18b3cd40f5ec9cdaf6789fb87aa4511d489fdd98a960ef2883","other":"21682cd736edfb8b451dfd9c55eb9ea3b7cf2e35cad32537f1f4800644fbf9a7","up":true},{"one":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","other":"88da9dccda89c111dde40318fd1e5239e77b670f5a1b88a3e9320b7350885e31","up":true},{"one":"d6f37555e9f35a3ef3b8b4d5c18cf0702ed99c1c8b5b0a8554deddcbe542b88c","other":"d68f504755b48f8d41570169dd3544ef1caea2ab1bb334f8d8c5fb27fe816f01","up":true},{"one":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","other":"0592048ae4e6a3000f36e8e1c0e1328c26c0d4bb6369d6cd867a08d847dacb9e","up":true},{"one":"0f5e34b25f6851d0f3b572c64420de0aec01754bea0854bd7e9910b4b1cc66c3","other":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","up":true},{"one":"d3d254a3024a7aac01e97a5f0aff13756741867e599bc94c068f0d1d19ca8176","other":"d6f37555e9f35a3ef3b8b4d5c18cf0702ed99c1c8b5b0a8554deddcbe542b88c","up":true},{"one":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","other":"004e812995bc04f22993466df9889ca1b650bba242cf80bb070ce9564e7bedd8","up":true},{"one":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","other":"670d1c31407b4f34bb955031e9a3ab7980273155080b459a082687d53a3b42a6","up":true},{"one":"86128689b03706f9a86baf4753154afbb0c535f84089039ef5610d82fb969a6e","other":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","up":true},{"one":"34fce2b8208c6c8ed005943b3f2cb9f8923739ce262e2040f7ffc048093ef910","other":"31edba65e539a9a3c7107a131c4fb4b5a8e2c2d4e2db7874a0116c173961e27c","up":true},{"one":"86128689b03706f9a86baf4753154afbb0c535f84089039ef5610d82fb969a6e","other":"f78a54ba4b3fa75cdb542bdcd6da0d49986a5f7e7939959a51b3b48a87605654","up":true},{"one":"3345e03880c377fc1fe7b6205011ce3e32525cefee5e03161105f1512893f9fb","other":"31edba65e539a9a3c7107a131c4fb4b5a8e2c2d4e2db7874a0116c173961e27c","up":true},{"one":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","other":"4d447ea80bc126e57b9fc91203a310c5f4b242df5ad70d5087d16697a626e601","up":true},{"one":"c4632e164170d35e91fce46f660265f73795f1d840effce5ab2aae18c73003d7","other":"d8b0425e4cb5134ddb2de5c05e0ae9b861bd67409c1446ca228318f79f5dd1f6","up":true},{"one":"353875d4ae2b9c5cc6cb14059bdfb9540647100c7c3334bc60db2ca2b4ddf3f5","other":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","up":true},{"one":"1b1eca7b35086d8b217431c1d6a01fc07dc8bc816196a80015c9e163bed676ff","other":"194ad126c41c487d95c83b19ed2bb053e4d2ec0605952d3a396dd4304e8e363c","up":true},{"one":"95e0529227286d442cdeeed428614f99c19f9223426e6f6cefbef2d9d47ce96f","other":"9eec5d5357732a7acd26cc8aa0bc65f6c6069aedf4f3ab4a6790c7fff6bc2695","up":true},{"one":"ceee9ac63786ad68d884f5d018248ae1b7b60f0ad434776d29bd4dfcdc0f04c1","other":"cb69f673a54a75de300d1d1ab989caa04579504cbcaf78f03bc192c9578ec5a0","up":true},{"one":"95e0529227286d442cdeeed428614f99c19f9223426e6f6cefbef2d9d47ce96f","other":"99aaaa7c98113955ecc0163943317f6ec08ae755e96c25c9f79ca1f529c60587","up":true},{"one":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","other":"b310299bef4f4e84a1ecc960143e4b1bbe98cd4eb8352458c477569ca9994c20","up":true},{"one":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","other":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","up":true},{"one":"03f5f1be5c1ae35469333a5b08622372f65ef8f233746afde56d2647c5273630","other":"06aa2f43956f978abe7b8f8f78a5a8cd59decb66ec54b7c81ae50c3752798c5e","up":true},{"one":"0ea201515867c6793cbe48350f209e69acd3829b9b16b6016d965d84fb6379c5","other":"06aa2f43956f978abe7b8f8f78a5a8cd59decb66ec54b7c81ae50c3752798c5e","up":true},{"one":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","other":"96b6ec2cda7c9e962bbc97e7916b92d69b112265c4456d30dade806e09fea62e","up":true},{"one":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","other":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","up":true},{"one":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","other":"e67d72c8c72bbc7bec1d4e936d65adbeab75d1b6cafdf2c39e38bfc723cceebf","up":true},{"one":"167306fb0e24ddd775c2ac0722103ce5db98bb0ef60159b91f584475d34cb5bf","other":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","up":true},{"one":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","other":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","up":true},{"one":"0f5e34b25f6851d0f3b572c64420de0aec01754bea0854bd7e9910b4b1cc66c3","other":"0ea201515867c6793cbe48350f209e69acd3829b9b16b6016d965d84fb6379c5","up":true},{"one":"7294223c0ab27851df8c25418acf09a28efa95fc238252af8a68390db07b77e4","other":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","up":true},{"one":"79abd33d150fdbcbce6f55f3d101608b80e03ede7e4627bc06ba5c1b001538a4","other":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","up":true},{"one":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","other":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","up":true},{"one":"92325e52babbf2b8ae3c37beec7e440d11efed2d103befa2c4a969315a2eab56","other":"9390ef61753fcf9dae5e585a85650287fe1f792538721a2cf767adc44c5e7ce0","up":true},{"one":"ed6570e63fc0e2e2d6290645c4339b70a0f1c14064d286b4e0ba143607bd70c8","other":"e8392bdce92a7dcfa5e3196a6e1d4e5dc4814c72e104d3e2aa1f1ab99e2a47a6","up":true},{"one":"74062d9dd73e26066fd44e4f6c696f271c9dc6860bdc5ba3fcf447f455c8cafa","other":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","up":true},{"one":"b71052b072cb98fb79151fd0c6044828e535a0547cace931183c7e4e7f227698","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"99dbf361b67d6c370fd4f201b059e93da50dd88cde2e25d0bcf3c73afbf13535","other":"9a82d22b29c88eaca2f20a62e9e63675904065e72c70ce2fbbefc0c9d2e53e90","up":true},{"one":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","other":"dc3ec6529cec19709403e473d4546ba07f7aaaaec5be7cb0618246a4c0458944","up":true},{"one":"adfc03118406bc5af4e3f3c6c89c3448c3fe4f9df20b15bcca9bbacf58ee5457","other":"abfabc2964ce2f7b8b3702e897a8cc83fb0c009d5af9670b889db582e57ef7c1","up":true},{"one":"604c6bfab4f5d16d5b786318cf8f9e0197fc18e5d8a8e6f4fd345e653d2abe17","other":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","up":true},{"one":"95e0529227286d442cdeeed428614f99c19f9223426e6f6cefbef2d9d47ce96f","other":"9a82d22b29c88eaca2f20a62e9e63675904065e72c70ce2fbbefc0c9d2e53e90","up":true},{"one":"c4632e164170d35e91fce46f660265f73795f1d840effce5ab2aae18c73003d7","other":"c4844b8046d1c47f699ee1c232d945829105d45af26fba722ce4d0a937c99631","up":true},{"one":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","other":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","up":true},{"one":"42c019ddad2fde0f0c7f5041eb584996976844f8b2e08e20458437578d0257ef","other":"47f95708475c9d61ff2016e7707c4a3796f6c87313bd1d2bf91aa620113ba5f8","up":true},{"one":"1e4441309404b0dd8dd9e8b1fe78708b015a0b0278ce86bcdaabc7d2d691791a","other":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","up":true},{"one":"167306fb0e24ddd775c2ac0722103ce5db98bb0ef60159b91f584475d34cb5bf","other":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","up":true},{"one":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","other":"15f6bb0640c3ac8ae8ecf10d0d46cc739bb6f86b0c0cf706103aad9f3785219b","up":true},{"one":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","other":"e787fd35b438dbae3d0283f7181fc4afbeb6f333201ad4cd1b46bf7094a6a85f","up":true},{"one":"1d942c85568f640497c46a9bd5a9825bc6fb05571203a3f0f14a3032602d81fc","other":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","up":true},{"one":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","other":"7d941474f90981a03c95af4cf68be163d01a317e202ac0db82e25f46fc3db8aa","up":true},{"one":"1b869ca5aec1ff0c9980b06b79084019752fd4557054e251c0091ee5d2c7d478","other":"0592048ae4e6a3000f36e8e1c0e1328c26c0d4bb6369d6cd867a08d847dacb9e","up":true},{"one":"15f6bb0640c3ac8ae8ecf10d0d46cc739bb6f86b0c0cf706103aad9f3785219b","other":"167306fb0e24ddd775c2ac0722103ce5db98bb0ef60159b91f584475d34cb5bf","up":true},{"one":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","other":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","up":true},{"one":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","other":"0210146e650132dd0ceb8cea779cafc5485822eeeb9fcc17f292af5cef6e9632","up":true},{"one":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","other":"b71052b072cb98fb79151fd0c6044828e535a0547cace931183c7e4e7f227698","up":true},{"one":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","other":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","up":true},{"one":"95e0529227286d442cdeeed428614f99c19f9223426e6f6cefbef2d9d47ce96f","other":"99dbf361b67d6c370fd4f201b059e93da50dd88cde2e25d0bcf3c73afbf13535","up":true},{"one":"c4632e164170d35e91fce46f660265f73795f1d840effce5ab2aae18c73003d7","other":"c7703c4697637f40a50a5d0b656c4e54010ef1277b64d251ccd0752512e246e9","up":true},{"one":"2013ce63483af2990b35f9ecbe5a7653c34b24ded173753575b24451787ed8b9","other":"265d7fc553fb8dc016d7c6ccc31fc372912a11e9e8da0e5822e371a91d82e857","up":true},{"one":"571638637156bdaf92d9c715647a1cf64e91877c2d998658b7c6cd27522271bd","other":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","up":true},{"one":"daa2c427e8ecc566e634908fb0d47077d8e79854bf884342c06c4bcb355e80e6","other":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","up":true},{"one":"571638637156bdaf92d9c715647a1cf64e91877c2d998658b7c6cd27522271bd","other":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","up":true},{"one":"ceee9ac63786ad68d884f5d018248ae1b7b60f0ad434776d29bd4dfcdc0f04c1","other":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","up":true},{"one":"42c019ddad2fde0f0c7f5041eb584996976844f8b2e08e20458437578d0257ef","other":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","up":true},{"one":"5f056f9593be46d3eba4dc8d9e0b93262cd3f00e007c13c4c011d53e7c199628","other":"5fd02ea98977d976f5d47a4005300b9865b1dcdac77bf8a98a8920a89b57825c","up":true},{"one":"daa2c427e8ecc566e634908fb0d47077d8e79854bf884342c06c4bcb355e80e6","other":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","up":true},{"one":"2af02e62ff3e40be25d22c2f745a505b895b3804a80663cb53db4edd5365c568","other":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","up":true},{"one":"7d941474f90981a03c95af4cf68be163d01a317e202ac0db82e25f46fc3db8aa","other":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","up":true},{"one":"a80b39cf280fa80b323370cfc37e8e3ff38de47d5de445c500f36c8cf2390b53","other":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","up":true},{"one":"3a4a300de1009d694bebce2362ca429086ade2f551989d55ba459a68efea15af","other":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","up":true},{"one":"e787fd35b438dbae3d0283f7181fc4afbeb6f333201ad4cd1b46bf7094a6a85f","other":"e67d72c8c72bbc7bec1d4e936d65adbeab75d1b6cafdf2c39e38bfc723cceebf","up":true},{"one":"f1fcfc064446a432ac7a21efc386fa3d053d0ca4540fd4ddbbb253ec11f9ce94","other":"ed6570e63fc0e2e2d6290645c4339b70a0f1c14064d286b4e0ba143607bd70c8","up":true},{"one":"396b256b223f346a490844895b9f1046b4790d624af1c43ecae1de318c7214a8","other":"21682cd736edfb8b451dfd9c55eb9ea3b7cf2e35cad32537f1f4800644fbf9a7","up":true},{"one":"1b1eca7b35086d8b217431c1d6a01fc07dc8bc816196a80015c9e163bed676ff","other":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","up":true},{"one":"1e4441309404b0dd8dd9e8b1fe78708b015a0b0278ce86bcdaabc7d2d691791a","other":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","up":true},{"one":"b71052b072cb98fb79151fd0c6044828e535a0547cace931183c7e4e7f227698","other":"b310299bef4f4e84a1ecc960143e4b1bbe98cd4eb8352458c477569ca9994c20","up":true},{"one":"9a82d22b29c88eaca2f20a62e9e63675904065e72c70ce2fbbefc0c9d2e53e90","other":"99fba7334f47bfcf21331d6179bfb2eee2ecebd1ea23fd829c542844b10ed974","up":true},{"one":"bc08af284ab661dcc1cb23859271590e50a09ff8af8073769c812901ea0b9080","other":"b310299bef4f4e84a1ecc960143e4b1bbe98cd4eb8352458c477569ca9994c20","up":true},{"one":"daa2c427e8ecc566e634908fb0d47077d8e79854bf884342c06c4bcb355e80e6","other":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","up":true},{"one":"1b869ca5aec1ff0c9980b06b79084019752fd4557054e251c0091ee5d2c7d478","other":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","up":true},{"one":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","other":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","up":true},{"one":"df5ea72c0c257bbea9b43901a1e406bf6055a307d083367e7f1d944e65c2b3cd","other":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","up":true},{"one":"d68f504755b48f8d41570169dd3544ef1caea2ab1bb334f8d8c5fb27fe816f01","other":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","up":true},{"one":"bc08af284ab661dcc1cb23859271590e50a09ff8af8073769c812901ea0b9080","other":"bf5ac6f9b651895c8d75ab3ae7994f91981dbbcdce25a7cd718fc996da96946d","up":true},{"one":"36485615576c68702382f614054494cc4a40b90fd66db86b9d86a6a131dcc6ac","other":"32dd86f3f5c3abe55d6b1fe352e9d1d0cc63d61cfa60b8e70b2ee9db2e28e1aa","up":true},{"one":"d8222a2c177f0cd17baae28327af5c13f2fb551dd01298d17855ce7f6f4f6189","other":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","up":true},{"one":"df5ea72c0c257bbea9b43901a1e406bf6055a307d083367e7f1d944e65c2b3cd","other":"daa2c427e8ecc566e634908fb0d47077d8e79854bf884342c06c4bcb355e80e6","up":true},{"one":"3dca1ae488cc497fe738c630465f987b419a5bb259fdfa17fe7130e238ca3022","other":"b71052b072cb98fb79151fd0c6044828e535a0547cace931183c7e4e7f227698","up":true},{"one":"1b869ca5aec1ff0c9980b06b79084019752fd4557054e251c0091ee5d2c7d478","other":"18f9292fbe6e94ec3df4d0a896c71ed2b1771437e42d8475a7aae63c7992834d","up":true},{"one":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","other":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","up":true},{"one":"0f81caba7d2f752fc670829f74565114bcf10c5b97d7f93252b467d0eee278b3","other":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","up":true},{"one":"46c5a266dd46041a62f21946290aad736d2298277bbbb16b1efb4490d9d31fa8","other":"4cf655b03778bcb41c32664f13168dc32968354468c8aa699de52b43a2e0dc7d","up":true},{"one":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","other":"89ee628583b6e0345ceb312c2e2cbf5ac410eb00531be903a4b94b60b639993b","up":true},{"one":"2e9f7d37106b59d6f411dfa0392325d1fa279fe45c18a101e32ece6716e88946","other":"32dd86f3f5c3abe55d6b1fe352e9d1d0cc63d61cfa60b8e70b2ee9db2e28e1aa","up":true},{"one":"e3c95a82f373da4522533252549c1d68b6f7621a887feb66199cfd4b7a35b6fc","other":"e8392bdce92a7dcfa5e3196a6e1d4e5dc4814c72e104d3e2aa1f1ab99e2a47a6","up":true},{"one":"458a31b8fdbc42f5e80f01118c9dc041740212c09ddfb4b137c9e7e93ae9ec51","other":"4019ce1dc667d90a85021b5fbf49ad77c1de7fd3443a40a17bdfaf5175efa1e1","up":true},{"one":"df5ea72c0c257bbea9b43901a1e406bf6055a307d083367e7f1d944e65c2b3cd","other":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","up":true},{"one":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","other":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","up":true},{"one":"a749372cccb7001a480b9416c41305bd1d3bc417e15669ae587d4375f2bbcbf7","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"f78a54ba4b3fa75cdb542bdcd6da0d49986a5f7e7939959a51b3b48a87605654","other":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","up":true},{"one":"9390ef61753fcf9dae5e585a85650287fe1f792538721a2cf767adc44c5e7ce0","other":"a749372cccb7001a480b9416c41305bd1d3bc417e15669ae587d4375f2bbcbf7","up":true},{"one":"c4844b8046d1c47f699ee1c232d945829105d45af26fba722ce4d0a937c99631","other":"c3f356325eb1e61f2cfd8d80d085fb144c6887e1d710c9d133109b84b823425d","up":true},{"one":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","other":"290fca7596740129fd1a3075bbf56885d8c83381f981adb0fb283799375a3bd5","up":true},{"one":"f4eee71a143722ea8288279876bf5687c1ed65eede0129e987e53f5ec76d8543","other":"f9243ad864859edd351592d24abdf5c9c4e37f4ffe2ae1201c45f26576ee5725","up":true},{"one":"2e9f7d37106b59d6f411dfa0392325d1fa279fe45c18a101e32ece6716e88946","other":"31edba65e539a9a3c7107a131c4fb4b5a8e2c2d4e2db7874a0116c173961e27c","up":true},{"one":"13d8107c669cc27f6cbc64cef6b2bd11f72db297f2857c2de7323b92632b8231","other":"123fe1d442c7dc905a7206e712fbd9cea640295a6823b39b1945629ffa89e895","up":true},{"one":"31edba65e539a9a3c7107a131c4fb4b5a8e2c2d4e2db7874a0116c173961e27c","other":"353875d4ae2b9c5cc6cb14059bdfb9540647100c7c3334bc60db2ca2b4ddf3f5","up":true},{"one":"4a828523731caf47f9aa2dfa20f8a8895e9a6562b3a034574639a229a733b4a6","other":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","up":true},{"one":"88da9dccda89c111dde40318fd1e5239e77b670f5a1b88a3e9320b7350885e31","other":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","up":true},{"one":"e8392bdce92a7dcfa5e3196a6e1d4e5dc4814c72e104d3e2aa1f1ab99e2a47a6","other":"f5dd00d94488f3387d64ccc2115cd91e5073c06de36d195a5c1a95db23cd5c68","up":true},{"one":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","other":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","up":true},{"one":"d68f504755b48f8d41570169dd3544ef1caea2ab1bb334f8d8c5fb27fe816f01","other":"d6d2d35b3819e03b39d7146c4dd1b53fb7b70681c8860464650d5eb2d09dda2c","up":true},{"one":"1a02108d9149739be6ba85ddd1e8a3961adeec38f77fcef95ab57f8f2f39f4bb","other":"123fe1d442c7dc905a7206e712fbd9cea640295a6823b39b1945629ffa89e895","up":true},{"one":"67831469d7f9b779df4318cda9d4310c53fdd66332af3740e654233ceb3fbcbc","other":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","up":true},{"one":"baf32cface953bd22141970b691567ed375f06043fe31de354598fb4230fb0a3","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"14418557af44e33fc8d46369426c3e2fc91622cb08779b3fefce706969ec2163","other":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","up":true},{"one":"265d7fc553fb8dc016d7c6ccc31fc372912a11e9e8da0e5822e371a91d82e857","other":"2f9f266a3b660fe685de2932de82979bd221706db1e2b954a93edc6609c378d9","up":true},{"one":"c7230d8d56b65a66ab721a8268ed436af78210aae7d068cc2b8afe39b6e61c81","other":"c63e796efdc44712403199c11c3e5ff28e5d6a4d9ecdf3e95620fbd6afb1bc2d","up":true},{"one":"7de7eb0b73f3b02b998e592e0fc51b26c0c3ae6695d41897874d2a3d8ce3554b","other":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","up":true},{"one":"14418557af44e33fc8d46369426c3e2fc91622cb08779b3fefce706969ec2163","other":"1a02108d9149739be6ba85ddd1e8a3961adeec38f77fcef95ab57f8f2f39f4bb","up":true},{"one":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","other":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","up":true},{"one":"29ffd73eb3b36593482208f5bf1b9c82b7f1a92b06173c769db0999d924c8969","other":"2fd800d224f124e47038bf140ebd486f1d0ab93169c998d2dfabfd1d3ced96ed","up":true},{"one":"ecd2b502d4e9bf77369c62d1142a9fb4ab1071e751cc6971e9e381e953b0fc12","other":"e8392bdce92a7dcfa5e3196a6e1d4e5dc4814c72e104d3e2aa1f1ab99e2a47a6","up":true},{"one":"edca23c0fccfd1ec66f4d39bb129462776b9091c2337f6afbfe8737a0ebc0d7d","other":"e8392bdce92a7dcfa5e3196a6e1d4e5dc4814c72e104d3e2aa1f1ab99e2a47a6","up":true},{"one":"c7230d8d56b65a66ab721a8268ed436af78210aae7d068cc2b8afe39b6e61c81","other":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","up":true},{"one":"bf5ac6f9b651895c8d75ab3ae7994f91981dbbcdce25a7cd718fc996da96946d","other":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","up":true},{"one":"77ec3a73ce1d597d307e87ea8d026baae2a7df4d288a657f240eaaaae7fdb456","other":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","up":true},{"one":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","other":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","up":true},{"one":"21682cd736edfb8b451dfd9c55eb9ea3b7cf2e35cad32537f1f4800644fbf9a7","other":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","up":true},{"one":"1d5fc65b4121cccaff0d2a853c6bbff0c10b4ec28cfc5ef5cbea8bc28bc4cbec","other":"1b1eca7b35086d8b217431c1d6a01fc07dc8bc816196a80015c9e163bed676ff","up":true},{"one":"275c93677aefd92ab38f9e5b5b4d29f90e809ccaf77b088102071aba26421f3e","other":"2013ce63483af2990b35f9ecbe5a7653c34b24ded173753575b24451787ed8b9","up":true},{"one":"211a516fb95d093eb43f4716edeb1e6f76ed79d2aeee3393740e244970187840","other":"2013ce63483af2990b35f9ecbe5a7653c34b24ded173753575b24451787ed8b9","up":true},{"one":"a749372cccb7001a480b9416c41305bd1d3bc417e15669ae587d4375f2bbcbf7","other":"baf32cface953bd22141970b691567ed375f06043fe31de354598fb4230fb0a3","up":true},{"one":"b79fd26ce97e98e7432029dea6bf11e48ea9e8f2385dec5c60be0cbd51bbef54","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"f5dd00d94488f3387d64ccc2115cd91e5073c06de36d195a5c1a95db23cd5c68","other":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","up":true},{"one":"29fd4473f1b6c37f8268d73dd5f683a0f7eb64bd793dab2c450c10d8ab0b666d","other":"290fca7596740129fd1a3075bbf56885d8c83381f981adb0fb283799375a3bd5","up":true},{"one":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","other":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","up":true},{"one":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","other":"d6f37555e9f35a3ef3b8b4d5c18cf0702ed99c1c8b5b0a8554deddcbe542b88c","up":true},{"one":"571638637156bdaf92d9c715647a1cf64e91877c2d998658b7c6cd27522271bd","other":"5110c7e54a18df6963919866e7f6c0c3a483045d5ac20fc0dfa5e1926681be7f","up":true},{"one":"adfc03118406bc5af4e3f3c6c89c3448c3fe4f9df20b15bcca9bbacf58ee5457","other":"ad36b802e9b01727e516879c0055e3c46b810845794f7772ad85e8062111b167","up":true},{"one":"adfc03118406bc5af4e3f3c6c89c3448c3fe4f9df20b15bcca9bbacf58ee5457","other":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","up":true},{"one":"a6720426faa1ea7e2d55c1c5ad05ba2e8c534d120ebb5e1375cf769d73191708","other":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","up":true},{"one":"def4f0b988942a8699f6b500cc2531c7dea065e3dc5bdcbeb26bca8f00e1d3a6","other":"d8b0425e4cb5134ddb2de5c05e0ae9b861bd67409c1446ca228318f79f5dd1f6","up":true},{"one":"34fce2b8208c6c8ed005943b3f2cb9f8923739ce262e2040f7ffc048093ef910","other":"36485615576c68702382f614054494cc4a40b90fd66db86b9d86a6a131dcc6ac","up":true},{"one":"4d447ea80bc126e57b9fc91203a310c5f4b242df5ad70d5087d16697a626e601","other":"487edf32027381256a864bc2e9bca175f7216cc1234731bd4dc90d2cbad6975d","up":true},{"one":"3345e03880c377fc1fe7b6205011ce3e32525cefee5e03161105f1512893f9fb","other":"32dd86f3f5c3abe55d6b1fe352e9d1d0cc63d61cfa60b8e70b2ee9db2e28e1aa","up":true},{"one":"f78a54ba4b3fa75cdb542bdcd6da0d49986a5f7e7939959a51b3b48a87605654","other":"f1fcfc064446a432ac7a21efc386fa3d053d0ca4540fd4ddbbb253ec11f9ce94","up":true},{"one":"dc3ec6529cec19709403e473d4546ba07f7aaaaec5be7cb0618246a4c0458944","other":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","up":true},{"one":"d8222a2c177f0cd17baae28327af5c13f2fb551dd01298d17855ce7f6f4f6189","other":"def4f0b988942a8699f6b500cc2531c7dea065e3dc5bdcbeb26bca8f00e1d3a6","up":true},{"one":"004e812995bc04f22993466df9889ca1b650bba242cf80bb070ce9564e7bedd8","other":"0210146e650132dd0ceb8cea779cafc5485822eeeb9fcc17f292af5cef6e9632","up":true},{"one":"a80b39cf280fa80b323370cfc37e8e3ff38de47d5de445c500f36c8cf2390b53","other":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","up":true},{"one":"7d941474f90981a03c95af4cf68be163d01a317e202ac0db82e25f46fc3db8aa","other":"7dc63c8146ed092aa4c9539abd112dc3a703631e364a62fd74b192eee5778123","up":true},{"one":"396b256b223f346a490844895b9f1046b4790d624af1c43ecae1de318c7214a8","other":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","up":true},{"one":"7a4118d2ce16e8258cc2cdf31c5891945e3787c46c0836fc916601584609b7db","other":"7dc63c8146ed092aa4c9539abd112dc3a703631e364a62fd74b192eee5778123","up":true},{"one":"1b1eca7b35086d8b217431c1d6a01fc07dc8bc816196a80015c9e163bed676ff","other":"1a02108d9149739be6ba85ddd1e8a3961adeec38f77fcef95ab57f8f2f39f4bb","up":true},{"one":"1e4441309404b0dd8dd9e8b1fe78708b015a0b0278ce86bcdaabc7d2d691791a","other":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","up":true},{"one":"259d3874697a0c3192f973c620880b2a86567289f5222d07eb91a51867352559","other":"275c93677aefd92ab38f9e5b5b4d29f90e809ccaf77b088102071aba26421f3e","up":true},{"one":"96b6ec2cda7c9e962bbc97e7916b92d69b112265c4456d30dade806e09fea62e","other":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","up":true},{"one":"2013ce63483af2990b35f9ecbe5a7653c34b24ded173753575b24451787ed8b9","other":"21682cd736edfb8b451dfd9c55eb9ea3b7cf2e35cad32537f1f4800644fbf9a7","up":true},{"one":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","other":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","up":true},{"one":"0f81caba7d2f752fc670829f74565114bcf10c5b97d7f93252b467d0eee278b3","other":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","up":true},{"one":"12b9aa0addf45c714ec95fa915294c997f9d73ee4d6ac7832d18d19554ec49a5","other":"123fe1d442c7dc905a7206e712fbd9cea640295a6823b39b1945629ffa89e895","up":true},{"one":"0592048ae4e6a3000f36e8e1c0e1328c26c0d4bb6369d6cd867a08d847dacb9e","other":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","up":true},{"one":"4a828523731caf47f9aa2dfa20f8a8895e9a6562b3a034574639a229a733b4a6","other":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","up":true},{"one":"f4eee71a143722ea8288279876bf5687c1ed65eede0129e987e53f5ec76d8543","other":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","up":true},{"one":"396b256b223f346a490844895b9f1046b4790d624af1c43ecae1de318c7214a8","other":"2f9f266a3b660fe685de2932de82979bd221706db1e2b954a93edc6609c378d9","up":true},{"one":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","other":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","up":true},{"one":"259d3874697a0c3192f973c620880b2a86567289f5222d07eb91a51867352559","other":"211a516fb95d093eb43f4716edeb1e6f76ed79d2aeee3393740e244970187840","up":true},{"one":"96b6ec2cda7c9e962bbc97e7916b92d69b112265c4456d30dade806e09fea62e","other":"95e0529227286d442cdeeed428614f99c19f9223426e6f6cefbef2d9d47ce96f","up":true},{"one":"3dca1ae488cc497fe738c630465f987b419a5bb259fdfa17fe7130e238ca3022","other":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","up":true},{"one":"0592048ae4e6a3000f36e8e1c0e1328c26c0d4bb6369d6cd867a08d847dacb9e","other":"06aa2f43956f978abe7b8f8f78a5a8cd59decb66ec54b7c81ae50c3752798c5e","up":true},{"one":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","other":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","up":true},{"one":"4a828523731caf47f9aa2dfa20f8a8895e9a6562b3a034574639a229a733b4a6","other":"4a670fe61711307f7781b3b5a8014fc0e63866efe356a25e61b2e768a7389980","up":true},{"one":"a485db6ec1929aaf695a84294f52905aa602584954e9ef71b4e18f7b847da31d","other":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","up":true},{"one":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"d6d2d35b3819e03b39d7146c4dd1b53fb7b70681c8860464650d5eb2d09dda2c","other":"d3d254a3024a7aac01e97a5f0aff13756741867e599bc94c068f0d1d19ca8176","up":true},{"one":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","other":"9c01d016aaf11f9e7b048ba10cea3c351d9ab5c8c66e8605f78c06772708985c","up":true},{"one":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","other":"d8222a2c177f0cd17baae28327af5c13f2fb551dd01298d17855ce7f6f4f6189","up":true},{"one":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","other":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","up":true},{"one":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","other":"9c01d016aaf11f9e7b048ba10cea3c351d9ab5c8c66e8605f78c06772708985c","up":true},{"one":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","other":"3f3e66f7c8fc7daae8a7a84a9c90f1ea64faa43930f36fc8f11e2d7b751f8ee9","up":true},{"one":"67831469d7f9b779df4318cda9d4310c53fdd66332af3740e654233ceb3fbcbc","other":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","up":true},{"one":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","other":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","up":true},{"one":"237459ba37141f18b3cd40f5ec9cdaf6789fb87aa4511d489fdd98a960ef2883","other":"211a516fb95d093eb43f4716edeb1e6f76ed79d2aeee3393740e244970187840","up":true},{"one":"b45d1aee940e5d2c71db22e2afbe908d5967e437fbb5bd051edea86ea4ac9d30","other":"b4c7555a38d1d4e3bd1d01bcc47a84efd6128e8cc0fd803362da8b341f984a4d","up":true},{"one":"7471be56a2f617783d5a1ae4390794ad24f453259b7c1f8f65be8f71c8bf8bf7","other":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","up":true},{"one":"4a670fe61711307f7781b3b5a8014fc0e63866efe356a25e61b2e768a7389980","other":"487edf32027381256a864bc2e9bca175f7216cc1234731bd4dc90d2cbad6975d","up":true},{"one":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","other":"4a81ee929074bd6d9932fb4d970f5207db8680bda0e951aa72a6bd64df45108d","up":true},{"one":"4cf655b03778bcb41c32664f13168dc32968354468c8aa699de52b43a2e0dc7d","other":"4a828523731caf47f9aa2dfa20f8a8895e9a6562b3a034574639a229a733b4a6","up":true},{"one":"3a4a300de1009d694bebce2362ca429086ade2f551989d55ba459a68efea15af","other":"388d46d6ea3fd92aa959b04d597ff1096d6133f171181c8fb54e52935b62d33c","up":true},{"one":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","other":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","up":true},{"one":"6ea59b849a9754c022219146efc27fa6561e5aca8338a111847259695198267b","other":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","up":true},{"one":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","other":"18f9292fbe6e94ec3df4d0a896c71ed2b1771437e42d8475a7aae63c7992834d","up":true},{"one":"5110c7e54a18df6963919866e7f6c0c3a483045d5ac20fc0dfa5e1926681be7f","other":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","up":true},{"one":"5110c7e54a18df6963919866e7f6c0c3a483045d5ac20fc0dfa5e1926681be7f","other":"52881f1fe0b8e9044a3bc035edbbd6343b9c41d49c0f2c2ca799c2e764489b91","up":true},{"one":"0210146e650132dd0ceb8cea779cafc5485822eeeb9fcc17f292af5cef6e9632","other":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","up":true},{"one":"2f2284537629514837d5c2296d2871d8c4c147719f0ddc28ef7c629efd44dd61","other":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","up":true},{"one":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","other":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","up":true},{"one":"2a69e3897d30e0cbe9b6179a55e30eb13c658b284e1dfcfe4dca08a69434df3c","other":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","up":true},{"one":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","other":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","up":true},{"one":"e4c3fa0700516da5650f794067eb1c90ba7eaab2b65019bf34552c26f8c1d9a6","other":"e8392bdce92a7dcfa5e3196a6e1d4e5dc4814c72e104d3e2aa1f1ab99e2a47a6","up":true},{"one":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","other":"f4eee71a143722ea8288279876bf5687c1ed65eede0129e987e53f5ec76d8543","up":true},{"one":"b310299bef4f4e84a1ecc960143e4b1bbe98cd4eb8352458c477569ca9994c20","other":"b4639751d67db0b32ad1846996fed0d5c59be76b77efaec5f66e64df35eefae8","up":true},{"one":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","other":"d3d254a3024a7aac01e97a5f0aff13756741867e599bc94c068f0d1d19ca8176","up":true},{"one":"3e85d2d3d6750f84500fcf523743e0c242943e69188cb83114adad1787e6c089","other":"396b256b223f346a490844895b9f1046b4790d624af1c43ecae1de318c7214a8","up":true},{"one":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","other":"670d1c31407b4f34bb955031e9a3ab7980273155080b459a082687d53a3b42a6","up":true},{"one":"abfabc2964ce2f7b8b3702e897a8cc83fb0c009d5af9670b889db582e57ef7c1","other":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","up":true},{"one":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","other":"396b256b223f346a490844895b9f1046b4790d624af1c43ecae1de318c7214a8","up":true},{"one":"6544c4a403d9809697420ff9840ea9de0c9ba6491de28dac288db6257f2dc218","other":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","up":true},{"one":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","other":"2af02e62ff3e40be25d22c2f745a505b895b3804a80663cb53db4edd5365c568","up":true},{"one":"3a4a300de1009d694bebce2362ca429086ade2f551989d55ba459a68efea15af","other":"31edba65e539a9a3c7107a131c4fb4b5a8e2c2d4e2db7874a0116c173961e27c","up":true},{"one":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","other":"52619a523050aa8caa32448e59721a76dc3321dc92cfcb1b05f1cec5b79d424a","up":true},{"one":"c98d389a6d8e6eb5a1eb5a6f235857d5d939d30e51c16d80c039c9e0eb2f7eca","other":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","up":true},{"one":"cb69f673a54a75de300d1d1ab989caa04579504cbcaf78f03bc192c9578ec5a0","other":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","up":true},{"one":"3af3bb6c4343a19e3c0f9a89697cd70a3c79c64b1e40afa59b188c5553eed567","other":"4a828523731caf47f9aa2dfa20f8a8895e9a6562b3a034574639a229a733b4a6","up":true},{"one":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","other":"396b256b223f346a490844895b9f1046b4790d624af1c43ecae1de318c7214a8","up":true},{"one":"4cf655b03778bcb41c32664f13168dc32968354468c8aa699de52b43a2e0dc7d","other":"487edf32027381256a864bc2e9bca175f7216cc1234731bd4dc90d2cbad6975d","up":true},{"one":"6d219b0ea4a1620a7ba051cc478da6df9ea443e87dde79e963ec789a5bad7459","other":"604c6bfab4f5d16d5b786318cf8f9e0197fc18e5d8a8e6f4fd345e653d2abe17","up":true},{"one":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","other":"7471be56a2f617783d5a1ae4390794ad24f453259b7c1f8f65be8f71c8bf8bf7","up":true},{"one":"2f9f266a3b660fe685de2932de82979bd221706db1e2b954a93edc6609c378d9","other":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","up":true},{"one":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","other":"259d3874697a0c3192f973c620880b2a86567289f5222d07eb91a51867352559","up":true},{"one":"f048e37427d68c4134a115cc7df6a8924ea95f24e7f2435372ae331e24b5b7fd","other":"f4eee71a143722ea8288279876bf5687c1ed65eede0129e987e53f5ec76d8543","up":true},{"one":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","other":"5110c7e54a18df6963919866e7f6c0c3a483045d5ac20fc0dfa5e1926681be7f","up":true},{"one":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","other":"237459ba37141f18b3cd40f5ec9cdaf6789fb87aa4511d489fdd98a960ef2883","up":true},{"one":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","other":"ed6570e63fc0e2e2d6290645c4339b70a0f1c14064d286b4e0ba143607bd70c8","up":true},{"one":"52619a523050aa8caa32448e59721a76dc3321dc92cfcb1b05f1cec5b79d424a","other":"5fa8a5a29a47694653a5e55fd9b76e9c4f717ecc9b7088a6ec7ee273b291d49e","up":true},{"one":"cb69f673a54a75de300d1d1ab989caa04579504cbcaf78f03bc192c9578ec5a0","other":"c98d389a6d8e6eb5a1eb5a6f235857d5d939d30e51c16d80c039c9e0eb2f7eca","up":true},{"one":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","other":"fed15b57e749c5f8f5fdb382eb95abe2ce0dee184c817e557122207f3729b05c","up":true},{"one":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","other":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","up":true},{"one":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","other":"03f5f1be5c1ae35469333a5b08622372f65ef8f233746afde56d2647c5273630","up":true},{"one":"6ea59b849a9754c022219146efc27fa6561e5aca8338a111847259695198267b","other":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","up":true},{"one":"3f3e66f7c8fc7daae8a7a84a9c90f1ea64faa43930f36fc8f11e2d7b751f8ee9","other":"32dd86f3f5c3abe55d6b1fe352e9d1d0cc63d61cfa60b8e70b2ee9db2e28e1aa","up":true},{"one":"79abd33d150fdbcbce6f55f3d101608b80e03ede7e4627bc06ba5c1b001538a4","other":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","up":true},{"one":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","other":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","up":true},{"one":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","other":"34fce2b8208c6c8ed005943b3f2cb9f8923739ce262e2040f7ffc048093ef910","up":true},{"one":"6544c4a403d9809697420ff9840ea9de0c9ba6491de28dac288db6257f2dc218","other":"604c6bfab4f5d16d5b786318cf8f9e0197fc18e5d8a8e6f4fd345e653d2abe17","up":true},{"one":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","other":"03f5f1be5c1ae35469333a5b08622372f65ef8f233746afde56d2647c5273630","up":true},{"one":"3e85d2d3d6750f84500fcf523743e0c242943e69188cb83114adad1787e6c089","other":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","up":true},{"one":"3a4a300de1009d694bebce2362ca429086ade2f551989d55ba459a68efea15af","other":"3af3bb6c4343a19e3c0f9a89697cd70a3c79c64b1e40afa59b188c5553eed567","up":true},{"one":"388d46d6ea3fd92aa959b04d597ff1096d6133f171181c8fb54e52935b62d33c","other":"396b256b223f346a490844895b9f1046b4790d624af1c43ecae1de318c7214a8","up":true},{"one":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","other":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","up":true},{"one":"5fa8a5a29a47694653a5e55fd9b76e9c4f717ecc9b7088a6ec7ee273b291d49e","other":"5fab772fff883d04cf7e4713266fb1e98fa1746e12faeb04e87bcb37b8b61187","up":true},{"one":"3af3bb6c4343a19e3c0f9a89697cd70a3c79c64b1e40afa59b188c5553eed567","other":"3f3e66f7c8fc7daae8a7a84a9c90f1ea64faa43930f36fc8f11e2d7b751f8ee9","up":true},{"one":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","other":"7854fb9f0d2f016653994b9e23033f27e21a2ef186a352c8d068b75ea20beec2","up":true},{"one":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","other":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","up":true},{"one":"c8fe626c60d6cd15912a22d2c5872f10a8f8aef5b6f6b2f1070edec6670bf8b1","other":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","up":true},{"one":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","other":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","up":true},{"one":"194ad126c41c487d95c83b19ed2bb053e4d2ec0605952d3a396dd4304e8e363c","other":"18f9292fbe6e94ec3df4d0a896c71ed2b1771437e42d8475a7aae63c7992834d","up":true},{"one":"66100e4f9d103d935164f39ea3f0332708f3825ebcf31ac6f8c9714c75a50860","other":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","up":true},{"one":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","other":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","up":true},{"one":"2f9f266a3b660fe685de2932de82979bd221706db1e2b954a93edc6609c378d9","other":"2e9f7d37106b59d6f411dfa0392325d1fa279fe45c18a101e32ece6716e88946","up":true},{"one":"1835f62fe3889f43ba7b4b577869ec764e96fa9a051f1301fb96795daa8b0755","other":"18f9292fbe6e94ec3df4d0a896c71ed2b1771437e42d8475a7aae63c7992834d","up":true},{"one":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","other":"b60deba7c8676305b67af41d2de866bddd72edc0667d427e51fc3f2e91412b45","up":true},{"one":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","other":"b4c7555a38d1d4e3bd1d01bcc47a84efd6128e8cc0fd803362da8b341f984a4d","up":true},{"one":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","other":"7ffed78e11c71eccfdddf6a8e5e367831b26a6b11e4d3b60c5b9fc50f4bcdf33","up":true},{"one":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","other":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","up":true},{"one":"72acf53ab7dbda90ea70e78d001fe21e7ab88e4054ff0453212e33a26fbbf7f1","other":"7294223c0ab27851df8c25418acf09a28efa95fc238252af8a68390db07b77e4","up":true},{"one":"9294e5b39a1cd4230400e033da1ac1f79a4025adab41ce8c0e0e47dda2418cb5","other":"9390ef61753fcf9dae5e585a85650287fe1f792538721a2cf767adc44c5e7ce0","up":true},{"one":"7294223c0ab27851df8c25418acf09a28efa95fc238252af8a68390db07b77e4","other":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","up":true},{"one":"985c14910d922bbd1556c30620e3ab72170da6553e658dd3806d773a734a1c93","other":"96b6ec2cda7c9e962bbc97e7916b92d69b112265c4456d30dade806e09fea62e","up":true},{"one":"b4c7555a38d1d4e3bd1d01bcc47a84efd6128e8cc0fd803362da8b341f984a4d","other":"b4639751d67db0b32ad1846996fed0d5c59be76b77efaec5f66e64df35eefae8","up":true},{"one":"14c81fb2fb8fa5a6b47288356ab486012046d09959b9ef4814a09dc3facb4853","other":"167306fb0e24ddd775c2ac0722103ce5db98bb0ef60159b91f584475d34cb5bf","up":true},{"one":"aa504681c991c69015c7ef0469902c44fc4e900356b181aa252a1280ffbd2e3b","other":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","up":true},{"one":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","other":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","up":true},{"one":"7dc63c8146ed092aa4c9539abd112dc3a703631e364a62fd74b192eee5778123","other":"7ffed78e11c71eccfdddf6a8e5e367831b26a6b11e4d3b60c5b9fc50f4bcdf33","up":true},{"one":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","other":"3f3e66f7c8fc7daae8a7a84a9c90f1ea64faa43930f36fc8f11e2d7b751f8ee9","up":true},{"one":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","other":"e4c3fa0700516da5650f794067eb1c90ba7eaab2b65019bf34552c26f8c1d9a6","up":true},{"one":"d3d254a3024a7aac01e97a5f0aff13756741867e599bc94c068f0d1d19ca8176","other":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","up":true},{"one":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","other":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","up":true},{"one":"47f95708475c9d61ff2016e7707c4a3796f6c87313bd1d2bf91aa620113ba5f8","other":"46c5a266dd46041a62f21946290aad736d2298277bbbb16b1efb4490d9d31fa8","up":true},{"one":"6544c4a403d9809697420ff9840ea9de0c9ba6491de28dac288db6257f2dc218","other":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","up":true},{"one":"6d219b0ea4a1620a7ba051cc478da6df9ea443e87dde79e963ec789a5bad7459","other":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","up":true},{"one":"14c81fb2fb8fa5a6b47288356ab486012046d09959b9ef4814a09dc3facb4853","other":"14418557af44e33fc8d46369426c3e2fc91622cb08779b3fefce706969ec2163","up":true},{"one":"74062d9dd73e26066fd44e4f6c696f271c9dc6860bdc5ba3fcf447f455c8cafa","other":"730749922ce3dcd8e249b988dbfb950c15473a2f6fd5cfe2a8126995fb1dc186","up":true},{"one":"9c01d016aaf11f9e7b048ba10cea3c351d9ab5c8c66e8605f78c06772708985c","other":"9eec5d5357732a7acd26cc8aa0bc65f6c6069aedf4f3ab4a6790c7fff6bc2695","up":true},{"one":"72fae969e2888f7cefa40a544ea66b8c2fd161b1af3ea1f0b8a7012a36188aa1","other":"730749922ce3dcd8e249b988dbfb950c15473a2f6fd5cfe2a8126995fb1dc186","up":true},{"one":"123fe1d442c7dc905a7206e712fbd9cea640295a6823b39b1945629ffa89e895","other":"1b1eca7b35086d8b217431c1d6a01fc07dc8bc816196a80015c9e163bed676ff","up":true},{"one":"9c01d016aaf11f9e7b048ba10cea3c351d9ab5c8c66e8605f78c06772708985c","other":"9fee945c89d6bb59e77a24183c2855f4eece0b5d3266f750ea95483eae1e1871","up":true},{"one":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","other":"9a82d22b29c88eaca2f20a62e9e63675904065e72c70ce2fbbefc0c9d2e53e90","up":true},{"one":"ad36b802e9b01727e516879c0055e3c46b810845794f7772ad85e8062111b167","other":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","up":true},{"one":"4af7ccb5c14efcb279502de37f436576eede95efbe5641f12f47e99b2bd9a172","other":"4a81ee929074bd6d9932fb4d970f5207db8680bda0e951aa72a6bd64df45108d","up":true},{"one":"458a31b8fdbc42f5e80f01118c9dc041740212c09ddfb4b137c9e7e93ae9ec51","other":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","up":true},{"one":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","other":"f5dd00d94488f3387d64ccc2115cd91e5073c06de36d195a5c1a95db23cd5c68","up":true},{"one":"f1fcfc064446a432ac7a21efc386fa3d053d0ca4540fd4ddbbb253ec11f9ce94","other":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","up":true},{"one":"7854fb9f0d2f016653994b9e23033f27e21a2ef186a352c8d068b75ea20beec2","other":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","up":true},{"one":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","other":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","up":true},{"one":"1b7250dae1f2b07504b381d28084b751a26ac791a1ed842ea5746826ab85ba22","other":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","up":true},{"one":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","other":"f5dd00d94488f3387d64ccc2115cd91e5073c06de36d195a5c1a95db23cd5c68","up":true},{"one":"604c6bfab4f5d16d5b786318cf8f9e0197fc18e5d8a8e6f4fd345e653d2abe17","other":"67831469d7f9b779df4318cda9d4310c53fdd66332af3740e654233ceb3fbcbc","up":true},{"one":"52619a523050aa8caa32448e59721a76dc3321dc92cfcb1b05f1cec5b79d424a","other":"52881f1fe0b8e9044a3bc035edbbd6343b9c41d49c0f2c2ca799c2e764489b91","up":true},{"one":"9c01d016aaf11f9e7b048ba10cea3c351d9ab5c8c66e8605f78c06772708985c","other":"99dbf361b67d6c370fd4f201b059e93da50dd88cde2e25d0bcf3c73afbf13535","up":true},{"one":"95e0529227286d442cdeeed428614f99c19f9223426e6f6cefbef2d9d47ce96f","other":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","up":true},{"one":"c4632e164170d35e91fce46f660265f73795f1d840effce5ab2aae18c73003d7","other":"c15d13aff7f5694fb8fc2a2154846ac703064133feb632e28830777bbfc7c016","up":true},{"one":"5f056f9593be46d3eba4dc8d9e0b93262cd3f00e007c13c4c011d53e7c199628","other":"5fab772fff883d04cf7e4713266fb1e98fa1746e12faeb04e87bcb37b8b61187","up":true},{"one":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","other":"1835f62fe3889f43ba7b4b577869ec764e96fa9a051f1301fb96795daa8b0755","up":true},{"one":"15f6bb0640c3ac8ae8ecf10d0d46cc739bb6f86b0c0cf706103aad9f3785219b","other":"14418557af44e33fc8d46369426c3e2fc91622cb08779b3fefce706969ec2163","up":true},{"one":"c99c6a2a2fe7f4613c8d918404c956eae0a00fce544c9eeba5da640e5e958874","other":"c3f356325eb1e61f2cfd8d80d085fb144c6887e1d710c9d133109b84b823425d","up":true},{"one":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","other":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","up":true},{"one":"d3d254a3024a7aac01e97a5f0aff13756741867e599bc94c068f0d1d19ca8176","other":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","up":true},{"one":"c63e796efdc44712403199c11c3e5ff28e5d6a4d9ecdf3e95620fbd6afb1bc2d","other":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","up":true},{"one":"9c01d016aaf11f9e7b048ba10cea3c351d9ab5c8c66e8605f78c06772708985c","other":"be0ab3ddc656568900d8e1f42394cc942464596b7565db40f6c5a0c24ee942f2","up":true},{"one":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","other":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","up":true},{"one":"74062d9dd73e26066fd44e4f6c696f271c9dc6860bdc5ba3fcf447f455c8cafa","other":"7471be56a2f617783d5a1ae4390794ad24f453259b7c1f8f65be8f71c8bf8bf7","up":true},{"one":"d68f504755b48f8d41570169dd3544ef1caea2ab1bb334f8d8c5fb27fe816f01","other":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","up":true},{"one":"f1fcfc064446a432ac7a21efc386fa3d053d0ca4540fd4ddbbb253ec11f9ce94","other":"f5dd00d94488f3387d64ccc2115cd91e5073c06de36d195a5c1a95db23cd5c68","up":true},{"one":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","other":"1d5fc65b4121cccaff0d2a853c6bbff0c10b4ec28cfc5ef5cbea8bc28bc4cbec","up":true},{"one":"1b7250dae1f2b07504b381d28084b751a26ac791a1ed842ea5746826ab85ba22","other":"1e4441309404b0dd8dd9e8b1fe78708b015a0b0278ce86bcdaabc7d2d691791a","up":true},{"one":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","other":"0592048ae4e6a3000f36e8e1c0e1328c26c0d4bb6369d6cd867a08d847dacb9e","up":true},{"one":"52619a523050aa8caa32448e59721a76dc3321dc92cfcb1b05f1cec5b79d424a","other":"5110c7e54a18df6963919866e7f6c0c3a483045d5ac20fc0dfa5e1926681be7f","up":true},{"one":"c15d13aff7f5694fb8fc2a2154846ac703064133feb632e28830777bbfc7c016","other":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","up":true},{"one":"ed6570e63fc0e2e2d6290645c4339b70a0f1c14064d286b4e0ba143607bd70c8","other":"ed1348b4037004b61bd4d6f9be1648112f835943a3b997b91ac931301311e007","up":true},{"one":"c15d13aff7f5694fb8fc2a2154846ac703064133feb632e28830777bbfc7c016","other":"cb69f673a54a75de300d1d1ab989caa04579504cbcaf78f03bc192c9578ec5a0","up":true},{"one":"adfc03118406bc5af4e3f3c6c89c3448c3fe4f9df20b15bcca9bbacf58ee5457","other":"b60deba7c8676305b67af41d2de866bddd72edc0667d427e51fc3f2e91412b45","up":true},{"one":"571638637156bdaf92d9c715647a1cf64e91877c2d998658b7c6cd27522271bd","other":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","up":true},{"one":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","other":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","up":true},{"one":"1a02108d9149739be6ba85ddd1e8a3961adeec38f77fcef95ab57f8f2f39f4bb","other":"1d5fc65b4121cccaff0d2a853c6bbff0c10b4ec28cfc5ef5cbea8bc28bc4cbec","up":true},{"one":"13d8107c669cc27f6cbc64cef6b2bd11f72db297f2857c2de7323b92632b8231","other":"14c81fb2fb8fa5a6b47288356ab486012046d09959b9ef4814a09dc3facb4853","up":true},{"one":"ed6570e63fc0e2e2d6290645c4339b70a0f1c14064d286b4e0ba143607bd70c8","other":"ecd2b502d4e9bf77369c62d1142a9fb4ab1071e751cc6971e9e381e953b0fc12","up":true},{"one":"3345e03880c377fc1fe7b6205011ce3e32525cefee5e03161105f1512893f9fb","other":"36485615576c68702382f614054494cc4a40b90fd66db86b9d86a6a131dcc6ac","up":true},{"one":"c4844b8046d1c47f699ee1c232d945829105d45af26fba722ce4d0a937c99631","other":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","up":true},{"one":"bf5ac6f9b651895c8d75ab3ae7994f91981dbbcdce25a7cd718fc996da96946d","other":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","up":true},{"one":"df5ea72c0c257bbea9b43901a1e406bf6055a307d083367e7f1d944e65c2b3cd","other":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","up":true},{"one":"86f71d23eb4efeeed4679be7a3d0f58f98b83d23b3e0a8b73f60f1535b51f25c","other":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","up":true},{"one":"1d5fc65b4121cccaff0d2a853c6bbff0c10b4ec28cfc5ef5cbea8bc28bc4cbec","other":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","up":true},{"one":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","other":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","up":true},{"one":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","other":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","up":true},{"one":"571638637156bdaf92d9c715647a1cf64e91877c2d998658b7c6cd27522271bd","other":"52619a523050aa8caa32448e59721a76dc3321dc92cfcb1b05f1cec5b79d424a","up":true},{"one":"adfc03118406bc5af4e3f3c6c89c3448c3fe4f9df20b15bcca9bbacf58ee5457","other":"b79fd26ce97e98e7432029dea6bf11e48ea9e8f2385dec5c60be0cbd51bbef54","up":true},{"one":"a6720426faa1ea7e2d55c1c5ad05ba2e8c534d120ebb5e1375cf769d73191708","other":"a749372cccb7001a480b9416c41305bd1d3bc417e15669ae587d4375f2bbcbf7","up":true},{"one":"c99c6a2a2fe7f4613c8d918404c956eae0a00fce544c9eeba5da640e5e958874","other":"c9f391b1c008fbac89791d7135d4d23c245ef6f28258d6ba4f826619da9a54d8","up":true},{"one":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","other":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","up":true},{"one":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"52619a523050aa8caa32448e59721a76dc3321dc92cfcb1b05f1cec5b79d424a","other":"50932e2c1f03cbc41d8d9600ce2457f3eacd82c36b61fa09bef512b104eaf8dc","up":true},{"one":"7d941474f90981a03c95af4cf68be163d01a317e202ac0db82e25f46fc3db8aa","other":"7de7eb0b73f3b02b998e592e0fc51b26c0c3ae6695d41897874d2a3d8ce3554b","up":true},{"one":"86f71d23eb4efeeed4679be7a3d0f58f98b83d23b3e0a8b73f60f1535b51f25c","other":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","up":true},{"one":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","other":"5fd02ea98977d976f5d47a4005300b9865b1dcdac77bf8a98a8920a89b57825c","up":true},{"one":"7a4118d2ce16e8258cc2cdf31c5891945e3787c46c0836fc916601584609b7db","other":"7854fb9f0d2f016653994b9e23033f27e21a2ef186a352c8d068b75ea20beec2","up":true},{"one":"89ee628583b6e0345ceb312c2e2cbf5ac410eb00531be903a4b94b60b639993b","other":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","up":true},{"one":"1b869ca5aec1ff0c9980b06b79084019752fd4557054e251c0091ee5d2c7d478","other":"1b7250dae1f2b07504b381d28084b751a26ac791a1ed842ea5746826ab85ba22","up":true},{"one":"af358f626738d2e5d17060abbaf24cb0124a48c5edb8f3baeda30bea7b403dfa","other":"b60deba7c8676305b67af41d2de866bddd72edc0667d427e51fc3f2e91412b45","up":true},{"one":"396b256b223f346a490844895b9f1046b4790d624af1c43ecae1de318c7214a8","other":"34fce2b8208c6c8ed005943b3f2cb9f8923739ce262e2040f7ffc048093ef910","up":true},{"one":"7a4118d2ce16e8258cc2cdf31c5891945e3787c46c0836fc916601584609b7db","other":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","up":true},{"one":"7a4118d2ce16e8258cc2cdf31c5891945e3787c46c0836fc916601584609b7db","other":"7de7eb0b73f3b02b998e592e0fc51b26c0c3ae6695d41897874d2a3d8ce3554b","up":true},{"one":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","other":"604c6bfab4f5d16d5b786318cf8f9e0197fc18e5d8a8e6f4fd345e653d2abe17","up":true},{"one":"03f5f1be5c1ae35469333a5b08622372f65ef8f233746afde56d2647c5273630","other":"004e812995bc04f22993466df9889ca1b650bba242cf80bb070ce9564e7bedd8","up":true},{"one":"f4e0d91ca58ed616918bf268565c2edb4176293e851b8d29cbbda55bdc608534","other":"f5dd00d94488f3387d64ccc2115cd91e5073c06de36d195a5c1a95db23cd5c68","up":true},{"one":"7854fb9f0d2f016653994b9e23033f27e21a2ef186a352c8d068b75ea20beec2","other":"79abd33d150fdbcbce6f55f3d101608b80e03ede7e4627bc06ba5c1b001538a4","up":true},{"one":"6dbd2dd8f11adebfbd3ec41289e35e4a8930da1bef5212b152deed8a8ab2f1bc","other":"66100e4f9d103d935164f39ea3f0332708f3825ebcf31ac6f8c9714c75a50860","up":true},{"one":"df5ea72c0c257bbea9b43901a1e406bf6055a307d083367e7f1d944e65c2b3cd","other":"def4f0b988942a8699f6b500cc2531c7dea065e3dc5bdcbeb26bca8f00e1d3a6","up":true},{"one":"bc08af284ab661dcc1cb23859271590e50a09ff8af8073769c812901ea0b9080","other":"be0ab3ddc656568900d8e1f42394cc942464596b7565db40f6c5a0c24ee942f2","up":true},{"one":"7ffed78e11c71eccfdddf6a8e5e367831b26a6b11e4d3b60c5b9fc50f4bcdf33","other":"7de7eb0b73f3b02b998e592e0fc51b26c0c3ae6695d41897874d2a3d8ce3554b","up":true},{"one":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","other":"f78a54ba4b3fa75cdb542bdcd6da0d49986a5f7e7939959a51b3b48a87605654","up":true},{"one":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","other":"1e4441309404b0dd8dd9e8b1fe78708b015a0b0278ce86bcdaabc7d2d691791a","up":true},{"one":"571638637156bdaf92d9c715647a1cf64e91877c2d998658b7c6cd27522271bd","other":"5fab772fff883d04cf7e4713266fb1e98fa1746e12faeb04e87bcb37b8b61187","up":true},{"one":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","other":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","up":true},{"one":"c63e796efdc44712403199c11c3e5ff28e5d6a4d9ecdf3e95620fbd6afb1bc2d","other":"c4844b8046d1c47f699ee1c232d945829105d45af26fba722ce4d0a937c99631","up":true},{"one":"1e4441309404b0dd8dd9e8b1fe78708b015a0b0278ce86bcdaabc7d2d691791a","other":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","up":true},{"one":"1d942c85568f640497c46a9bd5a9825bc6fb05571203a3f0f14a3032602d81fc","other":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","up":true},{"one":"3dca1ae488cc497fe738c630465f987b419a5bb259fdfa17fe7130e238ca3022","other":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","up":true},{"one":"d6f37555e9f35a3ef3b8b4d5c18cf0702ed99c1c8b5b0a8554deddcbe542b88c","other":"d6d2d35b3819e03b39d7146c4dd1b53fb7b70681c8860464650d5eb2d09dda2c","up":true},{"one":"d6f37555e9f35a3ef3b8b4d5c18cf0702ed99c1c8b5b0a8554deddcbe542b88c","other":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","up":true},{"one":"0f81caba7d2f752fc670829f74565114bcf10c5b97d7f93252b467d0eee278b3","other":"0f5e34b25f6851d0f3b572c64420de0aec01754bea0854bd7e9910b4b1cc66c3","up":true},{"one":"dc3ec6529cec19709403e473d4546ba07f7aaaaec5be7cb0618246a4c0458944","other":"def4f0b988942a8699f6b500cc2531c7dea065e3dc5bdcbeb26bca8f00e1d3a6","up":true},{"one":"1d942c85568f640497c46a9bd5a9825bc6fb05571203a3f0f14a3032602d81fc","other":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","up":true},{"one":"0ea201515867c6793cbe48350f209e69acd3829b9b16b6016d965d84fb6379c5","other":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","up":true},{"one":"d6f37555e9f35a3ef3b8b4d5c18cf0702ed99c1c8b5b0a8554deddcbe542b88c","other":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","up":true},{"one":"edca23c0fccfd1ec66f4d39bb129462776b9091c2337f6afbfe8737a0ebc0d7d","other":"ed6570e63fc0e2e2d6290645c4339b70a0f1c14064d286b4e0ba143607bd70c8","up":true},{"one":"36485615576c68702382f614054494cc4a40b90fd66db86b9d86a6a131dcc6ac","other":"353875d4ae2b9c5cc6cb14059bdfb9540647100c7c3334bc60db2ca2b4ddf3f5","up":true},{"one":"e67d72c8c72bbc7bec1d4e936d65adbeab75d1b6cafdf2c39e38bfc723cceebf","other":"e8392bdce92a7dcfa5e3196a6e1d4e5dc4814c72e104d3e2aa1f1ab99e2a47a6","up":true},{"one":"baf32cface953bd22141970b691567ed375f06043fe31de354598fb4230fb0a3","other":"b310299bef4f4e84a1ecc960143e4b1bbe98cd4eb8352458c477569ca9994c20","up":true},{"one":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","other":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","up":true},{"one":"458a31b8fdbc42f5e80f01118c9dc041740212c09ddfb4b137c9e7e93ae9ec51","other":"47f95708475c9d61ff2016e7707c4a3796f6c87313bd1d2bf91aa620113ba5f8","up":true},{"one":"2e9f7d37106b59d6f411dfa0392325d1fa279fe45c18a101e32ece6716e88946","other":"36485615576c68702382f614054494cc4a40b90fd66db86b9d86a6a131dcc6ac","up":true},{"one":"ad36b802e9b01727e516879c0055e3c46b810845794f7772ad85e8062111b167","other":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","up":true},{"one":"ae715a40e9bf50b4eb954c6b87efbf1a6e7abc3da6d84055e4591aadf5bad5c8","other":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","up":true},{"one":"f1fcfc064446a432ac7a21efc386fa3d053d0ca4540fd4ddbbb253ec11f9ce94","other":"f4e0d91ca58ed616918bf268565c2edb4176293e851b8d29cbbda55bdc608534","up":true},{"one":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","other":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","up":true},{"one":"9fee945c89d6bb59e77a24183c2855f4eece0b5d3266f750ea95483eae1e1871","other":"abfabc2964ce2f7b8b3702e897a8cc83fb0c009d5af9670b889db582e57ef7c1","up":true},{"one":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","other":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","up":true},{"one":"42c019ddad2fde0f0c7f5041eb584996976844f8b2e08e20458437578d0257ef","other":"4d447ea80bc126e57b9fc91203a310c5f4b242df5ad70d5087d16697a626e601","up":true},{"one":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","other":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","up":true},{"one":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","other":"604c6bfab4f5d16d5b786318cf8f9e0197fc18e5d8a8e6f4fd345e653d2abe17","up":true},{"one":"2279d612454ff034032a5acf5a039477a11479b9b9a93edc26b61312e8d9b156","other":"237459ba37141f18b3cd40f5ec9cdaf6789fb87aa4511d489fdd98a960ef2883","up":true},{"one":"18f9292fbe6e94ec3df4d0a896c71ed2b1771437e42d8475a7aae63c7992834d","other":"13d8107c669cc27f6cbc64cef6b2bd11f72db297f2857c2de7323b92632b8231","up":true},{"one":"2279d612454ff034032a5acf5a039477a11479b9b9a93edc26b61312e8d9b156","other":"2013ce63483af2990b35f9ecbe5a7653c34b24ded173753575b24451787ed8b9","up":true},{"one":"9390ef61753fcf9dae5e585a85650287fe1f792538721a2cf767adc44c5e7ce0","other":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","up":true},{"one":"86128689b03706f9a86baf4753154afbb0c535f84089039ef5610d82fb969a6e","other":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","up":true},{"one":"a485db6ec1929aaf695a84294f52905aa602584954e9ef71b4e18f7b847da31d","other":"a749372cccb7001a480b9416c41305bd1d3bc417e15669ae587d4375f2bbcbf7","up":true},{"one":"4a828523731caf47f9aa2dfa20f8a8895e9a6562b3a034574639a229a733b4a6","other":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","up":true},{"one":"353875d4ae2b9c5cc6cb14059bdfb9540647100c7c3334bc60db2ca2b4ddf3f5","other":"32dd86f3f5c3abe55d6b1fe352e9d1d0cc63d61cfa60b8e70b2ee9db2e28e1aa","up":true},{"one":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","other":"dc3ec6529cec19709403e473d4546ba07f7aaaaec5be7cb0618246a4c0458944","up":true},{"one":"f9243ad864859edd351592d24abdf5c9c4e37f4ffe2ae1201c45f26576ee5725","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"e67d72c8c72bbc7bec1d4e936d65adbeab75d1b6cafdf2c39e38bfc723cceebf","other":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","up":true},{"one":"fed15b57e749c5f8f5fdb382eb95abe2ce0dee184c817e557122207f3729b05c","other":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","up":true},{"one":"88da9dccda89c111dde40318fd1e5239e77b670f5a1b88a3e9320b7350885e31","other":"89ee628583b6e0345ceb312c2e2cbf5ac410eb00531be903a4b94b60b639993b","up":true},{"one":"18f9292fbe6e94ec3df4d0a896c71ed2b1771437e42d8475a7aae63c7992834d","other":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","up":true},{"one":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","other":"89ee628583b6e0345ceb312c2e2cbf5ac410eb00531be903a4b94b60b639993b","up":true},{"one":"b79fd26ce97e98e7432029dea6bf11e48ea9e8f2385dec5c60be0cbd51bbef54","other":"b310299bef4f4e84a1ecc960143e4b1bbe98cd4eb8352458c477569ca9994c20","up":true},{"one":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","other":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","up":true},{"one":"b71052b072cb98fb79151fd0c6044828e535a0547cace931183c7e4e7f227698","other":"b4c7555a38d1d4e3bd1d01bcc47a84efd6128e8cc0fd803362da8b341f984a4d","up":true},{"one":"be0ab3ddc656568900d8e1f42394cc942464596b7565db40f6c5a0c24ee942f2","other":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","up":true},{"one":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","other":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","up":true},{"one":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","other":"2e9f7d37106b59d6f411dfa0392325d1fa279fe45c18a101e32ece6716e88946","up":true},{"one":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","other":"66100e4f9d103d935164f39ea3f0332708f3825ebcf31ac6f8c9714c75a50860","up":true},{"one":"b71052b072cb98fb79151fd0c6044828e535a0547cace931183c7e4e7f227698","other":"b79fd26ce97e98e7432029dea6bf11e48ea9e8f2385dec5c60be0cbd51bbef54","up":true},{"one":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","other":"67831469d7f9b779df4318cda9d4310c53fdd66332af3740e654233ceb3fbcbc","up":true},{"one":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","other":"4019ce1dc667d90a85021b5fbf49ad77c1de7fd3443a40a17bdfaf5175efa1e1","up":true},{"one":"b60deba7c8676305b67af41d2de866bddd72edc0667d427e51fc3f2e91412b45","other":"b5c764a29cd142acbd83b74989e7d2232eeb2413b74c99ad5769e8e7bec568f6","up":true},{"one":"211a516fb95d093eb43f4716edeb1e6f76ed79d2aeee3393740e244970187840","other":"21682cd736edfb8b451dfd9c55eb9ea3b7cf2e35cad32537f1f4800644fbf9a7","up":true},{"one":"e67d72c8c72bbc7bec1d4e936d65adbeab75d1b6cafdf2c39e38bfc723cceebf","other":"e4c3fa0700516da5650f794067eb1c90ba7eaab2b65019bf34552c26f8c1d9a6","up":true},{"one":"fed15b57e749c5f8f5fdb382eb95abe2ce0dee184c817e557122207f3729b05c","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"2279d612454ff034032a5acf5a039477a11479b9b9a93edc26b61312e8d9b156","other":"21682cd736edfb8b451dfd9c55eb9ea3b7cf2e35cad32537f1f4800644fbf9a7","up":true},{"one":"e3c95a82f373da4522533252549c1d68b6f7621a887feb66199cfd4b7a35b6fc","other":"ecd2b502d4e9bf77369c62d1142a9fb4ab1071e751cc6971e9e381e953b0fc12","up":true},{"one":"e3c95a82f373da4522533252549c1d68b6f7621a887feb66199cfd4b7a35b6fc","other":"edca23c0fccfd1ec66f4d39bb129462776b9091c2337f6afbfe8737a0ebc0d7d","up":true},{"one":"167306fb0e24ddd775c2ac0722103ce5db98bb0ef60159b91f584475d34cb5bf","other":"14418557af44e33fc8d46369426c3e2fc91622cb08779b3fefce706969ec2163","up":true},{"one":"9390ef61753fcf9dae5e585a85650287fe1f792538721a2cf767adc44c5e7ce0","other":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","up":true},{"one":"9390ef61753fcf9dae5e585a85650287fe1f792538721a2cf767adc44c5e7ce0","other":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","up":true},{"one":"67831469d7f9b779df4318cda9d4310c53fdd66332af3740e654233ceb3fbcbc","other":"6544c4a403d9809697420ff9840ea9de0c9ba6491de28dac288db6257f2dc218","up":true},{"one":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","other":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","up":true},{"one":"d6d2d35b3819e03b39d7146c4dd1b53fb7b70681c8860464650d5eb2d09dda2c","other":"d5644a72a9adef667fe33fbf5af45214d1dd09331165b24e504e8f5eefb6195e","up":true},{"one":"9fee945c89d6bb59e77a24183c2855f4eece0b5d3266f750ea95483eae1e1871","other":"9c0c7b6da33520440b181a5fcc30aa8a7576ec9c6f64acef0c9525d05a2ee393","up":true},{"one":"9fee945c89d6bb59e77a24183c2855f4eece0b5d3266f750ea95483eae1e1871","other":"9390ef61753fcf9dae5e585a85650287fe1f792538721a2cf767adc44c5e7ce0","up":true},{"one":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","other":"5110c7e54a18df6963919866e7f6c0c3a483045d5ac20fc0dfa5e1926681be7f","up":true},{"one":"1da34e756912c1f9290970e2b60d5d8a88407923c0eac8c409b75f0f470ebc36","other":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","up":true},{"one":"4019ce1dc667d90a85021b5fbf49ad77c1de7fd3443a40a17bdfaf5175efa1e1","other":"43afd835930eac91c0c8947001dd60d8da9112c19cf3f1356589b58b48962166","up":true},{"one":"8163eebe696123817a6d998d7781612931df6fa2fd0293c0d0dd395d031c53f6","other":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","up":true},{"one":"9eec5d5357732a7acd26cc8aa0bc65f6c6069aedf4f3ab4a6790c7fff6bc2695","other":"9fee945c89d6bb59e77a24183c2855f4eece0b5d3266f750ea95483eae1e1871","up":true},{"one":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","other":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","up":true},{"one":"e787fd35b438dbae3d0283f7181fc4afbeb6f333201ad4cd1b46bf7094a6a85f","other":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","up":true},{"one":"670d1c31407b4f34bb955031e9a3ab7980273155080b459a082687d53a3b42a6","other":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","up":true},{"one":"bfeca898b1755a001eb0bcf8f5ad5713a550d0597f99ef7967ea4e7add8d3e81","other":"bf5ac6f9b651895c8d75ab3ae7994f91981dbbcdce25a7cd718fc996da96946d","up":true},{"one":"670d1c31407b4f34bb955031e9a3ab7980273155080b459a082687d53a3b42a6","other":"6ea59b849a9754c022219146efc27fa6561e5aca8338a111847259695198267b","up":true},{"one":"7d45f1e91bcd683d870422b2f591c76f6ab07ff4b079f35c280ce46f7fc364e1","other":"79bd78e5e219d35b8c6f1847c4c77c99e6486db80ec19196e391d7b76232e0c0","up":true},{"one":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","other":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","up":true},{"one":"487edf32027381256a864bc2e9bca175f7216cc1234731bd4dc90d2cbad6975d","other":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","up":true},{"one":"bfeca898b1755a001eb0bcf8f5ad5713a550d0597f99ef7967ea4e7add8d3e81","other":"baf32cface953bd22141970b691567ed375f06043fe31de354598fb4230fb0a3","up":true},{"one":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","other":"21682cd736edfb8b451dfd9c55eb9ea3b7cf2e35cad32537f1f4800644fbf9a7","up":true},{"one":"7d45f1e91bcd683d870422b2f591c76f6ab07ff4b079f35c280ce46f7fc364e1","other":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","up":true},{"one":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","other":"245495250d38095106022d3fc905eb7eb6f54adb2cb0bacdda0b305974eaa50a","up":true},{"one":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","other":"f048e37427d68c4134a115cc7df6a8924ea95f24e7f2435372ae331e24b5b7fd","up":true},{"one":"abfabc2964ce2f7b8b3702e897a8cc83fb0c009d5af9670b889db582e57ef7c1","other":"ad36b802e9b01727e516879c0055e3c46b810845794f7772ad85e8062111b167","up":true},{"one":"9c0c7b6da33520440b181a5fcc30aa8a7576ec9c6f64acef0c9525d05a2ee393","other":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","up":true},{"one":"3e85d2d3d6750f84500fcf523743e0c242943e69188cb83114adad1787e6c089","other":"3f3e66f7c8fc7daae8a7a84a9c90f1ea64faa43930f36fc8f11e2d7b751f8ee9","up":true},{"one":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","other":"d68f504755b48f8d41570169dd3544ef1caea2ab1bb334f8d8c5fb27fe816f01","up":true},{"one":"043f24ce87fbd01f6f663f323ac6c156810a67b12efbec2f219ae9a882a4bd16","other":"0210146e650132dd0ceb8cea779cafc5485822eeeb9fcc17f292af5cef6e9632","up":true},{"one":"f1fcfc064446a432ac7a21efc386fa3d053d0ca4540fd4ddbbb253ec11f9ce94","other":"f048e37427d68c4134a115cc7df6a8924ea95f24e7f2435372ae331e24b5b7fd","up":true},{"one":"abfabc2964ce2f7b8b3702e897a8cc83fb0c009d5af9670b889db582e57ef7c1","other":"b60deba7c8676305b67af41d2de866bddd72edc0667d427e51fc3f2e91412b45","up":true},{"one":"388d46d6ea3fd92aa959b04d597ff1096d6133f171181c8fb54e52935b62d33c","other":"3e4470c3278bb723d01e1f058cf355bec8d3beca8f589cca6e835d59437591a6","up":true},{"one":"388d46d6ea3fd92aa959b04d597ff1096d6133f171181c8fb54e52935b62d33c","other":"3f3e66f7c8fc7daae8a7a84a9c90f1ea64faa43930f36fc8f11e2d7b751f8ee9","up":true},{"one":"0f5e34b25f6851d0f3b572c64420de0aec01754bea0854bd7e9910b4b1cc66c3","other":"0592048ae4e6a3000f36e8e1c0e1328c26c0d4bb6369d6cd867a08d847dacb9e","up":true},{"one":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","other":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","up":true},{"one":"ceee9ac63786ad68d884f5d018248ae1b7b60f0ad434776d29bd4dfcdc0f04c1","other":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","up":true},{"one":"c8fe626c60d6cd15912a22d2c5872f10a8f8aef5b6f6b2f1070edec6670bf8b1","other":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","up":true},{"one":"7294223c0ab27851df8c25418acf09a28efa95fc238252af8a68390db07b77e4","other":"730749922ce3dcd8e249b988dbfb950c15473a2f6fd5cfe2a8126995fb1dc186","up":true},{"one":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","other":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","up":true},{"one":"0f5e34b25f6851d0f3b572c64420de0aec01754bea0854bd7e9910b4b1cc66c3","other":"03f5f1be5c1ae35469333a5b08622372f65ef8f233746afde56d2647c5273630","up":true},{"one":"b4c7555a38d1d4e3bd1d01bcc47a84efd6128e8cc0fd803362da8b341f984a4d","other":"b310299bef4f4e84a1ecc960143e4b1bbe98cd4eb8352458c477569ca9994c20","up":true},{"one":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","other":"f9243ad864859edd351592d24abdf5c9c4e37f4ffe2ae1201c45f26576ee5725","up":true},{"one":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","other":"3dca1ae488cc497fe738c630465f987b419a5bb259fdfa17fe7130e238ca3022","up":true},{"one":"985c14910d922bbd1556c30620e3ab72170da6553e658dd3806d773a734a1c93","other":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","up":true},{"one":"f048e37427d68c4134a115cc7df6a8924ea95f24e7f2435372ae331e24b5b7fd","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"cb69f673a54a75de300d1d1ab989caa04579504cbcaf78f03bc192c9578ec5a0","other":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","up":true},{"one":"79abd33d150fdbcbce6f55f3d101608b80e03ede7e4627bc06ba5c1b001538a4","other":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","up":true},{"one":"95e0529227286d442cdeeed428614f99c19f9223426e6f6cefbef2d9d47ce96f","other":"9390ef61753fcf9dae5e585a85650287fe1f792538721a2cf767adc44c5e7ce0","up":true},{"one":"194ad126c41c487d95c83b19ed2bb053e4d2ec0605952d3a396dd4304e8e363c","other":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","up":true},{"one":"aa504681c991c69015c7ef0469902c44fc4e900356b181aa252a1280ffbd2e3b","other":"a80b39cf280fa80b323370cfc37e8e3ff38de47d5de445c500f36c8cf2390b53","up":true},{"one":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","other":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","up":true},{"one":"6d219b0ea4a1620a7ba051cc478da6df9ea443e87dde79e963ec789a5bad7459","other":"61431b36dd5e311475fd2263650f90731c07b728c1052aedf5bb98d414f07e8f","up":true},{"one":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","other":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","up":true},{"one":"3af3bb6c4343a19e3c0f9a89697cd70a3c79c64b1e40afa59b188c5553eed567","other":"396b256b223f346a490844895b9f1046b4790d624af1c43ecae1de318c7214a8","up":true},{"one":"f156942ef74790d1b1b5522f83d7099a4b16bc33f6f4ab2f3e1d7dc5bc6b0529","other":"f1fcfc064446a432ac7a21efc386fa3d053d0ca4540fd4ddbbb253ec11f9ce94","up":true},{"one":"d8222a2c177f0cd17baae28327af5c13f2fb551dd01298d17855ce7f6f4f6189","other":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","up":true},{"one":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","other":"e3c95a82f373da4522533252549c1d68b6f7621a887feb66199cfd4b7a35b6fc","up":true},{"one":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","other":"571638637156bdaf92d9c715647a1cf64e91877c2d998658b7c6cd27522271bd","up":true},{"one":"63304df8e9960f7a91a6185ce0fb69e7061f2647b9e21cce1237f087ce34a78a","other":"604c6bfab4f5d16d5b786318cf8f9e0197fc18e5d8a8e6f4fd345e653d2abe17","up":true},{"one":"9c01d016aaf11f9e7b048ba10cea3c351d9ab5c8c66e8605f78c06772708985c","other":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","up":true},{"one":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","other":"def4f0b988942a8699f6b500cc2531c7dea065e3dc5bdcbeb26bca8f00e1d3a6","up":true},{"one":"7dc63c8146ed092aa4c9539abd112dc3a703631e364a62fd74b192eee5778123","other":"7de7eb0b73f3b02b998e592e0fc51b26c0c3ae6695d41897874d2a3d8ce3554b","up":true},{"one":"3d6baa9da17c330fd34a40cecf331877d4a561f6b4f711cd5adf41bd77d68748","other":"3dca1ae488cc497fe738c630465f987b419a5bb259fdfa17fe7130e238ca3022","up":true},{"one":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","other":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","up":true},{"one":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","other":"f5dd00d94488f3387d64ccc2115cd91e5073c06de36d195a5c1a95db23cd5c68","up":true},{"one":"f78a54ba4b3fa75cdb542bdcd6da0d49986a5f7e7939959a51b3b48a87605654","other":"f0e209f5d6f69f510986085461dde294750cf0e7c2918f01a5c1419ac629de22","up":true},{"one":"14418557af44e33fc8d46369426c3e2fc91622cb08779b3fefce706969ec2163","other":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","up":true},{"one":"86f71d23eb4efeeed4679be7a3d0f58f98b83d23b3e0a8b73f60f1535b51f25c","other":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","up":true},{"one":"86f71d23eb4efeeed4679be7a3d0f58f98b83d23b3e0a8b73f60f1535b51f25c","other":"88da9dccda89c111dde40318fd1e5239e77b670f5a1b88a3e9320b7350885e31","up":true},{"one":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","other":"f4eee71a143722ea8288279876bf5687c1ed65eede0129e987e53f5ec76d8543","up":true},{"one":"1b1eca7b35086d8b217431c1d6a01fc07dc8bc816196a80015c9e163bed676ff","other":"1b7250dae1f2b07504b381d28084b751a26ac791a1ed842ea5746826ab85ba22","up":true},{"one":"7d941474f90981a03c95af4cf68be163d01a317e202ac0db82e25f46fc3db8aa","other":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","up":true},{"one":"d68f504755b48f8d41570169dd3544ef1caea2ab1bb334f8d8c5fb27fe816f01","other":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","up":true},{"one":"d3d254a3024a7aac01e97a5f0aff13756741867e599bc94c068f0d1d19ca8176","other":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","up":true},{"one":"c7230d8d56b65a66ab721a8268ed436af78210aae7d068cc2b8afe39b6e61c81","other":"c4844b8046d1c47f699ee1c232d945829105d45af26fba722ce4d0a937c99631","up":true},{"one":"c7703c4697637f40a50a5d0b656c4e54010ef1277b64d251ccd0752512e246e9","other":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","up":true},{"one":"d8b0425e4cb5134ddb2de5c05e0ae9b861bd67409c1446ca228318f79f5dd1f6","other":"dc3ec6529cec19709403e473d4546ba07f7aaaaec5be7cb0618246a4c0458944","up":true},{"one":"f78a54ba4b3fa75cdb542bdcd6da0d49986a5f7e7939959a51b3b48a87605654","other":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","up":true},{"one":"a80b39cf280fa80b323370cfc37e8e3ff38de47d5de445c500f36c8cf2390b53","other":"abfabc2964ce2f7b8b3702e897a8cc83fb0c009d5af9670b889db582e57ef7c1","up":true},{"one":"86f71d23eb4efeeed4679be7a3d0f58f98b83d23b3e0a8b73f60f1535b51f25c","other":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","up":true},{"one":"c4632e164170d35e91fce46f660265f73795f1d840effce5ab2aae18c73003d7","other":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","up":true},{"one":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","other":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","up":true},{"one":"7a4118d2ce16e8258cc2cdf31c5891945e3787c46c0836fc916601584609b7db","other":"79abd33d150fdbcbce6f55f3d101608b80e03ede7e4627bc06ba5c1b001538a4","up":true},{"one":"193e52ce29062ceca4f468b49655a06e7d41f6d26ff0cab42c1d896b70bb9218","other":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","up":true},{"one":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","other":"be0ab3ddc656568900d8e1f42394cc942464596b7565db40f6c5a0c24ee942f2","up":true},{"one":"7471be56a2f617783d5a1ae4390794ad24f453259b7c1f8f65be8f71c8bf8bf7","other":"77ec3a73ce1d597d307e87ea8d026baae2a7df4d288a657f240eaaaae7fdb456","up":true},{"one":"7fa47192bef70d34223a0dad181da09b55e99a1d940e8c48863aefb63c9fb733","other":"7dc63c8146ed092aa4c9539abd112dc3a703631e364a62fd74b192eee5778123","up":true},{"one":"f4e0d91ca58ed616918bf268565c2edb4176293e851b8d29cbbda55bdc608534","other":"f4eee71a143722ea8288279876bf5687c1ed65eede0129e987e53f5ec76d8543","up":true},{"one":"06aa2f43956f978abe7b8f8f78a5a8cd59decb66ec54b7c81ae50c3752798c5e","other":"043f24ce87fbd01f6f663f323ac6c156810a67b12efbec2f219ae9a882a4bd16","up":true},{"one":"df5ea72c0c257bbea9b43901a1e406bf6055a307d083367e7f1d944e65c2b3cd","other":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","up":true},{"one":"f5dd00d94488f3387d64ccc2115cd91e5073c06de36d195a5c1a95db23cd5c68","other":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","up":true},{"one":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","other":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","up":true},{"one":"1d5fc65b4121cccaff0d2a853c6bbff0c10b4ec28cfc5ef5cbea8bc28bc4cbec","other":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","up":true},{"one":"c99c6a2a2fe7f4613c8d918404c956eae0a00fce544c9eeba5da640e5e958874","other":"c4844b8046d1c47f699ee1c232d945829105d45af26fba722ce4d0a937c99631","up":true},{"one":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","other":"f1fcfc064446a432ac7a21efc386fa3d053d0ca4540fd4ddbbb253ec11f9ce94","up":true},{"one":"a6720426faa1ea7e2d55c1c5ad05ba2e8c534d120ebb5e1375cf769d73191708","other":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","up":true},{"one":"3dca1ae488cc497fe738c630465f987b419a5bb259fdfa17fe7130e238ca3022","other":"4b00ab74395ab00e25470c744b8d32f44152f5419b43540fec1fbbaf491f52c0","up":true},{"one":"fb93417c597050f8deeb3098475f6cf0d213926332b59c25a538c1b4e6af89c0","other":"f915fbbce2ef58a4378f62b9c749764e968da46df7bed494b35a422d0359a5dd","up":true},{"one":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","other":"de827c6af1d3423fa60bf50ca6e12172d21a9fc39a6360e2804f83003710b534","up":true},{"one":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","other":"1d942c85568f640497c46a9bd5a9825bc6fb05571203a3f0f14a3032602d81fc","up":true},{"one":"3dca1ae488cc497fe738c630465f987b419a5bb259fdfa17fe7130e238ca3022","other":"4a828523731caf47f9aa2dfa20f8a8895e9a6562b3a034574639a229a733b4a6","up":true},{"one":"c99c6a2a2fe7f4613c8d918404c956eae0a00fce544c9eeba5da640e5e958874","other":"c8fe626c60d6cd15912a22d2c5872f10a8f8aef5b6f6b2f1070edec6670bf8b1","up":true},{"one":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","other":"dc3ec6529cec19709403e473d4546ba07f7aaaaec5be7cb0618246a4c0458944","up":true},{"one":"a6720426faa1ea7e2d55c1c5ad05ba2e8c534d120ebb5e1375cf769d73191708","other":"a80b39cf280fa80b323370cfc37e8e3ff38de47d5de445c500f36c8cf2390b53","up":true},{"one":"1b7250dae1f2b07504b381d28084b751a26ac791a1ed842ea5746826ab85ba22","other":"123fe1d442c7dc905a7206e712fbd9cea640295a6823b39b1945629ffa89e895","up":true},{"one":"259d3874697a0c3192f973c620880b2a86567289f5222d07eb91a51867352559","other":"245495250d38095106022d3fc905eb7eb6f54adb2cb0bacdda0b305974eaa50a","up":true},{"one":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","other":"f9243ad864859edd351592d24abdf5c9c4e37f4ffe2ae1201c45f26576ee5725","up":true},{"one":"1e4441309404b0dd8dd9e8b1fe78708b015a0b0278ce86bcdaabc7d2d691791a","other":"1d5fc65b4121cccaff0d2a853c6bbff0c10b4ec28cfc5ef5cbea8bc28bc4cbec","up":true},{"one":"daa2c427e8ecc566e634908fb0d47077d8e79854bf884342c06c4bcb355e80e6","other":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","up":true},{"one":"0f81caba7d2f752fc670829f74565114bcf10c5b97d7f93252b467d0eee278b3","other":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","up":true},{"one":"3dca1ae488cc497fe738c630465f987b419a5bb259fdfa17fe7130e238ca3022","other":"31edba65e539a9a3c7107a131c4fb4b5a8e2c2d4e2db7874a0116c173961e27c","up":true},{"one":"4cf655b03778bcb41c32664f13168dc32968354468c8aa699de52b43a2e0dc7d","other":"4d447ea80bc126e57b9fc91203a310c5f4b242df5ad70d5087d16697a626e601","up":true},{"one":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","other":"571638637156bdaf92d9c715647a1cf64e91877c2d998658b7c6cd27522271bd","up":true},{"one":"275c93677aefd92ab38f9e5b5b4d29f90e809ccaf77b088102071aba26421f3e","other":"245495250d38095106022d3fc905eb7eb6f54adb2cb0bacdda0b305974eaa50a","up":true},{"one":"a749372cccb7001a480b9416c41305bd1d3bc417e15669ae587d4375f2bbcbf7","other":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","up":true},{"one":"bfeca898b1755a001eb0bcf8f5ad5713a550d0597f99ef7967ea4e7add8d3e81","other":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","up":true},{"one":"43afd835930eac91c0c8947001dd60d8da9112c19cf3f1356589b58b48962166","other":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","up":true},{"one":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","other":"f4e0d91ca58ed616918bf268565c2edb4176293e851b8d29cbbda55bdc608534","up":true},{"one":"a6720426faa1ea7e2d55c1c5ad05ba2e8c534d120ebb5e1375cf769d73191708","other":"a485db6ec1929aaf695a84294f52905aa602584954e9ef71b4e18f7b847da31d","up":true},{"one":"c99c6a2a2fe7f4613c8d918404c956eae0a00fce544c9eeba5da640e5e958874","other":"ceee9ac63786ad68d884f5d018248ae1b7b60f0ad434776d29bd4dfcdc0f04c1","up":true},{"one":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","other":"ed6570e63fc0e2e2d6290645c4339b70a0f1c14064d286b4e0ba143607bd70c8","up":true},{"one":"7851f67196b812fd6cab7d577b90e254c6f9ea33259c60b5efd91654bd8db44c","other":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","up":true},{"one":"c63e796efdc44712403199c11c3e5ff28e5d6a4d9ecdf3e95620fbd6afb1bc2d","other":"c7703c4697637f40a50a5d0b656c4e54010ef1277b64d251ccd0752512e246e9","up":true},{"one":"1a02108d9149739be6ba85ddd1e8a3961adeec38f77fcef95ab57f8f2f39f4bb","other":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","up":true},{"one":"3dca1ae488cc497fe738c630465f987b419a5bb259fdfa17fe7130e238ca3022","other":"3f3e66f7c8fc7daae8a7a84a9c90f1ea64faa43930f36fc8f11e2d7b751f8ee9","up":true},{"one":"f4eee71a143722ea8288279876bf5687c1ed65eede0129e987e53f5ec76d8543","other":"f5dd00d94488f3387d64ccc2115cd91e5073c06de36d195a5c1a95db23cd5c68","up":true},{"one":"daa2c427e8ecc566e634908fb0d47077d8e79854bf884342c06c4bcb355e80e6","other":"def4f0b988942a8699f6b500cc2531c7dea065e3dc5bdcbeb26bca8f00e1d3a6","up":true},{"one":"1a02108d9149739be6ba85ddd1e8a3961adeec38f77fcef95ab57f8f2f39f4bb","other":"1835f62fe3889f43ba7b4b577869ec764e96fa9a051f1301fb96795daa8b0755","up":true},{"one":"265d7fc553fb8dc016d7c6ccc31fc372912a11e9e8da0e5822e371a91d82e857","other":"245495250d38095106022d3fc905eb7eb6f54adb2cb0bacdda0b305974eaa50a","up":true},{"one":"a485db6ec1929aaf695a84294f52905aa602584954e9ef71b4e18f7b847da31d","other":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","up":true},{"one":"0592048ae4e6a3000f36e8e1c0e1328c26c0d4bb6369d6cd867a08d847dacb9e","other":"043f24ce87fbd01f6f663f323ac6c156810a67b12efbec2f219ae9a882a4bd16","up":true},{"one":"f4eee71a143722ea8288279876bf5687c1ed65eede0129e987e53f5ec76d8543","other":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","up":true},{"one":"8163eebe696123817a6d998d7781612931df6fa2fd0293c0d0dd395d031c53f6","other":"89ee628583b6e0345ceb312c2e2cbf5ac410eb00531be903a4b94b60b639993b","up":true},{"one":"baf32cface953bd22141970b691567ed375f06043fe31de354598fb4230fb0a3","other":"b8a7578a22a1e9963deaeb7e414137bee0f4590bcb93d327e9525d6dc03b81a2","up":true},{"one":"0f5e34b25f6851d0f3b572c64420de0aec01754bea0854bd7e9910b4b1cc66c3","other":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","up":true},{"one":"6ea59b849a9754c022219146efc27fa6561e5aca8338a111847259695198267b","other":"604c6bfab4f5d16d5b786318cf8f9e0197fc18e5d8a8e6f4fd345e653d2abe17","up":true},{"one":"1a02108d9149739be6ba85ddd1e8a3961adeec38f77fcef95ab57f8f2f39f4bb","other":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","up":true},{"one":"1d5fc65b4121cccaff0d2a853c6bbff0c10b4ec28cfc5ef5cbea8bc28bc4cbec","other":"1d942c85568f640497c46a9bd5a9825bc6fb05571203a3f0f14a3032602d81fc","up":true},{"one":"86128689b03706f9a86baf4753154afbb0c535f84089039ef5610d82fb969a6e","other":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","up":true},{"one":"167306fb0e24ddd775c2ac0722103ce5db98bb0ef60159b91f584475d34cb5bf","other":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","up":true},{"one":"67831469d7f9b779df4318cda9d4310c53fdd66332af3740e654233ceb3fbcbc","other":"66100e4f9d103d935164f39ea3f0332708f3825ebcf31ac6f8c9714c75a50860","up":true},{"one":"9390ef61753fcf9dae5e585a85650287fe1f792538721a2cf767adc44c5e7ce0","other":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","up":true},{"one":"b60deba7c8676305b67af41d2de866bddd72edc0667d427e51fc3f2e91412b45","other":"b45d1aee940e5d2c71db22e2afbe908d5967e437fbb5bd051edea86ea4ac9d30","up":true},{"one":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","other":"88da9dccda89c111dde40318fd1e5239e77b670f5a1b88a3e9320b7350885e31","up":true},{"one":"d6d2d35b3819e03b39d7146c4dd1b53fb7b70681c8860464650d5eb2d09dda2c","other":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","up":true},{"one":"9fee945c89d6bb59e77a24183c2855f4eece0b5d3266f750ea95483eae1e1871","other":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","up":true},{"one":"4f90f53754ee50357491dbab7c3e6748b40133e3339920b32f4c3457f4258477","other":"4cf655b03778bcb41c32664f13168dc32968354468c8aa699de52b43a2e0dc7d","up":true},{"one":"4fd6a4b99b1ba76775594673421c8eea175c114519c8554798c2c364175bbaae","other":"4cf655b03778bcb41c32664f13168dc32968354468c8aa699de52b43a2e0dc7d","up":true},{"one":"be0ab3ddc656568900d8e1f42394cc942464596b7565db40f6c5a0c24ee942f2","other":"b71052b072cb98fb79151fd0c6044828e535a0547cace931183c7e4e7f227698","up":true},{"one":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","other":"d7abbad732948b66476e1833f0af6bb6250dbbf4ac71b9c75b4fc634b617e8d7","up":true},{"one":"9a82d22b29c88eaca2f20a62e9e63675904065e72c70ce2fbbefc0c9d2e53e90","other":"985c14910d922bbd1556c30620e3ab72170da6553e658dd3806d773a734a1c93","up":true},{"one":"42d403f4ee649ebcb94753e065391afd0b4da995a2628696afdb4b851e9f3774","other":"46c5a266dd46041a62f21946290aad736d2298277bbbb16b1efb4490d9d31fa8","up":false},{"one":"42c019ddad2fde0f0c7f5041eb584996976844f8b2e08e20458437578d0257ef","other":"4f90f53754ee50357491dbab7c3e6748b40133e3339920b32f4c3457f4258477","up":true},{"one":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","other":"af358f626738d2e5d17060abbaf24cb0124a48c5edb8f3baeda30bea7b403dfa","up":true},{"one":"daa2c427e8ecc566e634908fb0d47077d8e79854bf884342c06c4bcb355e80e6","other":"d8222a2c177f0cd17baae28327af5c13f2fb551dd01298d17855ce7f6f4f6189","up":true},{"one":"c98d389a6d8e6eb5a1eb5a6f235857d5d939d30e51c16d80c039c9e0eb2f7eca","other":"c99c6a2a2fe7f4613c8d918404c956eae0a00fce544c9eeba5da640e5e958874","up":true},{"one":"c98d389a6d8e6eb5a1eb5a6f235857d5d939d30e51c16d80c039c9e0eb2f7eca","other":"c883e2850f0a901af58130e87c4f81d898b03be8b88b580ae7075e6c40b33d8e","up":false},{"one":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","other":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","up":true},{"one":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","other":"89ee628583b6e0345ceb312c2e2cbf5ac410eb00531be903a4b94b60b639993b","up":true},{"one":"670d1c31407b4f34bb955031e9a3ab7980273155080b459a082687d53a3b42a6","other":"66100e4f9d103d935164f39ea3f0332708f3825ebcf31ac6f8c9714c75a50860","up":true},{"one":"abfabc2964ce2f7b8b3702e897a8cc83fb0c009d5af9670b889db582e57ef7c1","other":"b4c7555a38d1d4e3bd1d01bcc47a84efd6128e8cc0fd803362da8b341f984a4d","up":true},{"one":"95e0529227286d442cdeeed428614f99c19f9223426e6f6cefbef2d9d47ce96f","other":"9294e5b39a1cd4230400e033da1ac1f79a4025adab41ce8c0e0e47dda2418cb5","up":true},{"one":"8c615fbce8dd93846d810857d03235291732e4307cad0e66ffb23f053d2f0ceb","other":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","up":true},{"one":"ca81f50b5b4921ef7f584972609e1774da124233ebde0bf8f2ca8383017c8a13","other":"c99c6a2a2fe7f4613c8d918404c956eae0a00fce544c9eeba5da640e5e958874","up":true},{"one":"cb69f673a54a75de300d1d1ab989caa04579504cbcaf78f03bc192c9578ec5a0","other":"c99c6a2a2fe7f4613c8d918404c956eae0a00fce544c9eeba5da640e5e958874","up":true},{"one":"e67d72c8c72bbc7bec1d4e936d65adbeab75d1b6cafdf2c39e38bfc723cceebf","other":"e3c95a82f373da4522533252549c1d68b6f7621a887feb66199cfd4b7a35b6fc","up":true},{"one":"9c0c7b6da33520440b181a5fcc30aa8a7576ec9c6f64acef0c9525d05a2ee393","other":"9c01d016aaf11f9e7b048ba10cea3c351d9ab5c8c66e8605f78c06772708985c","up":true},{"one":"9c0c7b6da33520440b181a5fcc30aa8a7576ec9c6f64acef0c9525d05a2ee393","other":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","up":true},{"one":"b310299bef4f4e84a1ecc960143e4b1bbe98cd4eb8352458c477569ca9994c20","other":"b60deba7c8676305b67af41d2de866bddd72edc0667d427e51fc3f2e91412b45","up":true},{"one":"6544c4a403d9809697420ff9840ea9de0c9ba6491de28dac288db6257f2dc218","other":"670d1c31407b4f34bb955031e9a3ab7980273155080b459a082687d53a3b42a6","up":true},{"one":"4019ce1dc667d90a85021b5fbf49ad77c1de7fd3443a40a17bdfaf5175efa1e1","other":"42c019ddad2fde0f0c7f5041eb584996976844f8b2e08e20458437578d0257ef","up":true},{"one":"9c0c7b6da33520440b181a5fcc30aa8a7576ec9c6f64acef0c9525d05a2ee393","other":"9eec5d5357732a7acd26cc8aa0bc65f6c6069aedf4f3ab4a6790c7fff6bc2695","up":true},{"one":"6d219b0ea4a1620a7ba051cc478da6df9ea443e87dde79e963ec789a5bad7459","other":"670d1c31407b4f34bb955031e9a3ab7980273155080b459a082687d53a3b42a6","up":true},{"one":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","other":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","up":true},{"one":"569567603f057518a9dade31f2d12c57c5dde05ee50586ef0f0a6f98f4f63660","other":"571638637156bdaf92d9c715647a1cf64e91877c2d998658b7c6cd27522271bd","up":true},{"one":"abfabc2964ce2f7b8b3702e897a8cc83fb0c009d5af9670b889db582e57ef7c1","other":"aa889f0faaec1a5b7033151ce9d3d68466b128ff81dcb7befc2ce885d9236c77","up":true},{"one":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","other":"1d9364dcc19e9c94459be42a8474207806813d55ac768ea8e74228fbd73be591","up":true},{"one":"043f24ce87fbd01f6f663f323ac6c156810a67b12efbec2f219ae9a882a4bd16","other":"05e8f826e49722ce09edda07f9016688df5032bc0183589e3c94afda6dd63074","up":true},{"one":"6d219b0ea4a1620a7ba051cc478da6df9ea443e87dde79e963ec789a5bad7459","other":"67831469d7f9b779df4318cda9d4310c53fdd66332af3740e654233ceb3fbcbc","up":true},{"one":"0eeed90814e9408c94e81296f7b681ac18edacda5eb611af7f69306524f188b8","other":"06aa2f43956f978abe7b8f8f78a5a8cd59decb66ec54b7c81ae50c3752798c5e","up":true},{"one":"d8222a2c177f0cd17baae28327af5c13f2fb551dd01298d17855ce7f6f4f6189","other":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","up":true},{"one":"043f24ce87fbd01f6f663f323ac6c156810a67b12efbec2f219ae9a882a4bd16","other":"05ec2c85c83bbb8edeeba98fd2ef4b765114f5dc36a88175f5017c09e43b7a5a","up":true},{"one":"ad36b802e9b01727e516879c0055e3c46b810845794f7772ad85e8062111b167","other":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","up":true},{"one":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","other":"dc3ec6529cec19709403e473d4546ba07f7aaaaec5be7cb0618246a4c0458944","up":false},{"one":"ae715a40e9bf50b4eb954c6b87efbf1a6e7abc3da6d84055e4591aadf5bad5c8","other":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","up":true},{"one":"245495250d38095106022d3fc905eb7eb6f54adb2cb0bacdda0b305974eaa50a","other":"237459ba37141f18b3cd40f5ec9cdaf6789fb87aa4511d489fdd98a960ef2883","up":true},{"one":"7dc63c8146ed092aa4c9539abd112dc3a703631e364a62fd74b192eee5778123","other":"7d45f1e91bcd683d870422b2f591c76f6ab07ff4b079f35c280ce46f7fc364e1","up":true},{"one":"245495250d38095106022d3fc905eb7eb6f54adb2cb0bacdda0b305974eaa50a","other":"2e4c8c371968f3a3cea2e2c1c9bc45f8a0cbed49a7a1fbade17ea34a6e5749cb","up":false},{"one":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","other":"ddf8ffe61fdd1440ff2d50d42619d52eb125ab8aa1e8e5ce16274cc4f4728b19","up":true},{"one":"f78a54ba4b3fa75cdb542bdcd6da0d49986a5f7e7939959a51b3b48a87605654","other":"f5dd00d94488f3387d64ccc2115cd91e5073c06de36d195a5c1a95db23cd5c68","up":true},{"one":"d8222a2c177f0cd17baae28327af5c13f2fb551dd01298d17855ce7f6f4f6189","other":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","up":true},{"one":"7dc63c8146ed092aa4c9539abd112dc3a703631e364a62fd74b192eee5778123","other":"759eacbb4bc23d8f6826d5ff3d38343e4143c973488525ca747e66e6002e2364","up":true},{"one":"86f71d23eb4efeeed4679be7a3d0f58f98b83d23b3e0a8b73f60f1535b51f25c","other":"8163eebe696123817a6d998d7781612931df6fa2fd0293c0d0dd395d031c53f6","up":true},{"one":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","other":"af5fcb3714f6da5fe576fed6312f59042cbaa54ac8bd86e7afcd0753743487db","up":true},{"one":"4a81ee929074bd6d9932fb4d970f5207db8680bda0e951aa72a6bd64df45108d","other":"487edf32027381256a864bc2e9bca175f7216cc1234731bd4dc90d2cbad6975d","up":true},{"one":"d8b0425e4cb5134ddb2de5c05e0ae9b861bd67409c1446ca228318f79f5dd1f6","other":"d8222a2c177f0cd17baae28327af5c13f2fb551dd01298d17855ce7f6f4f6189","up":true},{"one":"d8b0425e4cb5134ddb2de5c05e0ae9b861bd67409c1446ca228318f79f5dd1f6","other":"dae3ea40e4e6e040a4740a614c99ec6b9e644302135cf347ea6ec9ebc87a9f21","up":true},{"one":"99fba7334f47bfcf21331d6179bfb2eee2ecebd1ea23fd829c542844b10ed974","other":"985c14910d922bbd1556c30620e3ab72170da6553e658dd3806d773a734a1c93","up":true},{"one":"99aaaa7c98113955ecc0163943317f6ec08ae755e96c25c9f79ca1f529c60587","other":"985c14910d922bbd1556c30620e3ab72170da6553e658dd3806d773a734a1c93","up":true},{"one":"99dbf361b67d6c370fd4f201b059e93da50dd88cde2e25d0bcf3c73afbf13535","other":"985c14910d922bbd1556c30620e3ab72170da6553e658dd3806d773a734a1c93","up":true},{"one":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","other":"ecd2b502d4e9bf77369c62d1142a9fb4ab1071e751cc6971e9e381e953b0fc12","up":true},{"one":"f4e0d91ca58ed616918bf268565c2edb4176293e851b8d29cbbda55bdc608534","other":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","up":true},{"one":"c15d13aff7f5694fb8fc2a2154846ac703064133feb632e28830777bbfc7c016","other":"c64f6bca15d4ef21a37b69c8f8bdbbbfc34e2ce0fc612cbc6a937339c0ccbbdb","up":true},{"one":"5fab772fff883d04cf7e4713266fb1e98fa1746e12faeb04e87bcb37b8b61187","other":"5c5d90a825221bafb2ab726f90422c9a59f9bf97f181c0a2887db2a531798048","up":true},{"one":"bf5ac6f9b651895c8d75ab3ae7994f91981dbbcdce25a7cd718fc996da96946d","other":"be0ab3ddc656568900d8e1f42394cc942464596b7565db40f6c5a0c24ee942f2","up":true},{"one":"e4c3fa0700516da5650f794067eb1c90ba7eaab2b65019bf34552c26f8c1d9a6","other":"e787fd35b438dbae3d0283f7181fc4afbeb6f333201ad4cd1b46bf7094a6a85f","up":true},{"one":"e4c3fa0700516da5650f794067eb1c90ba7eaab2b65019bf34552c26f8c1d9a6","other":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","up":true},{"one":"7ffed78e11c71eccfdddf6a8e5e367831b26a6b11e4d3b60c5b9fc50f4bcdf33","other":"7d941474f90981a03c95af4cf68be163d01a317e202ac0db82e25f46fc3db8aa","up":true},{"one":"1b7250dae1f2b07504b381d28084b751a26ac791a1ed842ea5746826ab85ba22","other":"14418557af44e33fc8d46369426c3e2fc91622cb08779b3fefce706969ec2163","up":false},{"one":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","other":"fb93417c597050f8deeb3098475f6cf0d213926332b59c25a538c1b4e6af89c0","up":true},{"one":"e4c3fa0700516da5650f794067eb1c90ba7eaab2b65019bf34552c26f8c1d9a6","other":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","up":true},{"one":"43afd835930eac91c0c8947001dd60d8da9112c19cf3f1356589b58b48962166","other":"47f95708475c9d61ff2016e7707c4a3796f6c87313bd1d2bf91aa620113ba5f8","up":false},{"one":"c7230d8d56b65a66ab721a8268ed436af78210aae7d068cc2b8afe39b6e61c81","other":"c7703c4697637f40a50a5d0b656c4e54010ef1277b64d251ccd0752512e246e9","up":true},{"one":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","other":"566eb2e022a83d49380a94cf17c45955f47faeede4b23f97d357e6e7d7152dae","up":true},{"one":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","other":"ed1348b4037004b61bd4d6f9be1648112f835943a3b997b91ac931301311e007","up":true},{"one":"c3f356325eb1e61f2cfd8d80d085fb144c6887e1d710c9d133109b84b823425d","other":"c15d13aff7f5694fb8fc2a2154846ac703064133feb632e28830777bbfc7c016","up":true},{"one":"e3c95a82f373da4522533252549c1d68b6f7621a887feb66199cfd4b7a35b6fc","other":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","up":true},{"one":"1a833a4918d6f4ebc0c39ba3bfd4e9de16bb732cbacb963ff58ad3d3198f1336","other":"1b7250dae1f2b07504b381d28084b751a26ac791a1ed842ea5746826ab85ba22","up":true},{"one":"7829673eec0b413d62bb06184e740042e252d3b68b3d1cc174bf01e32f87736f","other":"7a4118d2ce16e8258cc2cdf31c5891945e3787c46c0836fc916601584609b7db","up":true},{"one":"1e42fcbd6735744e505f12f6b0858a9fd17c0a7736a69ab0b969245a3cb1d6e4","other":"1d07520f0ace85fd72f14c881f3d6a5511902b20b6e0f78d2a261191c072ba5d","up":true},{"one":"6ea59b849a9754c022219146efc27fa6561e5aca8338a111847259695198267b","other":"6544c4a403d9809697420ff9840ea9de0c9ba6491de28dac288db6257f2dc218","up":true},{"one":"1d5fc65b4121cccaff0d2a853c6bbff0c10b4ec28cfc5ef5cbea8bc28bc4cbec","other":"1c982d886cbac7e6ac5e786a27ad2f8bd42908fca94358cd2838acbc20b5fd31","up":true},{"one":"86128689b03706f9a86baf4753154afbb0c535f84089039ef5610d82fb969a6e","other":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","up":true},{"one":"1a02108d9149739be6ba85ddd1e8a3961adeec38f77fcef95ab57f8f2f39f4bb","other":"1b869ca5aec1ff0c9980b06b79084019752fd4557054e251c0091ee5d2c7d478","up":true},{"one":"8163eebe696123817a6d998d7781612931df6fa2fd0293c0d0dd395d031c53f6","other":"86128689b03706f9a86baf4753154afbb0c535f84089039ef5610d82fb969a6e","up":true},{"one":"be0ab3ddc656568900d8e1f42394cc942464596b7565db40f6c5a0c24ee942f2","other":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","up":true},{"one":"7de7eb0b73f3b02b998e592e0fc51b26c0c3ae6695d41897874d2a3d8ce3554b","other":"7d45f1e91bcd683d870422b2f591c76f6ab07ff4b079f35c280ce46f7fc364e1","up":true},{"one":"b60deba7c8676305b67af41d2de866bddd72edc0667d427e51fc3f2e91412b45","other":"b71052b072cb98fb79151fd0c6044828e535a0547cace931183c7e4e7f227698","up":true},{"one":"9fee945c89d6bb59e77a24183c2855f4eece0b5d3266f750ea95483eae1e1871","other":"99fba7334f47bfcf21331d6179bfb2eee2ecebd1ea23fd829c542844b10ed974","up":true},{"one":"b60deba7c8676305b67af41d2de866bddd72edc0667d427e51fc3f2e91412b45","other":"b4639751d67db0b32ad1846996fed0d5c59be76b77efaec5f66e64df35eefae8","up":true},{"one":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","other":"ceee9ac63786ad68d884f5d018248ae1b7b60f0ad434776d29bd4dfcdc0f04c1","up":true},{"one":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","other":"c8fe626c60d6cd15912a22d2c5872f10a8f8aef5b6f6b2f1070edec6670bf8b1","up":true},{"one":"94aa6cadb94dfc04e2ae3150aeeaeb73b6fc483482156fcabdca4c6596eaa098","other":"96b6ec2cda7c9e962bbc97e7916b92d69b112265c4456d30dade806e09fea62e","up":true},{"one":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","other":"2fd800d224f124e47038bf140ebd486f1d0ab93169c998d2dfabfd1d3ced96ed","up":true},{"one":"2a2253ec638947fe40b97489b2e2ec0df26ebc77d202276c1d208a3c802c336e","other":"2e9f7d37106b59d6f411dfa0392325d1fa279fe45c18a101e32ece6716e88946","up":false},{"one":"f3d3ba20652827b58c605a137e2f07daa5d139804eb4a9c4270129de3a0de759","other":"f1fcfc064446a432ac7a21efc386fa3d053d0ca4540fd4ddbbb253ec11f9ce94","up":true},{"one":"42c019ddad2fde0f0c7f5041eb584996976844f8b2e08e20458437578d0257ef","other":"43afd835930eac91c0c8947001dd60d8da9112c19cf3f1356589b58b48962166","up":true},{"one":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","other":"6544c4a403d9809697420ff9840ea9de0c9ba6491de28dac288db6257f2dc218","up":true},{"one":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","other":"887482890b190505bec49838e400f3f1ecb261039ace70bd60c368be94131ee3","up":true},{"one":"388d46d6ea3fd92aa959b04d597ff1096d6133f171181c8fb54e52935b62d33c","other":"3af3bb6c4343a19e3c0f9a89697cd70a3c79c64b1e40afa59b188c5553eed567","up":true},{"one":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","other":"d6d2d35b3819e03b39d7146c4dd1b53fb7b70681c8860464650d5eb2d09dda2c","up":true},{"one":"bc08af284ab661dcc1cb23859271590e50a09ff8af8073769c812901ea0b9080","other":"bfeca898b1755a001eb0bcf8f5ad5713a550d0597f99ef7967ea4e7add8d3e81","up":true},{"one":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","other":"0592048ae4e6a3000f36e8e1c0e1328c26c0d4bb6369d6cd867a08d847dacb9e","up":true},{"one":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","other":"03f5f1be5c1ae35469333a5b08622372f65ef8f233746afde56d2647c5273630","up":true},{"one":"1566e3c8e2873d2fbe0f1e9eedbd2cbd5c69161a461e37a48fdfb11d448601e8","other":"0f81caba7d2f752fc670829f74565114bcf10c5b97d7f93252b467d0eee278b3","up":true},{"one":"c8f9cc1fb3ec871e2ce03157f90cfe1c3b01a503c30afdc71d0e8b27ba3a8e19","other":"c99c6a2a2fe7f4613c8d918404c956eae0a00fce544c9eeba5da640e5e958874","up":false},{"one":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","other":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","up":true},{"one":"4a828523731caf47f9aa2dfa20f8a8895e9a6562b3a034574639a229a733b4a6","other":"487edf32027381256a864bc2e9bca175f7216cc1234731bd4dc90d2cbad6975d","up":true},{"one":"7dc63c8146ed092aa4c9539abd112dc3a703631e364a62fd74b192eee5778123","other":"77ec3a73ce1d597d307e87ea8d026baae2a7df4d288a657f240eaaaae7fdb456","up":true},{"one":"a6720426faa1ea7e2d55c1c5ad05ba2e8c534d120ebb5e1375cf769d73191708","other":"a03380e51f365445107b59009eaa9040873968cae477be5602efbbdf4a6414ec","up":true},{"one":"57d57ea21e4635857370be02618b158a2b6ed0a8236c67c47070ef41410b3cc4","other":"5571c936426702553d64564d56a909c628a8233ed436acc2a3b9f850c5c6b6f0","up":true},{"one":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","other":"92325e52babbf2b8ae3c37beec7e440d11efed2d103befa2c4a969315a2eab56","up":true},{"one":"7dc63c8146ed092aa4c9539abd112dc3a703631e364a62fd74b192eee5778123","other":"7471be56a2f617783d5a1ae4390794ad24f453259b7c1f8f65be8f71c8bf8bf7","up":true},{"one":"ad36b802e9b01727e516879c0055e3c46b810845794f7772ad85e8062111b167","other":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","up":true},{"one":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","other":"ae715a40e9bf50b4eb954c6b87efbf1a6e7abc3da6d84055e4591aadf5bad5c8","up":true},{"one":"1835f62fe3889f43ba7b4b577869ec764e96fa9a051f1301fb96795daa8b0755","other":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","up":true},{"one":"c15d13aff7f5694fb8fc2a2154846ac703064133feb632e28830777bbfc7c016","other":"c7230d8d56b65a66ab721a8268ed436af78210aae7d068cc2b8afe39b6e61c81","up":false},{"one":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","other":"edca23c0fccfd1ec66f4d39bb129462776b9091c2337f6afbfe8737a0ebc0d7d","up":true},{"one":"bfeca898b1755a001eb0bcf8f5ad5713a550d0597f99ef7967ea4e7add8d3e81","other":"be0ab3ddc656568900d8e1f42394cc942464596b7565db40f6c5a0c24ee942f2","up":true},{"one":"f644c73e2c7e29dd45506736ca4a0c2cab3f8294c01a6d5decc193cb2d19a473","other":"f5cc76a5a77d79b8f9d3dc2eb53af5f643583aa9392b361aa9578cdad81fd4a5","up":true},{"one":"185a7952985b8efef8ac8ee37283921a458a6a107cba0f7fa33da7480b05c4cf","other":"123fe1d442c7dc905a7206e712fbd9cea640295a6823b39b1945629ffa89e895","up":true},{"one":"adfc03118406bc5af4e3f3c6c89c3448c3fe4f9df20b15bcca9bbacf58ee5457","other":"af303f61e91621224d5968e387946d4e1dbf76d1e7ee6f8f2ae60991154d3734","up":true},{"one":"c4632e164170d35e91fce46f660265f73795f1d840effce5ab2aae18c73003d7","other":"c7230d8d56b65a66ab721a8268ed436af78210aae7d068cc2b8afe39b6e61c81","up":true},{"one":"baf32cface953bd22141970b691567ed375f06043fe31de354598fb4230fb0a3","other":"bc08af284ab661dcc1cb23859271590e50a09ff8af8073769c812901ea0b9080","up":true},{"one":"8163eebe696123817a6d998d7781612931df6fa2fd0293c0d0dd395d031c53f6","other":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","up":false},{"one":"7d941474f90981a03c95af4cf68be163d01a317e202ac0db82e25f46fc3db8aa","other":"7d45f1e91bcd683d870422b2f591c76f6ab07ff4b079f35c280ce46f7fc364e1","up":true},{"one":"3dca1ae488cc497fe738c630465f987b419a5bb259fdfa17fe7130e238ca3022","other":"3e85d2d3d6750f84500fcf523743e0c242943e69188cb83114adad1787e6c089","up":true},{"one":"6ea59b849a9754c022219146efc27fa6561e5aca8338a111847259695198267b","other":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","up":true},{"one":"e67d72c8c72bbc7bec1d4e936d65adbeab75d1b6cafdf2c39e38bfc723cceebf","other":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","up":true},{"one":"6d219b0ea4a1620a7ba051cc478da6df9ea443e87dde79e963ec789a5bad7459","other":"67a246540c1fa19a0856cf5a99480a3826fc91e125b3bf5ec1e47a2c5aff54be","up":false},{"one":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","other":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","up":true},{"one":"df25c443ce1a7b63fe5d89bffcfeb49366a623525660fde8ea509a1934aa050c","other":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","up":true},{"one":"fd2df4df8f5f7c7cce198cdff07f2adc0e68b646fcc046363d284635c68bbb93","other":"fa7483c3b8652029a3c8599ec3cc1cec5a75181c564a7f6c50094370d43036ff","up":true},{"one":"e44b5f4a563a36e50e57253dfddac4cb5a92137f615f46f638995b4b18863f3b","other":"e76a40477e69f637ff70f1bf393b1dc96a946384340b341d0be97ba01dc12660","up":true},{"one":"e3c95a82f373da4522533252549c1d68b6f7621a887feb66199cfd4b7a35b6fc","other":"e6491ee529119721d2c1ce16d220f5d055ba35978f4d7c66c4ec2e3ac36183fa","up":true},{"one":"e3c95a82f373da4522533252549c1d68b6f7621a887feb66199cfd4b7a35b6fc","other":"e787fd35b438dbae3d0283f7181fc4afbeb6f333201ad4cd1b46bf7094a6a85f","up":true},{"one":"c4632e164170d35e91fce46f660265f73795f1d840effce5ab2aae18c73003d7","other":"c63e796efdc44712403199c11c3e5ff28e5d6a4d9ecdf3e95620fbd6afb1bc2d","up":true},{"one":"ae6578693ed933e36a7f499adde340c79bdca0f5f57334dba5646bc1e4d7b717","other":"af358f626738d2e5d17060abbaf24cb0124a48c5edb8f3baeda30bea7b403dfa","up":true},{"one":"d8222a2c177f0cd17baae28327af5c13f2fb551dd01298d17855ce7f6f4f6189","other":"dc86cf92a9640712455824fc7e1eabb939f60370d1be8fff55586c4aaa8b3331","up":true},{"one":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","other":"d68f504755b48f8d41570169dd3544ef1caea2ab1bb334f8d8c5fb27fe816f01","up":true},{"one":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","other":"8ac85afde0c46f53269dc42863e9599cfe7ed0ca3222e3718d83ea6481947ab3","up":true},{"one":"6ea59b849a9754c022219146efc27fa6561e5aca8338a111847259695198267b","other":"6d219b0ea4a1620a7ba051cc478da6df9ea443e87dde79e963ec789a5bad7459","up":true},{"one":"8c89e181c6fb18078b404d47b10c70d57d6643393505ed8ac88e63236ea224a0","other":"8ae6a3d0cc665664aed35acd396ef18f642afd2b1e1e23817174b96bedf26793","up":true},{"one":"d3fd74e6049dbeceb5b78ff4681b000da5ad8a3860d4a1070541b5f527d2cc84","other":"d6f37555e9f35a3ef3b8b4d5c18cf0702ed99c1c8b5b0a8554deddcbe542b88c","up":true},{"one":"9461928ad83c196e6a018ea2af138488346fd9c64147b544287e4473ba95ed90","other":"9294e5b39a1cd4230400e033da1ac1f79a4025adab41ce8c0e0e47dda2418cb5","up":false},{"one":"f78a54ba4b3fa75cdb542bdcd6da0d49986a5f7e7939959a51b3b48a87605654","other":"f4e0d91ca58ed616918bf268565c2edb4176293e851b8d29cbbda55bdc608534","up":true},{"one":"6ea59b849a9754c022219146efc27fa6561e5aca8338a111847259695198267b","other":"66100e4f9d103d935164f39ea3f0332708f3825ebcf31ac6f8c9714c75a50860","up":false},{"one":"f78a54ba4b3fa75cdb542bdcd6da0d49986a5f7e7939959a51b3b48a87605654","other":"f4eee71a143722ea8288279876bf5687c1ed65eede0129e987e53f5ec76d8543","up":true},{"one":"aca1a0237ccef04bbfff4359cf12945880e485eaad810fe1bc2f2569cc42fea3","other":"adfc03118406bc5af4e3f3c6c89c3448c3fe4f9df20b15bcca9bbacf58ee5457","up":true},{"one":"baf32cface953bd22141970b691567ed375f06043fe31de354598fb4230fb0a3","other":"be0ab3ddc656568900d8e1f42394cc942464596b7565db40f6c5a0c24ee942f2","up":true}]} \ No newline at end of file diff --git a/swarm/pss/testdata/snapshot_3.json b/swarm/pss/testdata/snapshot_3.json deleted file mode 100644 index 38ce68f34012..000000000000 --- a/swarm/pss/testdata/snapshot_3.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes":[{"node":{"config":{"id":"73d6ad4a75069dced660fa4cb98143ee5573df7cb15d9a295acf1655e9683384","private_key":"e567b7d9c554e5102cdc99b6523bace02dbb8951415c8816d82ba2d2e97fa23b","name":"node01","services":["bzz","pss"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"6e8da86abb894ab35044c8c455147225df96cab498da067a118f1fb9a417f9e3","private_key":"c7526db70acd02f36d3b201ef3e1d85e38c52bee6931453213dbc5edec4d0976","name":"node02","services":["bzz","pss"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"8a1eb78ff13df318e7f8116dffee98cd7d9905650fa53f16766b754a63f387ac","private_key":"61b5728f59bc43080c3b8eb0458fb30d7723e2747355b6dc980f35f3ed431199","name":"node03","services":["bzz","pss"],"enable_msg_events":false,"port":0},"up":true}}],"conns":[{"one":"6e8da86abb894ab35044c8c455147225df96cab498da067a118f1fb9a417f9e3","other":"8a1eb78ff13df318e7f8116dffee98cd7d9905650fa53f16766b754a63f387ac","up":true},{"one":"73d6ad4a75069dced660fa4cb98143ee5573df7cb15d9a295acf1655e9683384","other":"6e8da86abb894ab35044c8c455147225df96cab498da067a118f1fb9a417f9e3","up":true}]} \ No newline at end of file diff --git a/swarm/pss/testdata/snapshot_32.json b/swarm/pss/testdata/snapshot_32.json deleted file mode 100644 index c33cc6e1bbcc..000000000000 --- a/swarm/pss/testdata/snapshot_32.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes":[{"node":{"config":{"id":"41de70b44e7b0b0670d42f0ae348bc6501cef8b4174a6c6d9986645cd5e47aaf","private_key":"294c55925f084f4af87c7e09716c6334a538bac3e6b7157844c0c96a9dd02b4a","name":"node01","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c3863946fb7d43a75cbe2503fa301a4a20cf65caac1dfcbe5fafa2d873a5c6e5","private_key":"010dbe172f1240848fae9639e029301ebd297b29ad2d6936c67669d23470cee4","name":"node02","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"0688bd16330526285a532b2ee34a92075487d93938e2789e324ccb583af7956a","private_key":"7f974cee5ec96d070bf88898c24035988667ea21242a02822bccbe4ec487d126","name":"node03","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"211b73ccd445df519b8afabb07b3b590f3594f01715abca8f9cc7fade33f75ba","private_key":"d7e181b51ba95fd1475314470468045205d7ddefb1bbecfda3340267062489d1","name":"node04","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"3efb0d9530c931c9e47c6ff622c8ad695cbfad216cd9a2b4268c8c3763ffaff0","private_key":"87ed3ffe3111baec0adfd92068b47ed9e1efb642d0ff3115d7d76338f25eea76","name":"node05","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"55b8a5f2d199cc115b9d0978458444983137888591b0e91016cc2f75ce593a41","private_key":"7dbb4fe973c714c4291ce5a8aedce9c3425a25cdecfd5fe0c7f14b55e91d6a03","name":"node06","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"59b214370ca7c60bcda95a18d2b668519ef8ee53af9e4a677f1981defe79d4a7","private_key":"6f8900888d42ea5340f13634776acfd4a261837cdb772b9a059b23c7d425da1d","name":"node07","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"e9bcffd8355af43dfe4ec4415e77a92f6f6897f9c6bc78f31281346cd460cd34","private_key":"a94698db3c1a809d255fedabcb4f3314be27cad7be63cda59bb71724d24ca0d9","name":"node08","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"cd92d92973dc25f623e4492a165f8a8b043e9dd959ca498aa7c6e82ec8038d54","private_key":"37f683b58015f6a7ce73f814fc3860318bb2048052356a215f9eff00aa6ed34b","name":"node09","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"3efa59309f968ab20e053836f01e0d8b4c9e777063a73e871cab86c1fe6a0159","private_key":"b27bdea692559a2a77d497ee567e972e033d4e6e5dfb7c35948a9d231c25b0e0","name":"node10","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"175e3bbbfc70b443adf887386485a4c0171df26edaf7b44c32e430fc37b830d3","private_key":"0a75e7e7dfabf4ee693bfe127221c97eb9b2f4e19d32f7bc836ba253445358d7","name":"node11","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"5e883de2318b4355140edb00a60db79ddc16e0dabec47cd34e2a2b175e198711","private_key":"1557c4754c71e6468fae0c7d0e1b5af2cf70511ed2d5d9b0bc276315a4c8f922","name":"node12","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"0a5cc796e733b06866b67ec3652e41a03a0c03707fed56b767ca5cbfe3828b35","private_key":"d2b18f4edef23f629bc5624a36ac1d58f59207bf7de0b19220f90fc3c64b5a61","name":"node13","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"81449fb850a7c2713784bc938da3e1573d92e93dfd88199e8d877746b6c260af","private_key":"48fe56dfc63a454ab004e7a5dc7938e29694377b6bcc27d1c19d8a72349a8c2d","name":"node14","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"8b72d2a8665d545aae43b317a20f51af80357b290e826822309d9a1dc67a4470","private_key":"9155d38d2a2bbe1e5239f9993d96a350182e3840813436e23e1f2a4a9a32b7d9","name":"node15","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"02f7b6240d3c3990559d296355bb86f40162fee7a45c2e092eb90277266ecbd8","private_key":"e60772916c9d248e3a3819de52aacdd4008412aab20a457e88e764752679b8f8","name":"node16","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"3a268a0db7e8b31d4b362ecb598f3e549a2aa59392591b4d2f2ba37e6ceb72b3","private_key":"e7f27ccf343d2124211ba980dfe8ee7e774a5956268a25522347bb3be99bf38e","name":"node17","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"8939636008ec2eec222bd490ab86b42f9ef1fee7e41a32c70deb676920c3454a","private_key":"0e9cb88a7d70a04f0780ea51e001aabda0830f504e29e31c69d859acce0a9019","name":"node18","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"8564a53752aa3e7edb8e09c3ad61b765ce5abd2ad9c5cb63bfa036930d6de244","private_key":"cf8362e06e18a783f7e20baf956d40e2ea4a204ec868f8d34909802af222e997","name":"node19","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"9ea072155528aaca8fef7a25af26134898b65aed2464dbaa397235cb96f7cd15","private_key":"9a63fae9e77f9cfdc30f4747a939ba44e85824560d98b49047bfa8f4156d426c","name":"node20","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"58ebac47c884e77049a6c9c851ffb1e74622d27be8e30117f1f775391ac27717","private_key":"748efd85864661c429fd70a74bc32f1d81685bd81927c1499dbd93993dc27cad","name":"node21","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"0c14562578ab9796c635d1e5b713bc966341f6e742acd4a8a93a524cadd669eb","private_key":"fed7bc6fd67e331e642253e604215ba088daa48a1b84979699ad770c09b909dd","name":"node22","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"46599aa9671556263a5e5818575b8a12e7c755a44f9dac7873c1c48f33c1465e","private_key":"c61c4e9235825e60f2d8c57169e7d1913f45b057f298ba8fb05d51515e92a902","name":"node23","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"dcf5d113ddf39e516a3871a31843da3cd7740daa2a8f4827265a3d9bfaa3b402","private_key":"53704ffbabad329a9032a58cd7ec012499b0a521bb0300e1d7160d4e5d1220fd","name":"node24","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"7f25ae980b4aeac041d8bd17a8a4140f762aca78710cfd2fb41f8d91d713e4df","private_key":"7098a0386bb10a213728ea65f3ca98fd25a31daf3917190f6a1889d350e09674","name":"node25","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"fbe2f731b4bcdbf8490ba7599a011f561ec6a46c95bc9a34effaf29c91085c09","private_key":"e967b2c9db78764c1ce024423c48e170b68fb6232c313a32133faf4936e2c114","name":"node26","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"f1e3713691f9088c72295658b7e887d83058c668495830c63c0241716c9cae80","private_key":"f8cf717ad5b26578e58a97eddc6074cebb814cdaf3f4144acbafcce51ccbd249","name":"node27","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"de8f114621b53a5a68dfc1870600c60bf9912ad4d36458e52606ab27a6d7c3fa","private_key":"e6573973825826d193b5093ec610c34368630376e4e13843e5f2203c3ca88fa9","name":"node28","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"461dc187c3911fd12829fb3168b68135e3208771d385324434e8ca7ecf72b2e2","private_key":"0ff9df0f439480bc31f8a3ae593af2662bad0f5bd4e0c3e87839af326929de07","name":"node29","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c6b69d856ec8ac1822b4a1d54f8d3bc44831bcd20fbb5c8f8c67b68b33541fe9","private_key":"1daf1094602b9234a1651a8ba6013a807d9c0d56339c784d618e2c6705b65e23","name":"node30","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"ea5ae8b639fa80db684c774d814b7e9cff0cf9622427dae35239cc84c1b7dabc","private_key":"376cc1c769c48c4a04f3f1447fe31112cdbc8d898266a338f6675906511bc9c6","name":"node31","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"0b07a13f12316594013fb220a14910c9aec9446274b6e9f89da3bf1f5732624b","private_key":"6a9a93cae21630926926ece339463ca165823f499f47d45632dbe1a49a84257c","name":"node32","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}}],"conns":[{"one":"211b73ccd445df519b8afabb07b3b590f3594f01715abca8f9cc7fade33f75ba","other":"3efb0d9530c931c9e47c6ff622c8ad695cbfad216cd9a2b4268c8c3763ffaff0","up":true},{"one":"55b8a5f2d199cc115b9d0978458444983137888591b0e91016cc2f75ce593a41","other":"59b214370ca7c60bcda95a18d2b668519ef8ee53af9e4a677f1981defe79d4a7","up":true},{"one":"0b07a13f12316594013fb220a14910c9aec9446274b6e9f89da3bf1f5732624b","other":"41de70b44e7b0b0670d42f0ae348bc6501cef8b4174a6c6d9986645cd5e47aaf","up":true},{"one":"41de70b44e7b0b0670d42f0ae348bc6501cef8b4174a6c6d9986645cd5e47aaf","other":"c3863946fb7d43a75cbe2503fa301a4a20cf65caac1dfcbe5fafa2d873a5c6e5","up":true},{"one":"e9bcffd8355af43dfe4ec4415e77a92f6f6897f9c6bc78f31281346cd460cd34","other":"cd92d92973dc25f623e4492a165f8a8b043e9dd959ca498aa7c6e82ec8038d54","up":true},{"one":"cd92d92973dc25f623e4492a165f8a8b043e9dd959ca498aa7c6e82ec8038d54","other":"3efa59309f968ab20e053836f01e0d8b4c9e777063a73e871cab86c1fe6a0159","up":true},{"one":"3efa59309f968ab20e053836f01e0d8b4c9e777063a73e871cab86c1fe6a0159","other":"175e3bbbfc70b443adf887386485a4c0171df26edaf7b44c32e430fc37b830d3","up":true},{"one":"175e3bbbfc70b443adf887386485a4c0171df26edaf7b44c32e430fc37b830d3","other":"5e883de2318b4355140edb00a60db79ddc16e0dabec47cd34e2a2b175e198711","up":true},{"one":"5e883de2318b4355140edb00a60db79ddc16e0dabec47cd34e2a2b175e198711","other":"0a5cc796e733b06866b67ec3652e41a03a0c03707fed56b767ca5cbfe3828b35","up":true},{"one":"0a5cc796e733b06866b67ec3652e41a03a0c03707fed56b767ca5cbfe3828b35","other":"81449fb850a7c2713784bc938da3e1573d92e93dfd88199e8d877746b6c260af","up":true},{"one":"81449fb850a7c2713784bc938da3e1573d92e93dfd88199e8d877746b6c260af","other":"8b72d2a8665d545aae43b317a20f51af80357b290e826822309d9a1dc67a4470","up":true},{"one":"8b72d2a8665d545aae43b317a20f51af80357b290e826822309d9a1dc67a4470","other":"02f7b6240d3c3990559d296355bb86f40162fee7a45c2e092eb90277266ecbd8","up":true},{"one":"02f7b6240d3c3990559d296355bb86f40162fee7a45c2e092eb90277266ecbd8","other":"3a268a0db7e8b31d4b362ecb598f3e549a2aa59392591b4d2f2ba37e6ceb72b3","up":true},{"one":"3a268a0db7e8b31d4b362ecb598f3e549a2aa59392591b4d2f2ba37e6ceb72b3","other":"8939636008ec2eec222bd490ab86b42f9ef1fee7e41a32c70deb676920c3454a","up":true},{"one":"8939636008ec2eec222bd490ab86b42f9ef1fee7e41a32c70deb676920c3454a","other":"8564a53752aa3e7edb8e09c3ad61b765ce5abd2ad9c5cb63bfa036930d6de244","up":true},{"one":"8564a53752aa3e7edb8e09c3ad61b765ce5abd2ad9c5cb63bfa036930d6de244","other":"9ea072155528aaca8fef7a25af26134898b65aed2464dbaa397235cb96f7cd15","up":true},{"one":"9ea072155528aaca8fef7a25af26134898b65aed2464dbaa397235cb96f7cd15","other":"58ebac47c884e77049a6c9c851ffb1e74622d27be8e30117f1f775391ac27717","up":true},{"one":"58ebac47c884e77049a6c9c851ffb1e74622d27be8e30117f1f775391ac27717","other":"0c14562578ab9796c635d1e5b713bc966341f6e742acd4a8a93a524cadd669eb","up":true},{"one":"0c14562578ab9796c635d1e5b713bc966341f6e742acd4a8a93a524cadd669eb","other":"46599aa9671556263a5e5818575b8a12e7c755a44f9dac7873c1c48f33c1465e","up":true},{"one":"46599aa9671556263a5e5818575b8a12e7c755a44f9dac7873c1c48f33c1465e","other":"dcf5d113ddf39e516a3871a31843da3cd7740daa2a8f4827265a3d9bfaa3b402","up":true},{"one":"dcf5d113ddf39e516a3871a31843da3cd7740daa2a8f4827265a3d9bfaa3b402","other":"7f25ae980b4aeac041d8bd17a8a4140f762aca78710cfd2fb41f8d91d713e4df","up":true},{"one":"7f25ae980b4aeac041d8bd17a8a4140f762aca78710cfd2fb41f8d91d713e4df","other":"fbe2f731b4bcdbf8490ba7599a011f561ec6a46c95bc9a34effaf29c91085c09","up":true},{"one":"fbe2f731b4bcdbf8490ba7599a011f561ec6a46c95bc9a34effaf29c91085c09","other":"f1e3713691f9088c72295658b7e887d83058c668495830c63c0241716c9cae80","up":true},{"one":"f1e3713691f9088c72295658b7e887d83058c668495830c63c0241716c9cae80","other":"de8f114621b53a5a68dfc1870600c60bf9912ad4d36458e52606ab27a6d7c3fa","up":true},{"one":"de8f114621b53a5a68dfc1870600c60bf9912ad4d36458e52606ab27a6d7c3fa","other":"461dc187c3911fd12829fb3168b68135e3208771d385324434e8ca7ecf72b2e2","up":true},{"one":"461dc187c3911fd12829fb3168b68135e3208771d385324434e8ca7ecf72b2e2","other":"c6b69d856ec8ac1822b4a1d54f8d3bc44831bcd20fbb5c8f8c67b68b33541fe9","up":true},{"one":"c6b69d856ec8ac1822b4a1d54f8d3bc44831bcd20fbb5c8f8c67b68b33541fe9","other":"ea5ae8b639fa80db684c774d814b7e9cff0cf9622427dae35239cc84c1b7dabc","up":true},{"one":"ea5ae8b639fa80db684c774d814b7e9cff0cf9622427dae35239cc84c1b7dabc","other":"0b07a13f12316594013fb220a14910c9aec9446274b6e9f89da3bf1f5732624b","up":true},{"one":"3efb0d9530c931c9e47c6ff622c8ad695cbfad216cd9a2b4268c8c3763ffaff0","other":"55b8a5f2d199cc115b9d0978458444983137888591b0e91016cc2f75ce593a41","up":true},{"one":"c3863946fb7d43a75cbe2503fa301a4a20cf65caac1dfcbe5fafa2d873a5c6e5","other":"0688bd16330526285a532b2ee34a92075487d93938e2789e324ccb583af7956a","up":true},{"one":"0688bd16330526285a532b2ee34a92075487d93938e2789e324ccb583af7956a","other":"211b73ccd445df519b8afabb07b3b590f3594f01715abca8f9cc7fade33f75ba","up":true},{"one":"59b214370ca7c60bcda95a18d2b668519ef8ee53af9e4a677f1981defe79d4a7","other":"e9bcffd8355af43dfe4ec4415e77a92f6f6897f9c6bc78f31281346cd460cd34","up":true},{"one":"41de70b44e7b0b0670d42f0ae348bc6501cef8b4174a6c6d9986645cd5e47aaf","other":"461dc187c3911fd12829fb3168b68135e3208771d385324434e8ca7ecf72b2e2","up":true},{"one":"211b73ccd445df519b8afabb07b3b590f3594f01715abca8f9cc7fade33f75ba","other":"0c14562578ab9796c635d1e5b713bc966341f6e742acd4a8a93a524cadd669eb","up":true},{"one":"3efb0d9530c931c9e47c6ff622c8ad695cbfad216cd9a2b4268c8c3763ffaff0","other":"0c14562578ab9796c635d1e5b713bc966341f6e742acd4a8a93a524cadd669eb","up":true},{"one":"55b8a5f2d199cc115b9d0978458444983137888591b0e91016cc2f75ce593a41","other":"58ebac47c884e77049a6c9c851ffb1e74622d27be8e30117f1f775391ac27717","up":true},{"one":"59b214370ca7c60bcda95a18d2b668519ef8ee53af9e4a677f1981defe79d4a7","other":"58ebac47c884e77049a6c9c851ffb1e74622d27be8e30117f1f775391ac27717","up":true},{"one":"3efa59309f968ab20e053836f01e0d8b4c9e777063a73e871cab86c1fe6a0159","other":"3efb0d9530c931c9e47c6ff622c8ad695cbfad216cd9a2b4268c8c3763ffaff0","up":true},{"one":"cd92d92973dc25f623e4492a165f8a8b043e9dd959ca498aa7c6e82ec8038d54","other":"c3863946fb7d43a75cbe2503fa301a4a20cf65caac1dfcbe5fafa2d873a5c6e5","up":true},{"one":"dcf5d113ddf39e516a3871a31843da3cd7740daa2a8f4827265a3d9bfaa3b402","other":"de8f114621b53a5a68dfc1870600c60bf9912ad4d36458e52606ab27a6d7c3fa","up":true},{"one":"7f25ae980b4aeac041d8bd17a8a4140f762aca78710cfd2fb41f8d91d713e4df","other":"46599aa9671556263a5e5818575b8a12e7c755a44f9dac7873c1c48f33c1465e","up":true},{"one":"0a5cc796e733b06866b67ec3652e41a03a0c03707fed56b767ca5cbfe3828b35","other":"0c14562578ab9796c635d1e5b713bc966341f6e742acd4a8a93a524cadd669eb","up":true},{"one":"f1e3713691f9088c72295658b7e887d83058c668495830c63c0241716c9cae80","other":"ea5ae8b639fa80db684c774d814b7e9cff0cf9622427dae35239cc84c1b7dabc","up":true},{"one":"81449fb850a7c2713784bc938da3e1573d92e93dfd88199e8d877746b6c260af","other":"9ea072155528aaca8fef7a25af26134898b65aed2464dbaa397235cb96f7cd15","up":true},{"one":"02f7b6240d3c3990559d296355bb86f40162fee7a45c2e092eb90277266ecbd8","other":"0c14562578ab9796c635d1e5b713bc966341f6e742acd4a8a93a524cadd669eb","up":true},{"one":"3a268a0db7e8b31d4b362ecb598f3e549a2aa59392591b4d2f2ba37e6ceb72b3","other":"3efb0d9530c931c9e47c6ff622c8ad695cbfad216cd9a2b4268c8c3763ffaff0","up":true},{"one":"58ebac47c884e77049a6c9c851ffb1e74622d27be8e30117f1f775391ac27717","other":"5e883de2318b4355140edb00a60db79ddc16e0dabec47cd34e2a2b175e198711","up":true},{"one":"8564a53752aa3e7edb8e09c3ad61b765ce5abd2ad9c5cb63bfa036930d6de244","other":"58ebac47c884e77049a6c9c851ffb1e74622d27be8e30117f1f775391ac27717","up":true},{"one":"9ea072155528aaca8fef7a25af26134898b65aed2464dbaa397235cb96f7cd15","other":"dcf5d113ddf39e516a3871a31843da3cd7740daa2a8f4827265a3d9bfaa3b402","up":true},{"one":"e9bcffd8355af43dfe4ec4415e77a92f6f6897f9c6bc78f31281346cd460cd34","other":"ea5ae8b639fa80db684c774d814b7e9cff0cf9622427dae35239cc84c1b7dabc","up":true},{"one":"5e883de2318b4355140edb00a60db79ddc16e0dabec47cd34e2a2b175e198711","other":"59b214370ca7c60bcda95a18d2b668519ef8ee53af9e4a677f1981defe79d4a7","up":true},{"one":"0c14562578ab9796c635d1e5b713bc966341f6e742acd4a8a93a524cadd669eb","other":"0b07a13f12316594013fb220a14910c9aec9446274b6e9f89da3bf1f5732624b","up":true},{"one":"c6b69d856ec8ac1822b4a1d54f8d3bc44831bcd20fbb5c8f8c67b68b33541fe9","other":"c3863946fb7d43a75cbe2503fa301a4a20cf65caac1dfcbe5fafa2d873a5c6e5","up":true},{"one":"0b07a13f12316594013fb220a14910c9aec9446274b6e9f89da3bf1f5732624b","other":"0688bd16330526285a532b2ee34a92075487d93938e2789e324ccb583af7956a","up":true},{"one":"8939636008ec2eec222bd490ab86b42f9ef1fee7e41a32c70deb676920c3454a","other":"9ea072155528aaca8fef7a25af26134898b65aed2464dbaa397235cb96f7cd15","up":true},{"one":"46599aa9671556263a5e5818575b8a12e7c755a44f9dac7873c1c48f33c1465e","other":"461dc187c3911fd12829fb3168b68135e3208771d385324434e8ca7ecf72b2e2","up":true},{"one":"ea5ae8b639fa80db684c774d814b7e9cff0cf9622427dae35239cc84c1b7dabc","other":"fbe2f731b4bcdbf8490ba7599a011f561ec6a46c95bc9a34effaf29c91085c09","up":true},{"one":"175e3bbbfc70b443adf887386485a4c0171df26edaf7b44c32e430fc37b830d3","other":"0c14562578ab9796c635d1e5b713bc966341f6e742acd4a8a93a524cadd669eb","up":true},{"one":"8b72d2a8665d545aae43b317a20f51af80357b290e826822309d9a1dc67a4470","other":"8939636008ec2eec222bd490ab86b42f9ef1fee7e41a32c70deb676920c3454a","up":true},{"one":"461dc187c3911fd12829fb3168b68135e3208771d385324434e8ca7ecf72b2e2","other":"3efb0d9530c931c9e47c6ff622c8ad695cbfad216cd9a2b4268c8c3763ffaff0","up":true},{"one":"0688bd16330526285a532b2ee34a92075487d93938e2789e324ccb583af7956a","other":"02f7b6240d3c3990559d296355bb86f40162fee7a45c2e092eb90277266ecbd8","up":true},{"one":"c3863946fb7d43a75cbe2503fa301a4a20cf65caac1dfcbe5fafa2d873a5c6e5","other":"9ea072155528aaca8fef7a25af26134898b65aed2464dbaa397235cb96f7cd15","up":true},{"one":"de8f114621b53a5a68dfc1870600c60bf9912ad4d36458e52606ab27a6d7c3fa","other":"cd92d92973dc25f623e4492a165f8a8b043e9dd959ca498aa7c6e82ec8038d54","up":true},{"one":"fbe2f731b4bcdbf8490ba7599a011f561ec6a46c95bc9a34effaf29c91085c09","other":"e9bcffd8355af43dfe4ec4415e77a92f6f6897f9c6bc78f31281346cd460cd34","up":true},{"one":"41de70b44e7b0b0670d42f0ae348bc6501cef8b4174a6c6d9986645cd5e47aaf","other":"46599aa9671556263a5e5818575b8a12e7c755a44f9dac7873c1c48f33c1465e","up":true},{"one":"c3863946fb7d43a75cbe2503fa301a4a20cf65caac1dfcbe5fafa2d873a5c6e5","other":"f1e3713691f9088c72295658b7e887d83058c668495830c63c0241716c9cae80","up":true},{"one":"0688bd16330526285a532b2ee34a92075487d93938e2789e324ccb583af7956a","other":"0c14562578ab9796c635d1e5b713bc966341f6e742acd4a8a93a524cadd669eb","up":true},{"one":"211b73ccd445df519b8afabb07b3b590f3594f01715abca8f9cc7fade33f75ba","other":"3efa59309f968ab20e053836f01e0d8b4c9e777063a73e871cab86c1fe6a0159","up":true},{"one":"3efb0d9530c931c9e47c6ff622c8ad695cbfad216cd9a2b4268c8c3763ffaff0","other":"8b72d2a8665d545aae43b317a20f51af80357b290e826822309d9a1dc67a4470","up":true},{"one":"55b8a5f2d199cc115b9d0978458444983137888591b0e91016cc2f75ce593a41","other":"5e883de2318b4355140edb00a60db79ddc16e0dabec47cd34e2a2b175e198711","up":true},{"one":"59b214370ca7c60bcda95a18d2b668519ef8ee53af9e4a677f1981defe79d4a7","other":"0688bd16330526285a532b2ee34a92075487d93938e2789e324ccb583af7956a","up":true},{"one":"175e3bbbfc70b443adf887386485a4c0171df26edaf7b44c32e430fc37b830d3","other":"0a5cc796e733b06866b67ec3652e41a03a0c03707fed56b767ca5cbfe3828b35","up":true},{"one":"3efa59309f968ab20e053836f01e0d8b4c9e777063a73e871cab86c1fe6a0159","other":"3a268a0db7e8b31d4b362ecb598f3e549a2aa59392591b4d2f2ba37e6ceb72b3","up":true},{"one":"e9bcffd8355af43dfe4ec4415e77a92f6f6897f9c6bc78f31281346cd460cd34","other":"f1e3713691f9088c72295658b7e887d83058c668495830c63c0241716c9cae80","up":true},{"one":"5e883de2318b4355140edb00a60db79ddc16e0dabec47cd34e2a2b175e198711","other":"8564a53752aa3e7edb8e09c3ad61b765ce5abd2ad9c5cb63bfa036930d6de244","up":true},{"one":"9ea072155528aaca8fef7a25af26134898b65aed2464dbaa397235cb96f7cd15","other":"8b72d2a8665d545aae43b317a20f51af80357b290e826822309d9a1dc67a4470","up":true},{"one":"cd92d92973dc25f623e4492a165f8a8b043e9dd959ca498aa7c6e82ec8038d54","other":"c6b69d856ec8ac1822b4a1d54f8d3bc44831bcd20fbb5c8f8c67b68b33541fe9","up":true},{"one":"dcf5d113ddf39e516a3871a31843da3cd7740daa2a8f4827265a3d9bfaa3b402","other":"c3863946fb7d43a75cbe2503fa301a4a20cf65caac1dfcbe5fafa2d873a5c6e5","up":true},{"one":"0a5cc796e733b06866b67ec3652e41a03a0c03707fed56b767ca5cbfe3828b35","other":"0b07a13f12316594013fb220a14910c9aec9446274b6e9f89da3bf1f5732624b","up":true},{"one":"58ebac47c884e77049a6c9c851ffb1e74622d27be8e30117f1f775391ac27717","other":"7f25ae980b4aeac041d8bd17a8a4140f762aca78710cfd2fb41f8d91d713e4df","up":true},{"one":"81449fb850a7c2713784bc938da3e1573d92e93dfd88199e8d877746b6c260af","other":"8564a53752aa3e7edb8e09c3ad61b765ce5abd2ad9c5cb63bfa036930d6de244","up":true},{"one":"0c14562578ab9796c635d1e5b713bc966341f6e742acd4a8a93a524cadd669eb","other":"dcf5d113ddf39e516a3871a31843da3cd7740daa2a8f4827265a3d9bfaa3b402","up":true},{"one":"8b72d2a8665d545aae43b317a20f51af80357b290e826822309d9a1dc67a4470","other":"8564a53752aa3e7edb8e09c3ad61b765ce5abd2ad9c5cb63bfa036930d6de244","up":true},{"one":"46599aa9671556263a5e5818575b8a12e7c755a44f9dac7873c1c48f33c1465e","other":"59b214370ca7c60bcda95a18d2b668519ef8ee53af9e4a677f1981defe79d4a7","up":true},{"one":"02f7b6240d3c3990559d296355bb86f40162fee7a45c2e092eb90277266ecbd8","other":"0a5cc796e733b06866b67ec3652e41a03a0c03707fed56b767ca5cbfe3828b35","up":true},{"one":"3a268a0db7e8b31d4b362ecb598f3e549a2aa59392591b4d2f2ba37e6ceb72b3","other":"211b73ccd445df519b8afabb07b3b590f3594f01715abca8f9cc7fade33f75ba","up":true},{"one":"7f25ae980b4aeac041d8bd17a8a4140f762aca78710cfd2fb41f8d91d713e4df","other":"461dc187c3911fd12829fb3168b68135e3208771d385324434e8ca7ecf72b2e2","up":true},{"one":"8939636008ec2eec222bd490ab86b42f9ef1fee7e41a32c70deb676920c3454a","other":"81449fb850a7c2713784bc938da3e1573d92e93dfd88199e8d877746b6c260af","up":true},{"one":"fbe2f731b4bcdbf8490ba7599a011f561ec6a46c95bc9a34effaf29c91085c09","other":"9ea072155528aaca8fef7a25af26134898b65aed2464dbaa397235cb96f7cd15","up":true},{"one":"8564a53752aa3e7edb8e09c3ad61b765ce5abd2ad9c5cb63bfa036930d6de244","other":"dcf5d113ddf39e516a3871a31843da3cd7740daa2a8f4827265a3d9bfaa3b402","up":true},{"one":"f1e3713691f9088c72295658b7e887d83058c668495830c63c0241716c9cae80","other":"3efb0d9530c931c9e47c6ff622c8ad695cbfad216cd9a2b4268c8c3763ffaff0","up":true},{"one":"de8f114621b53a5a68dfc1870600c60bf9912ad4d36458e52606ab27a6d7c3fa","other":"c6b69d856ec8ac1822b4a1d54f8d3bc44831bcd20fbb5c8f8c67b68b33541fe9","up":true},{"one":"461dc187c3911fd12829fb3168b68135e3208771d385324434e8ca7ecf72b2e2","other":"59b214370ca7c60bcda95a18d2b668519ef8ee53af9e4a677f1981defe79d4a7","up":true},{"one":"0b07a13f12316594013fb220a14910c9aec9446274b6e9f89da3bf1f5732624b","other":"02f7b6240d3c3990559d296355bb86f40162fee7a45c2e092eb90277266ecbd8","up":true},{"one":"c6b69d856ec8ac1822b4a1d54f8d3bc44831bcd20fbb5c8f8c67b68b33541fe9","other":"9ea072155528aaca8fef7a25af26134898b65aed2464dbaa397235cb96f7cd15","up":true},{"one":"ea5ae8b639fa80db684c774d814b7e9cff0cf9622427dae35239cc84c1b7dabc","other":"9ea072155528aaca8fef7a25af26134898b65aed2464dbaa397235cb96f7cd15","up":true},{"one":"41de70b44e7b0b0670d42f0ae348bc6501cef8b4174a6c6d9986645cd5e47aaf","other":"7f25ae980b4aeac041d8bd17a8a4140f762aca78710cfd2fb41f8d91d713e4df","up":true},{"one":"55b8a5f2d199cc115b9d0978458444983137888591b0e91016cc2f75ce593a41","other":"9ea072155528aaca8fef7a25af26134898b65aed2464dbaa397235cb96f7cd15","up":true},{"one":"59b214370ca7c60bcda95a18d2b668519ef8ee53af9e4a677f1981defe79d4a7","other":"7f25ae980b4aeac041d8bd17a8a4140f762aca78710cfd2fb41f8d91d713e4df","up":true},{"one":"211b73ccd445df519b8afabb07b3b590f3594f01715abca8f9cc7fade33f75ba","other":"9ea072155528aaca8fef7a25af26134898b65aed2464dbaa397235cb96f7cd15","up":true},{"one":"0688bd16330526285a532b2ee34a92075487d93938e2789e324ccb583af7956a","other":"0a5cc796e733b06866b67ec3652e41a03a0c03707fed56b767ca5cbfe3828b35","up":true},{"one":"e9bcffd8355af43dfe4ec4415e77a92f6f6897f9c6bc78f31281346cd460cd34","other":"8939636008ec2eec222bd490ab86b42f9ef1fee7e41a32c70deb676920c3454a","up":true},{"one":"cd92d92973dc25f623e4492a165f8a8b043e9dd959ca498aa7c6e82ec8038d54","other":"9ea072155528aaca8fef7a25af26134898b65aed2464dbaa397235cb96f7cd15","up":true},{"one":"3efa59309f968ab20e053836f01e0d8b4c9e777063a73e871cab86c1fe6a0159","other":"46599aa9671556263a5e5818575b8a12e7c755a44f9dac7873c1c48f33c1465e","up":true},{"one":"5e883de2318b4355140edb00a60db79ddc16e0dabec47cd34e2a2b175e198711","other":"7f25ae980b4aeac041d8bd17a8a4140f762aca78710cfd2fb41f8d91d713e4df","up":true},{"one":"175e3bbbfc70b443adf887386485a4c0171df26edaf7b44c32e430fc37b830d3","other":"0b07a13f12316594013fb220a14910c9aec9446274b6e9f89da3bf1f5732624b","up":true},{"one":"0a5cc796e733b06866b67ec3652e41a03a0c03707fed56b767ca5cbfe3828b35","other":"211b73ccd445df519b8afabb07b3b590f3594f01715abca8f9cc7fade33f75ba","up":true},{"one":"02f7b6240d3c3990559d296355bb86f40162fee7a45c2e092eb90277266ecbd8","other":"7f25ae980b4aeac041d8bd17a8a4140f762aca78710cfd2fb41f8d91d713e4df","up":true},{"one":"81449fb850a7c2713784bc938da3e1573d92e93dfd88199e8d877746b6c260af","other":"e9bcffd8355af43dfe4ec4415e77a92f6f6897f9c6bc78f31281346cd460cd34","up":true},{"one":"8b72d2a8665d545aae43b317a20f51af80357b290e826822309d9a1dc67a4470","other":"e9bcffd8355af43dfe4ec4415e77a92f6f6897f9c6bc78f31281346cd460cd34","up":true},{"one":"3a268a0db7e8b31d4b362ecb598f3e549a2aa59392591b4d2f2ba37e6ceb72b3","other":"46599aa9671556263a5e5818575b8a12e7c755a44f9dac7873c1c48f33c1465e","up":true},{"one":"58ebac47c884e77049a6c9c851ffb1e74622d27be8e30117f1f775391ac27717","other":"41de70b44e7b0b0670d42f0ae348bc6501cef8b4174a6c6d9986645cd5e47aaf","up":true},{"one":"fbe2f731b4bcdbf8490ba7599a011f561ec6a46c95bc9a34effaf29c91085c09","other":"cd92d92973dc25f623e4492a165f8a8b043e9dd959ca498aa7c6e82ec8038d54","up":true},{"one":"f1e3713691f9088c72295658b7e887d83058c668495830c63c0241716c9cae80","other":"8939636008ec2eec222bd490ab86b42f9ef1fee7e41a32c70deb676920c3454a","up":true},{"one":"de8f114621b53a5a68dfc1870600c60bf9912ad4d36458e52606ab27a6d7c3fa","other":"c3863946fb7d43a75cbe2503fa301a4a20cf65caac1dfcbe5fafa2d873a5c6e5","up":true},{"one":"dcf5d113ddf39e516a3871a31843da3cd7740daa2a8f4827265a3d9bfaa3b402","other":"c6b69d856ec8ac1822b4a1d54f8d3bc44831bcd20fbb5c8f8c67b68b33541fe9","up":true},{"one":"0b07a13f12316594013fb220a14910c9aec9446274b6e9f89da3bf1f5732624b","other":"3a268a0db7e8b31d4b362ecb598f3e549a2aa59392591b4d2f2ba37e6ceb72b3","up":true},{"one":"7f25ae980b4aeac041d8bd17a8a4140f762aca78710cfd2fb41f8d91d713e4df","other":"55b8a5f2d199cc115b9d0978458444983137888591b0e91016cc2f75ce593a41","up":true},{"one":"0688bd16330526285a532b2ee34a92075487d93938e2789e324ccb583af7956a","other":"175e3bbbfc70b443adf887386485a4c0171df26edaf7b44c32e430fc37b830d3","up":true},{"one":"211b73ccd445df519b8afabb07b3b590f3594f01715abca8f9cc7fade33f75ba","other":"7f25ae980b4aeac041d8bd17a8a4140f762aca78710cfd2fb41f8d91d713e4df","up":true},{"one":"55b8a5f2d199cc115b9d0978458444983137888591b0e91016cc2f75ce593a41","other":"41de70b44e7b0b0670d42f0ae348bc6501cef8b4174a6c6d9986645cd5e47aaf","up":true},{"one":"175e3bbbfc70b443adf887386485a4c0171df26edaf7b44c32e430fc37b830d3","other":"02f7b6240d3c3990559d296355bb86f40162fee7a45c2e092eb90277266ecbd8","up":true},{"one":"5e883de2318b4355140edb00a60db79ddc16e0dabec47cd34e2a2b175e198711","other":"41de70b44e7b0b0670d42f0ae348bc6501cef8b4174a6c6d9986645cd5e47aaf","up":true},{"one":"de8f114621b53a5a68dfc1870600c60bf9912ad4d36458e52606ab27a6d7c3fa","other":"8564a53752aa3e7edb8e09c3ad61b765ce5abd2ad9c5cb63bfa036930d6de244","up":true},{"one":"dcf5d113ddf39e516a3871a31843da3cd7740daa2a8f4827265a3d9bfaa3b402","other":"cd92d92973dc25f623e4492a165f8a8b043e9dd959ca498aa7c6e82ec8038d54","up":true}]} \ No newline at end of file diff --git a/swarm/pss/testdata/snapshot_4.json b/swarm/pss/testdata/snapshot_4.json deleted file mode 100644 index a64f31375ae1..000000000000 --- a/swarm/pss/testdata/snapshot_4.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes":[{"node":{"config":{"id":"73d6ad4a75069dced660fa4cb98143ee5573df7cb15d9a295acf1655e9683384","private_key":"e567b7d9c554e5102cdc99b6523bace02dbb8951415c8816d82ba2d2e97fa23b","name":"node01","services":["bzz","pss"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"6e8da86abb894ab35044c8c455147225df96cab498da067a118f1fb9a417f9e3","private_key":"c7526db70acd02f36d3b201ef3e1d85e38c52bee6931453213dbc5edec4d0976","name":"node02","services":["bzz","pss"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"8a1eb78ff13df318e7f8116dffee98cd7d9905650fa53f16766b754a63f387ac","private_key":"61b5728f59bc43080c3b8eb0458fb30d7723e2747355b6dc980f35f3ed431199","name":"node03","services":["bzz","pss"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"d7768334f79d626adb433f44b703a818555e3331056036ef3f8d1282586bf044","private_key":"075b07c29ceac4ffa2a114afd67b21dfc438126bc169bf7c154be6d81d86ed38","name":"node04","services":["bzz","pss"],"enable_msg_events":false,"port":0},"up":true}}],"conns":[{"one":"6e8da86abb894ab35044c8c455147225df96cab498da067a118f1fb9a417f9e3","other":"8a1eb78ff13df318e7f8116dffee98cd7d9905650fa53f16766b754a63f387ac","up":true},{"one":"73d6ad4a75069dced660fa4cb98143ee5573df7cb15d9a295acf1655e9683384","other":"6e8da86abb894ab35044c8c455147225df96cab498da067a118f1fb9a417f9e3","up":true},{"one":"8a1eb78ff13df318e7f8116dffee98cd7d9905650fa53f16766b754a63f387ac","other":"d7768334f79d626adb433f44b703a818555e3331056036ef3f8d1282586bf044","up":true}]} \ No newline at end of file diff --git a/swarm/pss/testdata/snapshot_64.json b/swarm/pss/testdata/snapshot_64.json deleted file mode 100644 index d8031614e93b..000000000000 --- a/swarm/pss/testdata/snapshot_64.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes":[{"node":{"config":{"id":"7f4198ddcfb55e9f690024cc19ce4f32d5bbc8d56cfb43f806513a74e52dc277","private_key":"8f572fa1cb0643b3413cd0dfceac00a4dac9ec09af0c2d134809b6385dad35d7","name":"node01","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"e75238135f0c071ca541fb0a9569e38036c51af350a16bf4a1526f1752318059","private_key":"a66e079664952b1fb1028e959dd8a05a21477e2f298ea89592adc522d38e511b","name":"node02","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1102d287c72becf1bfa8567cf43cc2051aed16a79196f9109cdedf829a2a5a2d","private_key":"072e1cf99b52c2f1d40560809048fea86f39ae1db94bec2c9b71b0bfe092910a","name":"node03","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"34ad9431b71cdeae0c1941b3ae680387b3cedd815f39d65034892920aa869f38","private_key":"003ec9cb18f157360d7a3eb506b37ec69dbbd141bbeb7b8fae5f470b1a0768db","name":"node04","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"9bb12f66006f3347790c2c4e74e5463fd6ab651768296ee41de51757b9f40b89","private_key":"fd6fc50368fbc7e848635726291264cf6ad682fb4fe785b724c8b22ea31e1e24","name":"node05","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"9045e912dc0acaf9d86ec75d98e93bc90832dbca1119641f8569a98040be940c","private_key":"da156f269cb01e7fbea5efd5f7ab6283ef0994a1ca139768f49c68e027ca76b0","name":"node06","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"cf69e5f8effee000dc16109a7e0d04e4e13aeb2f6c089999e555946a46e548f6","private_key":"ae570046bebe22a2cd095ca2b2282cab29b2501c6f217ae3e19d96bace36c199","name":"node07","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"cc708e47196a6435b13b18f213f50bf075f3f97100d52d18d70052efc301c91a","private_key":"dee3b946981d4b7bd055941cd6722b166efc24f28f6b26da9cd52a8f91f3a411","name":"node08","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"f3a57f887ddcf1793207948374ce67da9f6a1227f253e3a90acc5b36a48ee645","private_key":"b7e566b24a7c97f714c9920c4e426dd462ee171bb8fd91a3efee9bd1d28dd060","name":"node09","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"5e64f329525e17129b3f51580b399a5f09d7652a663bbaac83c8173dd9fded41","private_key":"070b030d06605c46eafadabb979d38f9d4f48cf55e24a96378f4f0bbe2806887","name":"node10","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"74117e299fffce1ef7f323ea1d6fb1c509cd5581f6c53e0f5e9af8d0b83748f2","private_key":"4ad1a65bb55e2f6d41cc3383b1de11c79bab13723c02d4f5abaf725d6aaea3e4","name":"node11","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"a416fa852bb425e00b805ffabb525ae45024403685a0e8a67815617fd1d078da","private_key":"bbe3351a79ad82f8b832dc16567dd1bcc24b1897bc9bfec930f3461a61f7d652","name":"node12","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"3bc8d405359a665d3f6d64cf9ba194b7e614c2050add0a1205be7f6c3c984e1d","private_key":"26e548a577ae1fcc718256e75d48d70ed406dda8798f84c967d7dfe45c6aceb5","name":"node13","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"e45d7b4870acb8a984c065cce7bd17acaf79c5ad4d477f810563315aebacd8e3","private_key":"97ca855261319532dfa74bf6194b4302c3d2f971adbd462e3437408df6dcbe47","name":"node14","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"113d7850e8169761d357e0ad5a262560fe6137fc6a992b3b2cedcebbe5d5f6a2","private_key":"a762e1b25bed356f9a9e3aed01c7a38e8f57441b6caa18874712ef1159616b57","name":"node15","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"e0205da225e77fa15332864eaef1c437be306090b0489faf3f6725cb3c5b4712","private_key":"53407f7862d16cdfd7beb23612d5d57dda1963cf729766feb046966e15850b32","name":"node16","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"2218170def9962d400bf932ced91f3c14595062b50ef86eb4d2f7fd384fc8597","private_key":"f3ffbcce6a7f7bfe25c3ab3ba95dd0574944bb5a09eabda3da69f187c48ea747","name":"node17","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c85b39668486c5eb3ea9f312d9897bda5688c4995318e4e64a0be2ac0cb47692","private_key":"0fc26e702e6bf4755536ef14b178df72099f9d2e818b53ce85f5b5ff3ea6c9c8","name":"node18","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"5fd664467fac65594c3dd6f89bf2a5398e28aa95d248c3a3d21abaecfebfde5d","private_key":"6f1be181439476148e13cd2c39dd8983588047ef5f966091688ce37a01f78441","name":"node19","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"461c7f8f8948693dea717679ffc4bbf0eb573f4597543993e3be39d3594fdd47","private_key":"b2732f7493e1101eb17248f6a6d83b5811c49cfc13f1aa41a624d1bb3e85368f","name":"node20","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"df45befed47eec3248ce3b55e9cc56f8d718fc13dcb1a03755cdc0cda89e4dcb","private_key":"b573a79bddda3cee3997d5c1210e33810dfce4f43a9a47d3f41ad02352d0515e","name":"node21","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"aa655b48b225d692184a21a2f7162950851a914f89671cc1af3e87a21b37c5e3","private_key":"3e8547c0320dad0457a7c6d576c1700ac4af59504cae9178faafcd60c9830e99","name":"node22","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"6b336cfff0d431edce4cee0cf35ffb1e50b7a5c6e15d3d816fb1bb1cf6488f22","private_key":"dcee5e2db10836dcd5e3bbe14845a6203eebb791ae82200732e6b21d453e0642","name":"node23","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"903561af2459b0e46d8188121e66d8b999ad39341944eb099eff70213244c813","private_key":"e895b2d5bfbd538d231463563cec8889c8aef0fbeb77acfd3ab9bb75504d39cd","name":"node24","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"e0463c87b0b7deefbd521721430e0fd24683aa8b41c65f259d678d9844bd800f","private_key":"199b03688e42fbdcdd00b6230f59bf7a0b6ac22f6088c7d398535121f867b30a","name":"node25","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"c0d6b4950771888e9770051d341f2071fecdb09e28c289e251668327eb8ccf66","private_key":"04a50f62b3bd30de9b6a4ca7c72d0fe81f6c111ab99bbbf4bb9c21b6c8983639","name":"node26","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"cfc872fe7f213f4ada5ec413037450ae2b141157c83998b9520ac2ddb51707bd","private_key":"dee1658338c1840984753b1121833d17232db7a62a8ed1b4fc1268e99138385e","name":"node27","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"cec77f188351d24d5067de2e0cc57b48a9a5455d78bda6b7632d14a679070c3d","private_key":"9fa3a527ec0ce68f936c2dadcc080238cfd5a3a462c20462567976eaf1d52810","name":"node28","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"6d29dbed992f0f0008a72978045df08a4ae1753571fdefbb676f52b5ff843433","private_key":"26df6d28e33e4da3390df260d9bbad739c31cc494a632239633485872b1ead29","name":"node29","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"292bb75a5407dda2ad7b642b1b790264e7ce16bbd8cd22e512eeec3d600ea520","private_key":"d7549640c8223b3b3e55195e3b519a85b7af1f1fc26ebbd56c9117a639544593","name":"node30","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"9e9451cb84ec10881e16a87d84b0e4e1aa78f5e9bc49ca5244a3da414b5210da","private_key":"16ea03e396aba3e56e9e2d89e53b68e46e51408b090fe2b99e65591ae6cd02c0","name":"node31","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"cab41b44229682f11d6aca3d3136597524e9eb4e1640147b87f3f8c7227959f1","private_key":"2f111d17fef6870e109950bfd76e7e6db0913a06d14f8f5a311a39f447e65aea","name":"node32","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"b2a236a2acf5cf675c521a3e87ef847933e1736c220a54cf66db4ba03f369483","private_key":"b20e729862ae141e3134c9eb2ed703957c0fd006f041df213946d66bfe06a3a7","name":"node33","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"71fe0f225ee23106cb6d6c57cdeb49ecc4ffb2b13441956eca6d94505aa2a72c","private_key":"d35682e43d6382cb9508e5479fc2d09bec82166746bd406cc249f655715ae986","name":"node34","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"a365bf427dc109429657033f4b7b22575b6a8d955d6eaf44288725e5e47b0a86","private_key":"9d7b2568317659ec35ed53c64f5c1aa0813dde36f1f492139ffefdd91ae0717e","name":"node35","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"5a4fa4f4482dbaf7bbe6c3d2ae28c35187fbd0f00d1293be41719aa8e5e4a388","private_key":"1177e7fa4a1e785fc30996682cf9ecd265d86943f65d7e3cf4c25cefcd861479","name":"node36","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"bece4d99fafdcebd22f03a4640026c55f7155ff814b295a2aec7a98d3973595a","private_key":"29e2a9ddf2c5b8914fcf8d3782d464b0be6252d589b2a7daa1ea6b93b205c4f4","name":"node37","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"b04c7f907691ac51adab6fe657dd4592044ca4222ee60eb86c7851c855057a8e","private_key":"de7a36b57175c1b9dd9686884f73048ea29215a01fdee9f2cf5068218efdc9d6","name":"node38","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"6a5cd611a5a8a94c82d6d3b40912909f326d4111c82983c3f719cfa3f621312f","private_key":"209fa24e0d0a335e74967ae3ca2914ea6fb1ac99b9495820e9680855fab3081f","name":"node39","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"cee0b72bcb0a112c9aa33eb80166d279c2cc0216782a19454a086c0147b8126d","private_key":"225e538725db916b0e7be129cbda4da08e6be4ccaef64cf870551466d2658834","name":"node40","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"d79a1e6f3dc0e3382eaccdc1fb0f49cd66f483addcc52d767fc12f4b6754e206","private_key":"1cf5c454ae9746fb367557d4f4a4a9d22157508dd1b19223b4640a3fa9c1dce8","name":"node41","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"39e710327f4176b5ed7ebc921db592a6b9197b237029faab4525a00d18470e8a","private_key":"e4f81e7a844611f55194d22f62b681f479dadd58596a292cbbf6ef794df1c99a","name":"node42","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"5205bbd1c298864045f08fe7cc4c2409055db3ee4d790d2587c9ca3366acd977","private_key":"cfea4812067444b8816b5d70ed03fe5491d28ca6012a871bcb8aebbd100f9489","name":"node43","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"0f5c44c74d4cc9b10d283a0099fba5f925c3455f5308d2044c377f8a5181703a","private_key":"384043ea1944918b9fac6bbbd88341254533ae49e3b2ce16c1df0941b1118303","name":"node44","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"b52335dfb9367471204dfba231ba274815b859164e19ee872e20326d0575b88a","private_key":"2de5004aa7337bd0819c88121cb0e708bc072c153bf94e274d7e786288996630","name":"node45","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"1f17c9223987ccf4d9683df0664cd568164c69d2f62bad2a79ef4636623ba87a","private_key":"aeb73b9ba035e0d3569e9dd5780dea2994151e2b43cb74e0e297fa285ba3d794","name":"node46","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"0f988a170fdc7761b8d9814a1a7ac0cc0e98483b91841046b9948783befeed88","private_key":"6f192dda3ba98c5d13e94cc2e64ed95478ee64e97b230f224237977bf04cd724","name":"node47","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"0b45cb8af78fb1af7ab55d022ed00da66393b7eea1b7a3a2f802ffeba4c79dd3","private_key":"5c3a125bd1eca6243b28fcbd20a0811d7e09ce602ca98ba5f253435d76e0e4a9","name":"node48","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"9e384d8e6d319016c2807884e0b46da4ce9fcb426efb76224ce41b5d4dae5632","private_key":"e4c7dec3dd327bfaa41b91fb8ccdbb96156d1fe464da973445a35f61e5bc6814","name":"node49","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"5c197e623ab40c0de2c83a4ffd16930cf791e60357f162f5b2a2e865ff0cea73","private_key":"07853d60907494e01f54f879c85c5f3fec6d4d615f9553d12b3da9ea1ad7a4ec","name":"node50","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"b45f23d6ac8824b480f71f25abf5aa2c9db4c3a904660d391768168430355827","private_key":"6de081218acdb9ac4c4b8fbf1fde5f2be3c601cbf9104c970de43c2512c5e4c3","name":"node51","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"85d645e3738c441f508671f52cde0aa7ae2032c082c86b7ca6af92598d7174d5","private_key":"7b7cb0ca071a27ee059bda50862b7fed343122d2546f5ac5a49dd5b47910530a","name":"node52","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"647ccb25b23a917f611a46a72b12fd457610f07143155e1f828b41fd2d3e0b58","private_key":"542108cf09fe9447bb772d25802153811dd504943271556789165c6742636520","name":"node53","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"7fe48790180ab38e7687009847285f7af5e73b21b60cfad9d844ba8b2e7576e4","private_key":"87ab72e1c606a3b799ef3f34d3aa14549953c570f569a7b7952bb8f460b05a56","name":"node54","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"88b4c3d6ae6a1d4c39caef84d56f308b58e2ca0477cc5197f3f5be41c890bd7c","private_key":"a568b1f02f9c9129fd2924f704b8948351325edea81e43132c857be97dc7216d","name":"node55","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"ab1c8bbfea9434576e15465cfbd66e8c6565345d83e248d6fcd3748e82806c95","private_key":"885083158aab574e1ab94645ea978b0e98a06605b1cbbfc48450f46522476b22","name":"node56","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"80d0a541f041fbab580164228d09af6be9e3beec5301e998398231362298835e","private_key":"5ee85ab61759ce457c89801346b14f68f1f16d6c59b2c034a2aef87242488041","name":"node57","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"66d586d2f06e0b26d051d3fed949bf8f36ca3ba4c64b8230987ae0c7d29c2dfd","private_key":"0bc8227f073c115b1ecd2add3e0ce8f58974528279de040eeaf189abfa6bc506","name":"node58","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"bd5b11dfb3bf903c4cb8ce44756e41fefb5582deb79055ccc65fe7b47b2a1a41","private_key":"a548a32f98cf950d6811b01fd764cd17b51d7012b18e944b3bb90dc660e35817","name":"node59","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"eebe8082b0f6efbade062eb49f2592519524a8c3f22975ba901ed9755a9d0ead","private_key":"55c9fd7b7ce7d50376a51050ac0a0a61b4657f9392ad14509bf432758a02b102","name":"node60","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"f0517f6a1187d0f454aaf98506fdc830e14d31259fbf195f1694078dcaa5582f","private_key":"daa4e758bca88df487bb1bcd41e59643256d23f237f96d654010801c07240435","name":"node61","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"2597463214ea0faa3db781fa8cb336da59aff43553f045a0f704ab10f5f54cf2","private_key":"0090eced227d4fcb0e639349e4164040aa3c9d858531c2df416023c416753a5d","name":"node62","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"8c5f07bdc96c7bbd8fc7bcf5e3ba841cabfc47f910acbf416730f5e67f847b34","private_key":"96f0c30375428cd6184d43806adfbc9a4600e0180b50afa6ce06bfb1581cf1e0","name":"node63","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"4ff286f9e1f7d2d0a8e14a5547e03580d67dbedd1e2ca0c88ad01a2e56c0f45c","private_key":"4738e28b9e3f0920f9c89ac195862299c670e5b354e5bbd644395336a446166f","name":"node64","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}}],"conns":[{"one":"7f4198ddcfb55e9f690024cc19ce4f32d5bbc8d56cfb43f806513a74e52dc277","other":"e75238135f0c071ca541fb0a9569e38036c51af350a16bf4a1526f1752318059","up":true},{"one":"e75238135f0c071ca541fb0a9569e38036c51af350a16bf4a1526f1752318059","other":"1102d287c72becf1bfa8567cf43cc2051aed16a79196f9109cdedf829a2a5a2d","up":true},{"one":"1102d287c72becf1bfa8567cf43cc2051aed16a79196f9109cdedf829a2a5a2d","other":"34ad9431b71cdeae0c1941b3ae680387b3cedd815f39d65034892920aa869f38","up":true},{"one":"113d7850e8169761d357e0ad5a262560fe6137fc6a992b3b2cedcebbe5d5f6a2","other":"e0205da225e77fa15332864eaef1c437be306090b0489faf3f6725cb3c5b4712","up":true},{"one":"b2a236a2acf5cf675c521a3e87ef847933e1736c220a54cf66db4ba03f369483","other":"71fe0f225ee23106cb6d6c57cdeb49ecc4ffb2b13441956eca6d94505aa2a72c","up":true},{"one":"b04c7f907691ac51adab6fe657dd4592044ca4222ee60eb86c7851c855057a8e","other":"6a5cd611a5a8a94c82d6d3b40912909f326d4111c82983c3f719cfa3f621312f","up":true},{"one":"e0205da225e77fa15332864eaef1c437be306090b0489faf3f6725cb3c5b4712","other":"2218170def9962d400bf932ced91f3c14595062b50ef86eb4d2f7fd384fc8597","up":true},{"one":"6d29dbed992f0f0008a72978045df08a4ae1753571fdefbb676f52b5ff843433","other":"292bb75a5407dda2ad7b642b1b790264e7ce16bbd8cd22e512eeec3d600ea520","up":true},{"one":"c85b39668486c5eb3ea9f312d9897bda5688c4995318e4e64a0be2ac0cb47692","other":"5fd664467fac65594c3dd6f89bf2a5398e28aa95d248c3a3d21abaecfebfde5d","up":true},{"one":"bece4d99fafdcebd22f03a4640026c55f7155ff814b295a2aec7a98d3973595a","other":"b04c7f907691ac51adab6fe657dd4592044ca4222ee60eb86c7851c855057a8e","up":true},{"one":"2218170def9962d400bf932ced91f3c14595062b50ef86eb4d2f7fd384fc8597","other":"c85b39668486c5eb3ea9f312d9897bda5688c4995318e4e64a0be2ac0cb47692","up":true},{"one":"9e9451cb84ec10881e16a87d84b0e4e1aa78f5e9bc49ca5244a3da414b5210da","other":"cab41b44229682f11d6aca3d3136597524e9eb4e1640147b87f3f8c7227959f1","up":true},{"one":"34ad9431b71cdeae0c1941b3ae680387b3cedd815f39d65034892920aa869f38","other":"9bb12f66006f3347790c2c4e74e5463fd6ab651768296ee41de51757b9f40b89","up":true},{"one":"cee0b72bcb0a112c9aa33eb80166d279c2cc0216782a19454a086c0147b8126d","other":"d79a1e6f3dc0e3382eaccdc1fb0f49cd66f483addcc52d767fc12f4b6754e206","up":true},{"one":"cec77f188351d24d5067de2e0cc57b48a9a5455d78bda6b7632d14a679070c3d","other":"6d29dbed992f0f0008a72978045df08a4ae1753571fdefbb676f52b5ff843433","up":true},{"one":"cab41b44229682f11d6aca3d3136597524e9eb4e1640147b87f3f8c7227959f1","other":"b2a236a2acf5cf675c521a3e87ef847933e1736c220a54cf66db4ba03f369483","up":true},{"one":"6a5cd611a5a8a94c82d6d3b40912909f326d4111c82983c3f719cfa3f621312f","other":"cee0b72bcb0a112c9aa33eb80166d279c2cc0216782a19454a086c0147b8126d","up":true},{"one":"6b336cfff0d431edce4cee0cf35ffb1e50b7a5c6e15d3d816fb1bb1cf6488f22","other":"903561af2459b0e46d8188121e66d8b999ad39341944eb099eff70213244c813","up":true},{"one":"9045e912dc0acaf9d86ec75d98e93bc90832dbca1119641f8569a98040be940c","other":"cf69e5f8effee000dc16109a7e0d04e4e13aeb2f6c089999e555946a46e548f6","up":true},{"one":"71fe0f225ee23106cb6d6c57cdeb49ecc4ffb2b13441956eca6d94505aa2a72c","other":"a365bf427dc109429657033f4b7b22575b6a8d955d6eaf44288725e5e47b0a86","up":true},{"one":"0f5c44c74d4cc9b10d283a0099fba5f925c3455f5308d2044c377f8a5181703a","other":"b52335dfb9367471204dfba231ba274815b859164e19ee872e20326d0575b88a","up":true},{"one":"903561af2459b0e46d8188121e66d8b999ad39341944eb099eff70213244c813","other":"e0463c87b0b7deefbd521721430e0fd24683aa8b41c65f259d678d9844bd800f","up":true},{"one":"cc708e47196a6435b13b18f213f50bf075f3f97100d52d18d70052efc301c91a","other":"f3a57f887ddcf1793207948374ce67da9f6a1227f253e3a90acc5b36a48ee645","up":true},{"one":"a365bf427dc109429657033f4b7b22575b6a8d955d6eaf44288725e5e47b0a86","other":"5a4fa4f4482dbaf7bbe6c3d2ae28c35187fbd0f00d1293be41719aa8e5e4a388","up":true},{"one":"d79a1e6f3dc0e3382eaccdc1fb0f49cd66f483addcc52d767fc12f4b6754e206","other":"39e710327f4176b5ed7ebc921db592a6b9197b237029faab4525a00d18470e8a","up":true},{"one":"e0463c87b0b7deefbd521721430e0fd24683aa8b41c65f259d678d9844bd800f","other":"c0d6b4950771888e9770051d341f2071fecdb09e28c289e251668327eb8ccf66","up":true},{"one":"f3a57f887ddcf1793207948374ce67da9f6a1227f253e3a90acc5b36a48ee645","other":"5e64f329525e17129b3f51580b399a5f09d7652a663bbaac83c8173dd9fded41","up":true},{"one":"5205bbd1c298864045f08fe7cc4c2409055db3ee4d790d2587c9ca3366acd977","other":"0f5c44c74d4cc9b10d283a0099fba5f925c3455f5308d2044c377f8a5181703a","up":true},{"one":"39e710327f4176b5ed7ebc921db592a6b9197b237029faab4525a00d18470e8a","other":"5205bbd1c298864045f08fe7cc4c2409055db3ee4d790d2587c9ca3366acd977","up":true},{"one":"5fd664467fac65594c3dd6f89bf2a5398e28aa95d248c3a3d21abaecfebfde5d","other":"461c7f8f8948693dea717679ffc4bbf0eb573f4597543993e3be39d3594fdd47","up":true},{"one":"5e64f329525e17129b3f51580b399a5f09d7652a663bbaac83c8173dd9fded41","other":"74117e299fffce1ef7f323ea1d6fb1c509cd5581f6c53e0f5e9af8d0b83748f2","up":true},{"one":"c0d6b4950771888e9770051d341f2071fecdb09e28c289e251668327eb8ccf66","other":"cfc872fe7f213f4ada5ec413037450ae2b141157c83998b9520ac2ddb51707bd","up":true},{"one":"74117e299fffce1ef7f323ea1d6fb1c509cd5581f6c53e0f5e9af8d0b83748f2","other":"a416fa852bb425e00b805ffabb525ae45024403685a0e8a67815617fd1d078da","up":true},{"one":"cfc872fe7f213f4ada5ec413037450ae2b141157c83998b9520ac2ddb51707bd","other":"cec77f188351d24d5067de2e0cc57b48a9a5455d78bda6b7632d14a679070c3d","up":true},{"one":"a416fa852bb425e00b805ffabb525ae45024403685a0e8a67815617fd1d078da","other":"3bc8d405359a665d3f6d64cf9ba194b7e614c2050add0a1205be7f6c3c984e1d","up":true},{"one":"461c7f8f8948693dea717679ffc4bbf0eb573f4597543993e3be39d3594fdd47","other":"df45befed47eec3248ce3b55e9cc56f8d718fc13dcb1a03755cdc0cda89e4dcb","up":true},{"one":"e45d7b4870acb8a984c065cce7bd17acaf79c5ad4d477f810563315aebacd8e3","other":"113d7850e8169761d357e0ad5a262560fe6137fc6a992b3b2cedcebbe5d5f6a2","up":true},{"one":"1f17c9223987ccf4d9683df0664cd568164c69d2f62bad2a79ef4636623ba87a","other":"0f988a170fdc7761b8d9814a1a7ac0cc0e98483b91841046b9948783befeed88","up":true},{"one":"df45befed47eec3248ce3b55e9cc56f8d718fc13dcb1a03755cdc0cda89e4dcb","other":"aa655b48b225d692184a21a2f7162950851a914f89671cc1af3e87a21b37c5e3","up":true},{"one":"aa655b48b225d692184a21a2f7162950851a914f89671cc1af3e87a21b37c5e3","other":"6b336cfff0d431edce4cee0cf35ffb1e50b7a5c6e15d3d816fb1bb1cf6488f22","up":true},{"one":"9bb12f66006f3347790c2c4e74e5463fd6ab651768296ee41de51757b9f40b89","other":"9045e912dc0acaf9d86ec75d98e93bc90832dbca1119641f8569a98040be940c","up":true},{"one":"292bb75a5407dda2ad7b642b1b790264e7ce16bbd8cd22e512eeec3d600ea520","other":"9e9451cb84ec10881e16a87d84b0e4e1aa78f5e9bc49ca5244a3da414b5210da","up":true},{"one":"5a4fa4f4482dbaf7bbe6c3d2ae28c35187fbd0f00d1293be41719aa8e5e4a388","other":"bece4d99fafdcebd22f03a4640026c55f7155ff814b295a2aec7a98d3973595a","up":true},{"one":"cf69e5f8effee000dc16109a7e0d04e4e13aeb2f6c089999e555946a46e548f6","other":"cc708e47196a6435b13b18f213f50bf075f3f97100d52d18d70052efc301c91a","up":true},{"one":"b52335dfb9367471204dfba231ba274815b859164e19ee872e20326d0575b88a","other":"1f17c9223987ccf4d9683df0664cd568164c69d2f62bad2a79ef4636623ba87a","up":true},{"one":"85d645e3738c441f508671f52cde0aa7ae2032c082c86b7ca6af92598d7174d5","other":"647ccb25b23a917f611a46a72b12fd457610f07143155e1f828b41fd2d3e0b58","up":true},{"one":"4ff286f9e1f7d2d0a8e14a5547e03580d67dbedd1e2ca0c88ad01a2e56c0f45c","other":"7f4198ddcfb55e9f690024cc19ce4f32d5bbc8d56cfb43f806513a74e52dc277","up":true},{"one":"bd5b11dfb3bf903c4cb8ce44756e41fefb5582deb79055ccc65fe7b47b2a1a41","other":"eebe8082b0f6efbade062eb49f2592519524a8c3f22975ba901ed9755a9d0ead","up":true},{"one":"eebe8082b0f6efbade062eb49f2592519524a8c3f22975ba901ed9755a9d0ead","other":"f0517f6a1187d0f454aaf98506fdc830e14d31259fbf195f1694078dcaa5582f","up":true},{"one":"f0517f6a1187d0f454aaf98506fdc830e14d31259fbf195f1694078dcaa5582f","other":"2597463214ea0faa3db781fa8cb336da59aff43553f045a0f704ab10f5f54cf2","up":true},{"one":"2597463214ea0faa3db781fa8cb336da59aff43553f045a0f704ab10f5f54cf2","other":"8c5f07bdc96c7bbd8fc7bcf5e3ba841cabfc47f910acbf416730f5e67f847b34","up":true},{"one":"8c5f07bdc96c7bbd8fc7bcf5e3ba841cabfc47f910acbf416730f5e67f847b34","other":"4ff286f9e1f7d2d0a8e14a5547e03580d67dbedd1e2ca0c88ad01a2e56c0f45c","up":true},{"one":"0f988a170fdc7761b8d9814a1a7ac0cc0e98483b91841046b9948783befeed88","other":"0b45cb8af78fb1af7ab55d022ed00da66393b7eea1b7a3a2f802ffeba4c79dd3","up":true},{"one":"0b45cb8af78fb1af7ab55d022ed00da66393b7eea1b7a3a2f802ffeba4c79dd3","other":"9e384d8e6d319016c2807884e0b46da4ce9fcb426efb76224ce41b5d4dae5632","up":true},{"one":"3bc8d405359a665d3f6d64cf9ba194b7e614c2050add0a1205be7f6c3c984e1d","other":"e45d7b4870acb8a984c065cce7bd17acaf79c5ad4d477f810563315aebacd8e3","up":true},{"one":"66d586d2f06e0b26d051d3fed949bf8f36ca3ba4c64b8230987ae0c7d29c2dfd","other":"bd5b11dfb3bf903c4cb8ce44756e41fefb5582deb79055ccc65fe7b47b2a1a41","up":true},{"one":"88b4c3d6ae6a1d4c39caef84d56f308b58e2ca0477cc5197f3f5be41c890bd7c","other":"ab1c8bbfea9434576e15465cfbd66e8c6565345d83e248d6fcd3748e82806c95","up":true},{"one":"9e384d8e6d319016c2807884e0b46da4ce9fcb426efb76224ce41b5d4dae5632","other":"5c197e623ab40c0de2c83a4ffd16930cf791e60357f162f5b2a2e865ff0cea73","up":true},{"one":"ab1c8bbfea9434576e15465cfbd66e8c6565345d83e248d6fcd3748e82806c95","other":"80d0a541f041fbab580164228d09af6be9e3beec5301e998398231362298835e","up":true},{"one":"5c197e623ab40c0de2c83a4ffd16930cf791e60357f162f5b2a2e865ff0cea73","other":"b45f23d6ac8824b480f71f25abf5aa2c9db4c3a904660d391768168430355827","up":true},{"one":"80d0a541f041fbab580164228d09af6be9e3beec5301e998398231362298835e","other":"66d586d2f06e0b26d051d3fed949bf8f36ca3ba4c64b8230987ae0c7d29c2dfd","up":true},{"one":"b45f23d6ac8824b480f71f25abf5aa2c9db4c3a904660d391768168430355827","other":"85d645e3738c441f508671f52cde0aa7ae2032c082c86b7ca6af92598d7174d5","up":true},{"one":"7fe48790180ab38e7687009847285f7af5e73b21b60cfad9d844ba8b2e7576e4","other":"88b4c3d6ae6a1d4c39caef84d56f308b58e2ca0477cc5197f3f5be41c890bd7c","up":true},{"one":"647ccb25b23a917f611a46a72b12fd457610f07143155e1f828b41fd2d3e0b58","other":"7fe48790180ab38e7687009847285f7af5e73b21b60cfad9d844ba8b2e7576e4","up":true},{"one":"7f4198ddcfb55e9f690024cc19ce4f32d5bbc8d56cfb43f806513a74e52dc277","other":"7fe48790180ab38e7687009847285f7af5e73b21b60cfad9d844ba8b2e7576e4","up":true},{"one":"f3a57f887ddcf1793207948374ce67da9f6a1227f253e3a90acc5b36a48ee645","other":"f0517f6a1187d0f454aaf98506fdc830e14d31259fbf195f1694078dcaa5582f","up":true},{"one":"9045e912dc0acaf9d86ec75d98e93bc90832dbca1119641f8569a98040be940c","other":"b52335dfb9367471204dfba231ba274815b859164e19ee872e20326d0575b88a","up":true},{"one":"9bb12f66006f3347790c2c4e74e5463fd6ab651768296ee41de51757b9f40b89","other":"b52335dfb9367471204dfba231ba274815b859164e19ee872e20326d0575b88a","up":true},{"one":"cf69e5f8effee000dc16109a7e0d04e4e13aeb2f6c089999e555946a46e548f6","other":"cec77f188351d24d5067de2e0cc57b48a9a5455d78bda6b7632d14a679070c3d","up":true},{"one":"34ad9431b71cdeae0c1941b3ae680387b3cedd815f39d65034892920aa869f38","other":"3bc8d405359a665d3f6d64cf9ba194b7e614c2050add0a1205be7f6c3c984e1d","up":true},{"one":"3bc8d405359a665d3f6d64cf9ba194b7e614c2050add0a1205be7f6c3c984e1d","other":"2218170def9962d400bf932ced91f3c14595062b50ef86eb4d2f7fd384fc8597","up":true},{"one":"aa655b48b225d692184a21a2f7162950851a914f89671cc1af3e87a21b37c5e3","other":"ab1c8bbfea9434576e15465cfbd66e8c6565345d83e248d6fcd3748e82806c95","up":true},{"one":"5e64f329525e17129b3f51580b399a5f09d7652a663bbaac83c8173dd9fded41","other":"461c7f8f8948693dea717679ffc4bbf0eb573f4597543993e3be39d3594fdd47","up":true},{"one":"e45d7b4870acb8a984c065cce7bd17acaf79c5ad4d477f810563315aebacd8e3","other":"e0205da225e77fa15332864eaef1c437be306090b0489faf3f6725cb3c5b4712","up":true},{"one":"e0205da225e77fa15332864eaef1c437be306090b0489faf3f6725cb3c5b4712","other":"e0463c87b0b7deefbd521721430e0fd24683aa8b41c65f259d678d9844bd800f","up":true},{"one":"2218170def9962d400bf932ced91f3c14595062b50ef86eb4d2f7fd384fc8597","other":"34ad9431b71cdeae0c1941b3ae680387b3cedd815f39d65034892920aa869f38","up":true},{"one":"c85b39668486c5eb3ea9f312d9897bda5688c4995318e4e64a0be2ac0cb47692","other":"cec77f188351d24d5067de2e0cc57b48a9a5455d78bda6b7632d14a679070c3d","up":true},{"one":"5fd664467fac65594c3dd6f89bf2a5398e28aa95d248c3a3d21abaecfebfde5d","other":"7f4198ddcfb55e9f690024cc19ce4f32d5bbc8d56cfb43f806513a74e52dc277","up":true},{"one":"39e710327f4176b5ed7ebc921db592a6b9197b237029faab4525a00d18470e8a","other":"3bc8d405359a665d3f6d64cf9ba194b7e614c2050add0a1205be7f6c3c984e1d","up":true},{"one":"461c7f8f8948693dea717679ffc4bbf0eb573f4597543993e3be39d3594fdd47","other":"7f4198ddcfb55e9f690024cc19ce4f32d5bbc8d56cfb43f806513a74e52dc277","up":true},{"one":"df45befed47eec3248ce3b55e9cc56f8d718fc13dcb1a03755cdc0cda89e4dcb","other":"cec77f188351d24d5067de2e0cc57b48a9a5455d78bda6b7632d14a679070c3d","up":true},{"one":"a416fa852bb425e00b805ffabb525ae45024403685a0e8a67815617fd1d078da","other":"ab1c8bbfea9434576e15465cfbd66e8c6565345d83e248d6fcd3748e82806c95","up":true},{"one":"903561af2459b0e46d8188121e66d8b999ad39341944eb099eff70213244c813","other":"9bb12f66006f3347790c2c4e74e5463fd6ab651768296ee41de51757b9f40b89","up":true},{"one":"5c197e623ab40c0de2c83a4ffd16930cf791e60357f162f5b2a2e865ff0cea73","other":"5fd664467fac65594c3dd6f89bf2a5398e28aa95d248c3a3d21abaecfebfde5d","up":true},{"one":"e0463c87b0b7deefbd521721430e0fd24683aa8b41c65f259d678d9844bd800f","other":"eebe8082b0f6efbade062eb49f2592519524a8c3f22975ba901ed9755a9d0ead","up":true},{"one":"5205bbd1c298864045f08fe7cc4c2409055db3ee4d790d2587c9ca3366acd977","other":"5a4fa4f4482dbaf7bbe6c3d2ae28c35187fbd0f00d1293be41719aa8e5e4a388","up":true},{"one":"cfc872fe7f213f4ada5ec413037450ae2b141157c83998b9520ac2ddb51707bd","other":"cf69e5f8effee000dc16109a7e0d04e4e13aeb2f6c089999e555946a46e548f6","up":true},{"one":"b52335dfb9367471204dfba231ba274815b859164e19ee872e20326d0575b88a","other":"bd5b11dfb3bf903c4cb8ce44756e41fefb5582deb79055ccc65fe7b47b2a1a41","up":true},{"one":"6a5cd611a5a8a94c82d6d3b40912909f326d4111c82983c3f719cfa3f621312f","other":"7fe48790180ab38e7687009847285f7af5e73b21b60cfad9d844ba8b2e7576e4","up":true},{"one":"1f17c9223987ccf4d9683df0664cd568164c69d2f62bad2a79ef4636623ba87a","other":"1102d287c72becf1bfa8567cf43cc2051aed16a79196f9109cdedf829a2a5a2d","up":true},{"one":"292bb75a5407dda2ad7b642b1b790264e7ce16bbd8cd22e512eeec3d600ea520","other":"3bc8d405359a665d3f6d64cf9ba194b7e614c2050add0a1205be7f6c3c984e1d","up":true},{"one":"0f988a170fdc7761b8d9814a1a7ac0cc0e98483b91841046b9948783befeed88","other":"1102d287c72becf1bfa8567cf43cc2051aed16a79196f9109cdedf829a2a5a2d","up":true},{"one":"cab41b44229682f11d6aca3d3136597524e9eb4e1640147b87f3f8c7227959f1","other":"cec77f188351d24d5067de2e0cc57b48a9a5455d78bda6b7632d14a679070c3d","up":true},{"one":"4ff286f9e1f7d2d0a8e14a5547e03580d67dbedd1e2ca0c88ad01a2e56c0f45c","other":"5a4fa4f4482dbaf7bbe6c3d2ae28c35187fbd0f00d1293be41719aa8e5e4a388","up":true},{"one":"b2a236a2acf5cf675c521a3e87ef847933e1736c220a54cf66db4ba03f369483","other":"b52335dfb9367471204dfba231ba274815b859164e19ee872e20326d0575b88a","up":true},{"one":"a365bf427dc109429657033f4b7b22575b6a8d955d6eaf44288725e5e47b0a86","other":"ab1c8bbfea9434576e15465cfbd66e8c6565345d83e248d6fcd3748e82806c95","up":true},{"one":"6b336cfff0d431edce4cee0cf35ffb1e50b7a5c6e15d3d816fb1bb1cf6488f22","other":"74117e299fffce1ef7f323ea1d6fb1c509cd5581f6c53e0f5e9af8d0b83748f2","up":true},{"one":"9e384d8e6d319016c2807884e0b46da4ce9fcb426efb76224ce41b5d4dae5632","other":"9bb12f66006f3347790c2c4e74e5463fd6ab651768296ee41de51757b9f40b89","up":true},{"one":"647ccb25b23a917f611a46a72b12fd457610f07143155e1f828b41fd2d3e0b58","other":"7f4198ddcfb55e9f690024cc19ce4f32d5bbc8d56cfb43f806513a74e52dc277","up":true},{"one":"0b45cb8af78fb1af7ab55d022ed00da66393b7eea1b7a3a2f802ffeba4c79dd3","other":"1102d287c72becf1bfa8567cf43cc2051aed16a79196f9109cdedf829a2a5a2d","up":true},{"one":"7fe48790180ab38e7687009847285f7af5e73b21b60cfad9d844ba8b2e7576e4","other":"74117e299fffce1ef7f323ea1d6fb1c509cd5581f6c53e0f5e9af8d0b83748f2","up":true},{"one":"5a4fa4f4482dbaf7bbe6c3d2ae28c35187fbd0f00d1293be41719aa8e5e4a388","other":"5fd664467fac65594c3dd6f89bf2a5398e28aa95d248c3a3d21abaecfebfde5d","up":true},{"one":"cc708e47196a6435b13b18f213f50bf075f3f97100d52d18d70052efc301c91a","other":"cfc872fe7f213f4ada5ec413037450ae2b141157c83998b9520ac2ddb51707bd","up":true},{"one":"cee0b72bcb0a112c9aa33eb80166d279c2cc0216782a19454a086c0147b8126d","other":"cec77f188351d24d5067de2e0cc57b48a9a5455d78bda6b7632d14a679070c3d","up":true},{"one":"88b4c3d6ae6a1d4c39caef84d56f308b58e2ca0477cc5197f3f5be41c890bd7c","other":"8c5f07bdc96c7bbd8fc7bcf5e3ba841cabfc47f910acbf416730f5e67f847b34","up":true},{"one":"d79a1e6f3dc0e3382eaccdc1fb0f49cd66f483addcc52d767fc12f4b6754e206","other":"df45befed47eec3248ce3b55e9cc56f8d718fc13dcb1a03755cdc0cda89e4dcb","up":true},{"one":"0f5c44c74d4cc9b10d283a0099fba5f925c3455f5308d2044c377f8a5181703a","other":"0b45cb8af78fb1af7ab55d022ed00da66393b7eea1b7a3a2f802ffeba4c79dd3","up":true},{"one":"eebe8082b0f6efbade062eb49f2592519524a8c3f22975ba901ed9755a9d0ead","other":"e45d7b4870acb8a984c065cce7bd17acaf79c5ad4d477f810563315aebacd8e3","up":true},{"one":"66d586d2f06e0b26d051d3fed949bf8f36ca3ba4c64b8230987ae0c7d29c2dfd","other":"647ccb25b23a917f611a46a72b12fd457610f07143155e1f828b41fd2d3e0b58","up":true},{"one":"f0517f6a1187d0f454aaf98506fdc830e14d31259fbf195f1694078dcaa5582f","other":"e0463c87b0b7deefbd521721430e0fd24683aa8b41c65f259d678d9844bd800f","up":true},{"one":"2597463214ea0faa3db781fa8cb336da59aff43553f045a0f704ab10f5f54cf2","other":"292bb75a5407dda2ad7b642b1b790264e7ce16bbd8cd22e512eeec3d600ea520","up":true},{"one":"9e9451cb84ec10881e16a87d84b0e4e1aa78f5e9bc49ca5244a3da414b5210da","other":"9bb12f66006f3347790c2c4e74e5463fd6ab651768296ee41de51757b9f40b89","up":true},{"one":"8c5f07bdc96c7bbd8fc7bcf5e3ba841cabfc47f910acbf416730f5e67f847b34","other":"9bb12f66006f3347790c2c4e74e5463fd6ab651768296ee41de51757b9f40b89","up":true},{"one":"b04c7f907691ac51adab6fe657dd4592044ca4222ee60eb86c7851c855057a8e","other":"b2a236a2acf5cf675c521a3e87ef847933e1736c220a54cf66db4ba03f369483","up":true},{"one":"74117e299fffce1ef7f323ea1d6fb1c509cd5581f6c53e0f5e9af8d0b83748f2","other":"7f4198ddcfb55e9f690024cc19ce4f32d5bbc8d56cfb43f806513a74e52dc277","up":true},{"one":"113d7850e8169761d357e0ad5a262560fe6137fc6a992b3b2cedcebbe5d5f6a2","other":"1102d287c72becf1bfa8567cf43cc2051aed16a79196f9109cdedf829a2a5a2d","up":true},{"one":"ab1c8bbfea9434576e15465cfbd66e8c6565345d83e248d6fcd3748e82806c95","other":"3bc8d405359a665d3f6d64cf9ba194b7e614c2050add0a1205be7f6c3c984e1d","up":true},{"one":"85d645e3738c441f508671f52cde0aa7ae2032c082c86b7ca6af92598d7174d5","other":"8c5f07bdc96c7bbd8fc7bcf5e3ba841cabfc47f910acbf416730f5e67f847b34","up":true},{"one":"bd5b11dfb3bf903c4cb8ce44756e41fefb5582deb79055ccc65fe7b47b2a1a41","other":"bece4d99fafdcebd22f03a4640026c55f7155ff814b295a2aec7a98d3973595a","up":true},{"one":"bece4d99fafdcebd22f03a4640026c55f7155ff814b295a2aec7a98d3973595a","other":"b2a236a2acf5cf675c521a3e87ef847933e1736c220a54cf66db4ba03f369483","up":true},{"one":"6d29dbed992f0f0008a72978045df08a4ae1753571fdefbb676f52b5ff843433","other":"71fe0f225ee23106cb6d6c57cdeb49ecc4ffb2b13441956eca6d94505aa2a72c","up":true},{"one":"1102d287c72becf1bfa8567cf43cc2051aed16a79196f9109cdedf829a2a5a2d","other":"0f5c44c74d4cc9b10d283a0099fba5f925c3455f5308d2044c377f8a5181703a","up":true},{"one":"71fe0f225ee23106cb6d6c57cdeb49ecc4ffb2b13441956eca6d94505aa2a72c","other":"74117e299fffce1ef7f323ea1d6fb1c509cd5581f6c53e0f5e9af8d0b83748f2","up":true},{"one":"e75238135f0c071ca541fb0a9569e38036c51af350a16bf4a1526f1752318059","other":"e45d7b4870acb8a984c065cce7bd17acaf79c5ad4d477f810563315aebacd8e3","up":true},{"one":"cec77f188351d24d5067de2e0cc57b48a9a5455d78bda6b7632d14a679070c3d","other":"a365bf427dc109429657033f4b7b22575b6a8d955d6eaf44288725e5e47b0a86","up":true},{"one":"80d0a541f041fbab580164228d09af6be9e3beec5301e998398231362298835e","other":"8c5f07bdc96c7bbd8fc7bcf5e3ba841cabfc47f910acbf416730f5e67f847b34","up":true},{"one":"1102d287c72becf1bfa8567cf43cc2051aed16a79196f9109cdedf829a2a5a2d","other":"461c7f8f8948693dea717679ffc4bbf0eb573f4597543993e3be39d3594fdd47","up":true},{"one":"c0d6b4950771888e9770051d341f2071fecdb09e28c289e251668327eb8ccf66","other":"cf69e5f8effee000dc16109a7e0d04e4e13aeb2f6c089999e555946a46e548f6","up":true},{"one":"7f4198ddcfb55e9f690024cc19ce4f32d5bbc8d56cfb43f806513a74e52dc277","other":"71fe0f225ee23106cb6d6c57cdeb49ecc4ffb2b13441956eca6d94505aa2a72c","up":true},{"one":"e75238135f0c071ca541fb0a9569e38036c51af350a16bf4a1526f1752318059","other":"e0463c87b0b7deefbd521721430e0fd24683aa8b41c65f259d678d9844bd800f","up":true},{"one":"df45befed47eec3248ce3b55e9cc56f8d718fc13dcb1a03755cdc0cda89e4dcb","other":"cee0b72bcb0a112c9aa33eb80166d279c2cc0216782a19454a086c0147b8126d","up":true},{"one":"34ad9431b71cdeae0c1941b3ae680387b3cedd815f39d65034892920aa869f38","other":"39e710327f4176b5ed7ebc921db592a6b9197b237029faab4525a00d18470e8a","up":true},{"one":"5a4fa4f4482dbaf7bbe6c3d2ae28c35187fbd0f00d1293be41719aa8e5e4a388","other":"5e64f329525e17129b3f51580b399a5f09d7652a663bbaac83c8173dd9fded41","up":true},{"one":"9bb12f66006f3347790c2c4e74e5463fd6ab651768296ee41de51757b9f40b89","other":"f0517f6a1187d0f454aaf98506fdc830e14d31259fbf195f1694078dcaa5582f","up":true},{"one":"aa655b48b225d692184a21a2f7162950851a914f89671cc1af3e87a21b37c5e3","other":"a365bf427dc109429657033f4b7b22575b6a8d955d6eaf44288725e5e47b0a86","up":true},{"one":"9045e912dc0acaf9d86ec75d98e93bc90832dbca1119641f8569a98040be940c","other":"903561af2459b0e46d8188121e66d8b999ad39341944eb099eff70213244c813","up":true},{"one":"6b336cfff0d431edce4cee0cf35ffb1e50b7a5c6e15d3d816fb1bb1cf6488f22","other":"6a5cd611a5a8a94c82d6d3b40912909f326d4111c82983c3f719cfa3f621312f","up":true},{"one":"cf69e5f8effee000dc16109a7e0d04e4e13aeb2f6c089999e555946a46e548f6","other":"cee0b72bcb0a112c9aa33eb80166d279c2cc0216782a19454a086c0147b8126d","up":true},{"one":"903561af2459b0e46d8188121e66d8b999ad39341944eb099eff70213244c813","other":"9e9451cb84ec10881e16a87d84b0e4e1aa78f5e9bc49ca5244a3da414b5210da","up":true},{"one":"cc708e47196a6435b13b18f213f50bf075f3f97100d52d18d70052efc301c91a","other":"cee0b72bcb0a112c9aa33eb80166d279c2cc0216782a19454a086c0147b8126d","up":true},{"one":"7fe48790180ab38e7687009847285f7af5e73b21b60cfad9d844ba8b2e7576e4","other":"71fe0f225ee23106cb6d6c57cdeb49ecc4ffb2b13441956eca6d94505aa2a72c","up":true},{"one":"e0463c87b0b7deefbd521721430e0fd24683aa8b41c65f259d678d9844bd800f","other":"e45d7b4870acb8a984c065cce7bd17acaf79c5ad4d477f810563315aebacd8e3","up":true},{"one":"ab1c8bbfea9434576e15465cfbd66e8c6565345d83e248d6fcd3748e82806c95","other":"d79a1e6f3dc0e3382eaccdc1fb0f49cd66f483addcc52d767fc12f4b6754e206","up":true},{"one":"f3a57f887ddcf1793207948374ce67da9f6a1227f253e3a90acc5b36a48ee645","other":"e45d7b4870acb8a984c065cce7bd17acaf79c5ad4d477f810563315aebacd8e3","up":true},{"one":"bece4d99fafdcebd22f03a4640026c55f7155ff814b295a2aec7a98d3973595a","other":"b52335dfb9367471204dfba231ba274815b859164e19ee872e20326d0575b88a","up":true},{"one":"c0d6b4950771888e9770051d341f2071fecdb09e28c289e251668327eb8ccf66","other":"cee0b72bcb0a112c9aa33eb80166d279c2cc0216782a19454a086c0147b8126d","up":true},{"one":"88b4c3d6ae6a1d4c39caef84d56f308b58e2ca0477cc5197f3f5be41c890bd7c","other":"80d0a541f041fbab580164228d09af6be9e3beec5301e998398231362298835e","up":true},{"one":"5e64f329525e17129b3f51580b399a5f09d7652a663bbaac83c8173dd9fded41","other":"5fd664467fac65594c3dd6f89bf2a5398e28aa95d248c3a3d21abaecfebfde5d","up":true},{"one":"b04c7f907691ac51adab6fe657dd4592044ca4222ee60eb86c7851c855057a8e","other":"b52335dfb9367471204dfba231ba274815b859164e19ee872e20326d0575b88a","up":true},{"one":"cfc872fe7f213f4ada5ec413037450ae2b141157c83998b9520ac2ddb51707bd","other":"cee0b72bcb0a112c9aa33eb80166d279c2cc0216782a19454a086c0147b8126d","up":true},{"one":"74117e299fffce1ef7f323ea1d6fb1c509cd5581f6c53e0f5e9af8d0b83748f2","other":"113d7850e8169761d357e0ad5a262560fe6137fc6a992b3b2cedcebbe5d5f6a2","up":true},{"one":"6a5cd611a5a8a94c82d6d3b40912909f326d4111c82983c3f719cfa3f621312f","other":"6d29dbed992f0f0008a72978045df08a4ae1753571fdefbb676f52b5ff843433","up":true},{"one":"cec77f188351d24d5067de2e0cc57b48a9a5455d78bda6b7632d14a679070c3d","other":"ab1c8bbfea9434576e15465cfbd66e8c6565345d83e248d6fcd3748e82806c95","up":true},{"one":"4ff286f9e1f7d2d0a8e14a5547e03580d67dbedd1e2ca0c88ad01a2e56c0f45c","other":"461c7f8f8948693dea717679ffc4bbf0eb573f4597543993e3be39d3594fdd47","up":true},{"one":"a416fa852bb425e00b805ffabb525ae45024403685a0e8a67815617fd1d078da","other":"a365bf427dc109429657033f4b7b22575b6a8d955d6eaf44288725e5e47b0a86","up":true},{"one":"cee0b72bcb0a112c9aa33eb80166d279c2cc0216782a19454a086c0147b8126d","other":"cab41b44229682f11d6aca3d3136597524e9eb4e1640147b87f3f8c7227959f1","up":true},{"one":"6d29dbed992f0f0008a72978045df08a4ae1753571fdefbb676f52b5ff843433","other":"66d586d2f06e0b26d051d3fed949bf8f36ca3ba4c64b8230987ae0c7d29c2dfd","up":true},{"one":"80d0a541f041fbab580164228d09af6be9e3beec5301e998398231362298835e","other":"9bb12f66006f3347790c2c4e74e5463fd6ab651768296ee41de51757b9f40b89","up":true},{"one":"3bc8d405359a665d3f6d64cf9ba194b7e614c2050add0a1205be7f6c3c984e1d","other":"5205bbd1c298864045f08fe7cc4c2409055db3ee4d790d2587c9ca3366acd977","up":true},{"one":"d79a1e6f3dc0e3382eaccdc1fb0f49cd66f483addcc52d767fc12f4b6754e206","other":"cec77f188351d24d5067de2e0cc57b48a9a5455d78bda6b7632d14a679070c3d","up":true},{"one":"292bb75a5407dda2ad7b642b1b790264e7ce16bbd8cd22e512eeec3d600ea520","other":"113d7850e8169761d357e0ad5a262560fe6137fc6a992b3b2cedcebbe5d5f6a2","up":true},{"one":"66d586d2f06e0b26d051d3fed949bf8f36ca3ba4c64b8230987ae0c7d29c2dfd","other":"6a5cd611a5a8a94c82d6d3b40912909f326d4111c82983c3f719cfa3f621312f","up":true},{"one":"e45d7b4870acb8a984c065cce7bd17acaf79c5ad4d477f810563315aebacd8e3","other":"a365bf427dc109429657033f4b7b22575b6a8d955d6eaf44288725e5e47b0a86","up":true},{"one":"39e710327f4176b5ed7ebc921db592a6b9197b237029faab4525a00d18470e8a","other":"292bb75a5407dda2ad7b642b1b790264e7ce16bbd8cd22e512eeec3d600ea520","up":true},{"one":"9e9451cb84ec10881e16a87d84b0e4e1aa78f5e9bc49ca5244a3da414b5210da","other":"9e384d8e6d319016c2807884e0b46da4ce9fcb426efb76224ce41b5d4dae5632","up":true},{"one":"bd5b11dfb3bf903c4cb8ce44756e41fefb5582deb79055ccc65fe7b47b2a1a41","other":"b04c7f907691ac51adab6fe657dd4592044ca4222ee60eb86c7851c855057a8e","up":true},{"one":"113d7850e8169761d357e0ad5a262560fe6137fc6a992b3b2cedcebbe5d5f6a2","other":"1f17c9223987ccf4d9683df0664cd568164c69d2f62bad2a79ef4636623ba87a","up":true},{"one":"5205bbd1c298864045f08fe7cc4c2409055db3ee4d790d2587c9ca3366acd977","other":"5c197e623ab40c0de2c83a4ffd16930cf791e60357f162f5b2a2e865ff0cea73","up":true},{"one":"cab41b44229682f11d6aca3d3136597524e9eb4e1640147b87f3f8c7227959f1","other":"c85b39668486c5eb3ea9f312d9897bda5688c4995318e4e64a0be2ac0cb47692","up":true},{"one":"eebe8082b0f6efbade062eb49f2592519524a8c3f22975ba901ed9755a9d0ead","other":"e75238135f0c071ca541fb0a9569e38036c51af350a16bf4a1526f1752318059","up":true},{"one":"b45f23d6ac8824b480f71f25abf5aa2c9db4c3a904660d391768168430355827","other":"b52335dfb9367471204dfba231ba274815b859164e19ee872e20326d0575b88a","up":true},{"one":"0f5c44c74d4cc9b10d283a0099fba5f925c3455f5308d2044c377f8a5181703a","other":"0f988a170fdc7761b8d9814a1a7ac0cc0e98483b91841046b9948783befeed88","up":true},{"one":"b2a236a2acf5cf675c521a3e87ef847933e1736c220a54cf66db4ba03f369483","other":"80d0a541f041fbab580164228d09af6be9e3beec5301e998398231362298835e","up":true},{"one":"f0517f6a1187d0f454aaf98506fdc830e14d31259fbf195f1694078dcaa5582f","other":"e0205da225e77fa15332864eaef1c437be306090b0489faf3f6725cb3c5b4712","up":true},{"one":"e0205da225e77fa15332864eaef1c437be306090b0489faf3f6725cb3c5b4712","other":"e75238135f0c071ca541fb0a9569e38036c51af350a16bf4a1526f1752318059","up":true},{"one":"b52335dfb9367471204dfba231ba274815b859164e19ee872e20326d0575b88a","other":"c85b39668486c5eb3ea9f312d9897bda5688c4995318e4e64a0be2ac0cb47692","up":true},{"one":"71fe0f225ee23106cb6d6c57cdeb49ecc4ffb2b13441956eca6d94505aa2a72c","other":"6a5cd611a5a8a94c82d6d3b40912909f326d4111c82983c3f719cfa3f621312f","up":true},{"one":"2597463214ea0faa3db781fa8cb336da59aff43553f045a0f704ab10f5f54cf2","other":"3bc8d405359a665d3f6d64cf9ba194b7e614c2050add0a1205be7f6c3c984e1d","up":true},{"one":"2218170def9962d400bf932ced91f3c14595062b50ef86eb4d2f7fd384fc8597","other":"2597463214ea0faa3db781fa8cb336da59aff43553f045a0f704ab10f5f54cf2","up":true},{"one":"1f17c9223987ccf4d9683df0664cd568164c69d2f62bad2a79ef4636623ba87a","other":"0f5c44c74d4cc9b10d283a0099fba5f925c3455f5308d2044c377f8a5181703a","up":true},{"one":"a365bf427dc109429657033f4b7b22575b6a8d955d6eaf44288725e5e47b0a86","other":"b2a236a2acf5cf675c521a3e87ef847933e1736c220a54cf66db4ba03f369483","up":true},{"one":"8c5f07bdc96c7bbd8fc7bcf5e3ba841cabfc47f910acbf416730f5e67f847b34","other":"d79a1e6f3dc0e3382eaccdc1fb0f49cd66f483addcc52d767fc12f4b6754e206","up":true},{"one":"c85b39668486c5eb3ea9f312d9897bda5688c4995318e4e64a0be2ac0cb47692","other":"cee0b72bcb0a112c9aa33eb80166d279c2cc0216782a19454a086c0147b8126d","up":true},{"one":"0f988a170fdc7761b8d9814a1a7ac0cc0e98483b91841046b9948783befeed88","other":"113d7850e8169761d357e0ad5a262560fe6137fc6a992b3b2cedcebbe5d5f6a2","up":true},{"one":"5fd664467fac65594c3dd6f89bf2a5398e28aa95d248c3a3d21abaecfebfde5d","other":"1102d287c72becf1bfa8567cf43cc2051aed16a79196f9109cdedf829a2a5a2d","up":true},{"one":"0b45cb8af78fb1af7ab55d022ed00da66393b7eea1b7a3a2f802ffeba4c79dd3","other":"74117e299fffce1ef7f323ea1d6fb1c509cd5581f6c53e0f5e9af8d0b83748f2","up":true},{"one":"9e384d8e6d319016c2807884e0b46da4ce9fcb426efb76224ce41b5d4dae5632","other":"903561af2459b0e46d8188121e66d8b999ad39341944eb099eff70213244c813","up":true},{"one":"5c197e623ab40c0de2c83a4ffd16930cf791e60357f162f5b2a2e865ff0cea73","other":"5e64f329525e17129b3f51580b399a5f09d7652a663bbaac83c8173dd9fded41","up":true},{"one":"85d645e3738c441f508671f52cde0aa7ae2032c082c86b7ca6af92598d7174d5","other":"80d0a541f041fbab580164228d09af6be9e3beec5301e998398231362298835e","up":true},{"one":"461c7f8f8948693dea717679ffc4bbf0eb573f4597543993e3be39d3594fdd47","other":"5c197e623ab40c0de2c83a4ffd16930cf791e60357f162f5b2a2e865ff0cea73","up":true},{"one":"647ccb25b23a917f611a46a72b12fd457610f07143155e1f828b41fd2d3e0b58","other":"6a5cd611a5a8a94c82d6d3b40912909f326d4111c82983c3f719cfa3f621312f","up":true},{"one":"a416fa852bb425e00b805ffabb525ae45024403685a0e8a67815617fd1d078da","other":"aa655b48b225d692184a21a2f7162950851a914f89671cc1af3e87a21b37c5e3","up":true},{"one":"7f4198ddcfb55e9f690024cc19ce4f32d5bbc8d56cfb43f806513a74e52dc277","other":"3bc8d405359a665d3f6d64cf9ba194b7e614c2050add0a1205be7f6c3c984e1d","up":true},{"one":"e75238135f0c071ca541fb0a9569e38036c51af350a16bf4a1526f1752318059","other":"b2a236a2acf5cf675c521a3e87ef847933e1736c220a54cf66db4ba03f369483","up":true},{"one":"34ad9431b71cdeae0c1941b3ae680387b3cedd815f39d65034892920aa869f38","other":"5fd664467fac65594c3dd6f89bf2a5398e28aa95d248c3a3d21abaecfebfde5d","up":true},{"one":"e0463c87b0b7deefbd521721430e0fd24683aa8b41c65f259d678d9844bd800f","other":"292bb75a5407dda2ad7b642b1b790264e7ce16bbd8cd22e512eeec3d600ea520","up":true},{"one":"c0d6b4950771888e9770051d341f2071fecdb09e28c289e251668327eb8ccf66","other":"cec77f188351d24d5067de2e0cc57b48a9a5455d78bda6b7632d14a679070c3d","up":true},{"one":"cec77f188351d24d5067de2e0cc57b48a9a5455d78bda6b7632d14a679070c3d","other":"f3a57f887ddcf1793207948374ce67da9f6a1227f253e3a90acc5b36a48ee645","up":true},{"one":"3bc8d405359a665d3f6d64cf9ba194b7e614c2050add0a1205be7f6c3c984e1d","other":"113d7850e8169761d357e0ad5a262560fe6137fc6a992b3b2cedcebbe5d5f6a2","up":true},{"one":"9045e912dc0acaf9d86ec75d98e93bc90832dbca1119641f8569a98040be940c","other":"9e9451cb84ec10881e16a87d84b0e4e1aa78f5e9bc49ca5244a3da414b5210da","up":true},{"one":"cfc872fe7f213f4ada5ec413037450ae2b141157c83998b9520ac2ddb51707bd","other":"2597463214ea0faa3db781fa8cb336da59aff43553f045a0f704ab10f5f54cf2","up":true},{"one":"e45d7b4870acb8a984c065cce7bd17acaf79c5ad4d477f810563315aebacd8e3","other":"c85b39668486c5eb3ea9f312d9897bda5688c4995318e4e64a0be2ac0cb47692","up":true},{"one":"cf69e5f8effee000dc16109a7e0d04e4e13aeb2f6c089999e555946a46e548f6","other":"5fd664467fac65594c3dd6f89bf2a5398e28aa95d248c3a3d21abaecfebfde5d","up":true},{"one":"e0205da225e77fa15332864eaef1c437be306090b0489faf3f6725cb3c5b4712","other":"85d645e3738c441f508671f52cde0aa7ae2032c082c86b7ca6af92598d7174d5","up":true},{"one":"5a4fa4f4482dbaf7bbe6c3d2ae28c35187fbd0f00d1293be41719aa8e5e4a388","other":"5c197e623ab40c0de2c83a4ffd16930cf791e60357f162f5b2a2e865ff0cea73","up":true},{"one":"cc708e47196a6435b13b18f213f50bf075f3f97100d52d18d70052efc301c91a","other":"cec77f188351d24d5067de2e0cc57b48a9a5455d78bda6b7632d14a679070c3d","up":true},{"one":"2218170def9962d400bf932ced91f3c14595062b50ef86eb4d2f7fd384fc8597","other":"292bb75a5407dda2ad7b642b1b790264e7ce16bbd8cd22e512eeec3d600ea520","up":true},{"one":"cee0b72bcb0a112c9aa33eb80166d279c2cc0216782a19454a086c0147b8126d","other":"85d645e3738c441f508671f52cde0aa7ae2032c082c86b7ca6af92598d7174d5","up":true},{"one":"6d29dbed992f0f0008a72978045df08a4ae1753571fdefbb676f52b5ff843433","other":"6b336cfff0d431edce4cee0cf35ffb1e50b7a5c6e15d3d816fb1bb1cf6488f22","up":true},{"one":"f3a57f887ddcf1793207948374ce67da9f6a1227f253e3a90acc5b36a48ee645","other":"e75238135f0c071ca541fb0a9569e38036c51af350a16bf4a1526f1752318059","up":true},{"one":"c85b39668486c5eb3ea9f312d9897bda5688c4995318e4e64a0be2ac0cb47692","other":"cfc872fe7f213f4ada5ec413037450ae2b141157c83998b9520ac2ddb51707bd","up":true},{"one":"292bb75a5407dda2ad7b642b1b790264e7ce16bbd8cd22e512eeec3d600ea520","other":"34ad9431b71cdeae0c1941b3ae680387b3cedd815f39d65034892920aa869f38","up":true},{"one":"bece4d99fafdcebd22f03a4640026c55f7155ff814b295a2aec7a98d3973595a","other":"b45f23d6ac8824b480f71f25abf5aa2c9db4c3a904660d391768168430355827","up":true},{"one":"5e64f329525e17129b3f51580b399a5f09d7652a663bbaac83c8173dd9fded41","other":"113d7850e8169761d357e0ad5a262560fe6137fc6a992b3b2cedcebbe5d5f6a2","up":true},{"one":"5fd664467fac65594c3dd6f89bf2a5398e28aa95d248c3a3d21abaecfebfde5d","other":"5205bbd1c298864045f08fe7cc4c2409055db3ee4d790d2587c9ca3366acd977","up":true},{"one":"9e9451cb84ec10881e16a87d84b0e4e1aa78f5e9bc49ca5244a3da414b5210da","other":"a416fa852bb425e00b805ffabb525ae45024403685a0e8a67815617fd1d078da","up":true},{"one":"b04c7f907691ac51adab6fe657dd4592044ca4222ee60eb86c7851c855057a8e","other":"b45f23d6ac8824b480f71f25abf5aa2c9db4c3a904660d391768168430355827","up":true},{"one":"461c7f8f8948693dea717679ffc4bbf0eb573f4597543993e3be39d3594fdd47","other":"5a4fa4f4482dbaf7bbe6c3d2ae28c35187fbd0f00d1293be41719aa8e5e4a388","up":true},{"one":"cab41b44229682f11d6aca3d3136597524e9eb4e1640147b87f3f8c7227959f1","other":"cf69e5f8effee000dc16109a7e0d04e4e13aeb2f6c089999e555946a46e548f6","up":true},{"one":"6a5cd611a5a8a94c82d6d3b40912909f326d4111c82983c3f719cfa3f621312f","other":"3bc8d405359a665d3f6d64cf9ba194b7e614c2050add0a1205be7f6c3c984e1d","up":true},{"one":"df45befed47eec3248ce3b55e9cc56f8d718fc13dcb1a03755cdc0cda89e4dcb","other":"cfc872fe7f213f4ada5ec413037450ae2b141157c83998b9520ac2ddb51707bd","up":true},{"one":"66d586d2f06e0b26d051d3fed949bf8f36ca3ba4c64b8230987ae0c7d29c2dfd","other":"6b336cfff0d431edce4cee0cf35ffb1e50b7a5c6e15d3d816fb1bb1cf6488f22","up":true},{"one":"b2a236a2acf5cf675c521a3e87ef847933e1736c220a54cf66db4ba03f369483","other":"b45f23d6ac8824b480f71f25abf5aa2c9db4c3a904660d391768168430355827","up":true},{"one":"aa655b48b225d692184a21a2f7162950851a914f89671cc1af3e87a21b37c5e3","other":"80d0a541f041fbab580164228d09af6be9e3beec5301e998398231362298835e","up":true},{"one":"71fe0f225ee23106cb6d6c57cdeb49ecc4ffb2b13441956eca6d94505aa2a72c","other":"113d7850e8169761d357e0ad5a262560fe6137fc6a992b3b2cedcebbe5d5f6a2","up":true},{"one":"d79a1e6f3dc0e3382eaccdc1fb0f49cd66f483addcc52d767fc12f4b6754e206","other":"cf69e5f8effee000dc16109a7e0d04e4e13aeb2f6c089999e555946a46e548f6","up":true},{"one":"6b336cfff0d431edce4cee0cf35ffb1e50b7a5c6e15d3d816fb1bb1cf6488f22","other":"647ccb25b23a917f611a46a72b12fd457610f07143155e1f828b41fd2d3e0b58","up":true},{"one":"a365bf427dc109429657033f4b7b22575b6a8d955d6eaf44288725e5e47b0a86","other":"80d0a541f041fbab580164228d09af6be9e3beec5301e998398231362298835e","up":true},{"one":"39e710327f4176b5ed7ebc921db592a6b9197b237029faab4525a00d18470e8a","other":"113d7850e8169761d357e0ad5a262560fe6137fc6a992b3b2cedcebbe5d5f6a2","up":true},{"one":"903561af2459b0e46d8188121e66d8b999ad39341944eb099eff70213244c813","other":"a416fa852bb425e00b805ffabb525ae45024403685a0e8a67815617fd1d078da","up":true},{"one":"5205bbd1c298864045f08fe7cc4c2409055db3ee4d790d2587c9ca3366acd977","other":"5e64f329525e17129b3f51580b399a5f09d7652a663bbaac83c8173dd9fded41","up":true},{"one":"b52335dfb9367471204dfba231ba274815b859164e19ee872e20326d0575b88a","other":"aa655b48b225d692184a21a2f7162950851a914f89671cc1af3e87a21b37c5e3","up":true},{"one":"1f17c9223987ccf4d9683df0664cd568164c69d2f62bad2a79ef4636623ba87a","other":"6d29dbed992f0f0008a72978045df08a4ae1753571fdefbb676f52b5ff843433","up":true},{"one":"0f5c44c74d4cc9b10d283a0099fba5f925c3455f5308d2044c377f8a5181703a","other":"34ad9431b71cdeae0c1941b3ae680387b3cedd815f39d65034892920aa869f38","up":true},{"one":"0f988a170fdc7761b8d9814a1a7ac0cc0e98483b91841046b9948783befeed88","other":"c85b39668486c5eb3ea9f312d9897bda5688c4995318e4e64a0be2ac0cb47692","up":true},{"one":"4ff286f9e1f7d2d0a8e14a5547e03580d67dbedd1e2ca0c88ad01a2e56c0f45c","other":"5c197e623ab40c0de2c83a4ffd16930cf791e60357f162f5b2a2e865ff0cea73","up":true},{"one":"7fe48790180ab38e7687009847285f7af5e73b21b60cfad9d844ba8b2e7576e4","other":"3bc8d405359a665d3f6d64cf9ba194b7e614c2050add0a1205be7f6c3c984e1d","up":true},{"one":"0b45cb8af78fb1af7ab55d022ed00da66393b7eea1b7a3a2f802ffeba4c79dd3","other":"39e710327f4176b5ed7ebc921db592a6b9197b237029faab4525a00d18470e8a","up":true},{"one":"bd5b11dfb3bf903c4cb8ce44756e41fefb5582deb79055ccc65fe7b47b2a1a41","other":"b45f23d6ac8824b480f71f25abf5aa2c9db4c3a904660d391768168430355827","up":true},{"one":"88b4c3d6ae6a1d4c39caef84d56f308b58e2ca0477cc5197f3f5be41c890bd7c","other":"85d645e3738c441f508671f52cde0aa7ae2032c082c86b7ca6af92598d7174d5","up":true},{"one":"647ccb25b23a917f611a46a72b12fd457610f07143155e1f828b41fd2d3e0b58","other":"6d29dbed992f0f0008a72978045df08a4ae1753571fdefbb676f52b5ff843433","up":true},{"one":"eebe8082b0f6efbade062eb49f2592519524a8c3f22975ba901ed9755a9d0ead","other":"e0205da225e77fa15332864eaef1c437be306090b0489faf3f6725cb3c5b4712","up":true},{"one":"9e384d8e6d319016c2807884e0b46da4ce9fcb426efb76224ce41b5d4dae5632","other":"df45befed47eec3248ce3b55e9cc56f8d718fc13dcb1a03755cdc0cda89e4dcb","up":true},{"one":"ab1c8bbfea9434576e15465cfbd66e8c6565345d83e248d6fcd3748e82806c95","other":"b2a236a2acf5cf675c521a3e87ef847933e1736c220a54cf66db4ba03f369483","up":true},{"one":"f0517f6a1187d0f454aaf98506fdc830e14d31259fbf195f1694078dcaa5582f","other":"e75238135f0c071ca541fb0a9569e38036c51af350a16bf4a1526f1752318059","up":true},{"one":"80d0a541f041fbab580164228d09af6be9e3beec5301e998398231362298835e","other":"d79a1e6f3dc0e3382eaccdc1fb0f49cd66f483addcc52d767fc12f4b6754e206","up":true},{"one":"5c197e623ab40c0de2c83a4ffd16930cf791e60357f162f5b2a2e865ff0cea73","other":"34ad9431b71cdeae0c1941b3ae680387b3cedd815f39d65034892920aa869f38","up":true},{"one":"b45f23d6ac8824b480f71f25abf5aa2c9db4c3a904660d391768168430355827","other":"aa655b48b225d692184a21a2f7162950851a914f89671cc1af3e87a21b37c5e3","up":true},{"one":"2597463214ea0faa3db781fa8cb336da59aff43553f045a0f704ab10f5f54cf2","other":"4ff286f9e1f7d2d0a8e14a5547e03580d67dbedd1e2ca0c88ad01a2e56c0f45c","up":true},{"one":"85d645e3738c441f508671f52cde0aa7ae2032c082c86b7ca6af92598d7174d5","other":"c0d6b4950771888e9770051d341f2071fecdb09e28c289e251668327eb8ccf66","up":true},{"one":"8c5f07bdc96c7bbd8fc7bcf5e3ba841cabfc47f910acbf416730f5e67f847b34","other":"a365bf427dc109429657033f4b7b22575b6a8d955d6eaf44288725e5e47b0a86","up":true},{"one":"e75238135f0c071ca541fb0a9569e38036c51af350a16bf4a1526f1752318059","other":"d79a1e6f3dc0e3382eaccdc1fb0f49cd66f483addcc52d767fc12f4b6754e206","up":true},{"one":"9045e912dc0acaf9d86ec75d98e93bc90832dbca1119641f8569a98040be940c","other":"9e384d8e6d319016c2807884e0b46da4ce9fcb426efb76224ce41b5d4dae5632","up":true},{"one":"a416fa852bb425e00b805ffabb525ae45024403685a0e8a67815617fd1d078da","other":"df45befed47eec3248ce3b55e9cc56f8d718fc13dcb1a03755cdc0cda89e4dcb","up":true},{"one":"cf69e5f8effee000dc16109a7e0d04e4e13aeb2f6c089999e555946a46e548f6","other":"f3a57f887ddcf1793207948374ce67da9f6a1227f253e3a90acc5b36a48ee645","up":true},{"one":"c85b39668486c5eb3ea9f312d9897bda5688c4995318e4e64a0be2ac0cb47692","other":"cf69e5f8effee000dc16109a7e0d04e4e13aeb2f6c089999e555946a46e548f6","up":true},{"one":"cc708e47196a6435b13b18f213f50bf075f3f97100d52d18d70052efc301c91a","other":"0f988a170fdc7761b8d9814a1a7ac0cc0e98483b91841046b9948783befeed88","up":true},{"one":"461c7f8f8948693dea717679ffc4bbf0eb573f4597543993e3be39d3594fdd47","other":"5205bbd1c298864045f08fe7cc4c2409055db3ee4d790d2587c9ca3366acd977","up":true},{"one":"e0205da225e77fa15332864eaef1c437be306090b0489faf3f6725cb3c5b4712","other":"c0d6b4950771888e9770051d341f2071fecdb09e28c289e251668327eb8ccf66","up":true},{"one":"f3a57f887ddcf1793207948374ce67da9f6a1227f253e3a90acc5b36a48ee645","other":"e0463c87b0b7deefbd521721430e0fd24683aa8b41c65f259d678d9844bd800f","up":true},{"one":"df45befed47eec3248ce3b55e9cc56f8d718fc13dcb1a03755cdc0cda89e4dcb","other":"cf69e5f8effee000dc16109a7e0d04e4e13aeb2f6c089999e555946a46e548f6","up":true},{"one":"2218170def9962d400bf932ced91f3c14595062b50ef86eb4d2f7fd384fc8597","other":"6d29dbed992f0f0008a72978045df08a4ae1753571fdefbb676f52b5ff843433","up":true},{"one":"9e9451cb84ec10881e16a87d84b0e4e1aa78f5e9bc49ca5244a3da414b5210da","other":"80d0a541f041fbab580164228d09af6be9e3beec5301e998398231362298835e","up":true},{"one":"6b336cfff0d431edce4cee0cf35ffb1e50b7a5c6e15d3d816fb1bb1cf6488f22","other":"113d7850e8169761d357e0ad5a262560fe6137fc6a992b3b2cedcebbe5d5f6a2","up":true},{"one":"903561af2459b0e46d8188121e66d8b999ad39341944eb099eff70213244c813","other":"80d0a541f041fbab580164228d09af6be9e3beec5301e998398231362298835e","up":true},{"one":"c0d6b4950771888e9770051d341f2071fecdb09e28c289e251668327eb8ccf66","other":"cc708e47196a6435b13b18f213f50bf075f3f97100d52d18d70052efc301c91a","up":true},{"one":"cfc872fe7f213f4ada5ec413037450ae2b141157c83998b9520ac2ddb51707bd","other":"a416fa852bb425e00b805ffabb525ae45024403685a0e8a67815617fd1d078da","up":true},{"one":"6d29dbed992f0f0008a72978045df08a4ae1753571fdefbb676f52b5ff843433","other":"461c7f8f8948693dea717679ffc4bbf0eb573f4597543993e3be39d3594fdd47","up":true},{"one":"85d645e3738c441f508671f52cde0aa7ae2032c082c86b7ca6af92598d7174d5","other":"903561af2459b0e46d8188121e66d8b999ad39341944eb099eff70213244c813","up":true},{"one":"cab41b44229682f11d6aca3d3136597524e9eb4e1640147b87f3f8c7227959f1","other":"cfc872fe7f213f4ada5ec413037450ae2b141157c83998b9520ac2ddb51707bd","up":true},{"one":"4ff286f9e1f7d2d0a8e14a5547e03580d67dbedd1e2ca0c88ad01a2e56c0f45c","other":"5e64f329525e17129b3f51580b399a5f09d7652a663bbaac83c8173dd9fded41","up":true},{"one":"71fe0f225ee23106cb6d6c57cdeb49ecc4ffb2b13441956eca6d94505aa2a72c","other":"1102d287c72becf1bfa8567cf43cc2051aed16a79196f9109cdedf829a2a5a2d","up":true},{"one":"5a4fa4f4482dbaf7bbe6c3d2ae28c35187fbd0f00d1293be41719aa8e5e4a388","other":"34ad9431b71cdeae0c1941b3ae680387b3cedd815f39d65034892920aa869f38","up":true},{"one":"647ccb25b23a917f611a46a72b12fd457610f07143155e1f828b41fd2d3e0b58","other":"39e710327f4176b5ed7ebc921db592a6b9197b237029faab4525a00d18470e8a","up":true},{"one":"bece4d99fafdcebd22f03a4640026c55f7155ff814b295a2aec7a98d3973595a","other":"f3a57f887ddcf1793207948374ce67da9f6a1227f253e3a90acc5b36a48ee645","up":true},{"one":"b04c7f907691ac51adab6fe657dd4592044ca4222ee60eb86c7851c855057a8e","other":"f3a57f887ddcf1793207948374ce67da9f6a1227f253e3a90acc5b36a48ee645","up":true},{"one":"7fe48790180ab38e7687009847285f7af5e73b21b60cfad9d844ba8b2e7576e4","other":"461c7f8f8948693dea717679ffc4bbf0eb573f4597543993e3be39d3594fdd47","up":true},{"one":"6a5cd611a5a8a94c82d6d3b40912909f326d4111c82983c3f719cfa3f621312f","other":"4ff286f9e1f7d2d0a8e14a5547e03580d67dbedd1e2ca0c88ad01a2e56c0f45c","up":true},{"one":"cee0b72bcb0a112c9aa33eb80166d279c2cc0216782a19454a086c0147b8126d","other":"f3a57f887ddcf1793207948374ce67da9f6a1227f253e3a90acc5b36a48ee645","up":true},{"one":"d79a1e6f3dc0e3382eaccdc1fb0f49cd66f483addcc52d767fc12f4b6754e206","other":"cfc872fe7f213f4ada5ec413037450ae2b141157c83998b9520ac2ddb51707bd","up":true},{"one":"88b4c3d6ae6a1d4c39caef84d56f308b58e2ca0477cc5197f3f5be41c890bd7c","other":"c0d6b4950771888e9770051d341f2071fecdb09e28c289e251668327eb8ccf66","up":true},{"one":"5205bbd1c298864045f08fe7cc4c2409055db3ee4d790d2587c9ca3366acd977","other":"903561af2459b0e46d8188121e66d8b999ad39341944eb099eff70213244c813","up":true},{"one":"1f17c9223987ccf4d9683df0664cd568164c69d2f62bad2a79ef4636623ba87a","other":"34ad9431b71cdeae0c1941b3ae680387b3cedd815f39d65034892920aa869f38","up":true},{"one":"66d586d2f06e0b26d051d3fed949bf8f36ca3ba4c64b8230987ae0c7d29c2dfd","other":"34ad9431b71cdeae0c1941b3ae680387b3cedd815f39d65034892920aa869f38","up":true},{"one":"0f988a170fdc7761b8d9814a1a7ac0cc0e98483b91841046b9948783befeed88","other":"461c7f8f8948693dea717679ffc4bbf0eb573f4597543993e3be39d3594fdd47","up":true},{"one":"bd5b11dfb3bf903c4cb8ce44756e41fefb5582deb79055ccc65fe7b47b2a1a41","other":"b2a236a2acf5cf675c521a3e87ef847933e1736c220a54cf66db4ba03f369483","up":true},{"one":"9e384d8e6d319016c2807884e0b46da4ce9fcb426efb76224ce41b5d4dae5632","other":"aa655b48b225d692184a21a2f7162950851a914f89671cc1af3e87a21b37c5e3","up":true},{"one":"5c197e623ab40c0de2c83a4ffd16930cf791e60357f162f5b2a2e865ff0cea73","other":"66d586d2f06e0b26d051d3fed949bf8f36ca3ba4c64b8230987ae0c7d29c2dfd","up":true},{"one":"b45f23d6ac8824b480f71f25abf5aa2c9db4c3a904660d391768168430355827","other":"d79a1e6f3dc0e3382eaccdc1fb0f49cd66f483addcc52d767fc12f4b6754e206","up":true},{"one":"eebe8082b0f6efbade062eb49f2592519524a8c3f22975ba901ed9755a9d0ead","other":"34ad9431b71cdeae0c1941b3ae680387b3cedd815f39d65034892920aa869f38","up":true},{"one":"f0517f6a1187d0f454aaf98506fdc830e14d31259fbf195f1694078dcaa5582f","other":"e45d7b4870acb8a984c065cce7bd17acaf79c5ad4d477f810563315aebacd8e3","up":true},{"one":"2597463214ea0faa3db781fa8cb336da59aff43553f045a0f704ab10f5f54cf2","other":"0f988a170fdc7761b8d9814a1a7ac0cc0e98483b91841046b9948783befeed88","up":true},{"one":"cc708e47196a6435b13b18f213f50bf075f3f97100d52d18d70052efc301c91a","other":"b04c7f907691ac51adab6fe657dd4592044ca4222ee60eb86c7851c855057a8e","up":true},{"one":"9045e912dc0acaf9d86ec75d98e93bc90832dbca1119641f8569a98040be940c","other":"5205bbd1c298864045f08fe7cc4c2409055db3ee4d790d2587c9ca3366acd977","up":true},{"one":"c85b39668486c5eb3ea9f312d9897bda5688c4995318e4e64a0be2ac0cb47692","other":"cc708e47196a6435b13b18f213f50bf075f3f97100d52d18d70052efc301c91a","up":true},{"one":"df45befed47eec3248ce3b55e9cc56f8d718fc13dcb1a03755cdc0cda89e4dcb","other":"cc708e47196a6435b13b18f213f50bf075f3f97100d52d18d70052efc301c91a","up":true},{"one":"d79a1e6f3dc0e3382eaccdc1fb0f49cd66f483addcc52d767fc12f4b6754e206","other":"cc708e47196a6435b13b18f213f50bf075f3f97100d52d18d70052efc301c91a","up":true},{"one":"5205bbd1c298864045f08fe7cc4c2409055db3ee4d790d2587c9ca3366acd977","other":"71fe0f225ee23106cb6d6c57cdeb49ecc4ffb2b13441956eca6d94505aa2a72c","up":true},{"one":"88b4c3d6ae6a1d4c39caef84d56f308b58e2ca0477cc5197f3f5be41c890bd7c","other":"903561af2459b0e46d8188121e66d8b999ad39341944eb099eff70213244c813","up":true},{"one":"f3a57f887ddcf1793207948374ce67da9f6a1227f253e3a90acc5b36a48ee645","other":"e0205da225e77fa15332864eaef1c437be306090b0489faf3f6725cb3c5b4712","up":true},{"one":"6b336cfff0d431edce4cee0cf35ffb1e50b7a5c6e15d3d816fb1bb1cf6488f22","other":"461c7f8f8948693dea717679ffc4bbf0eb573f4597543993e3be39d3594fdd47","up":true},{"one":"9e384d8e6d319016c2807884e0b46da4ce9fcb426efb76224ce41b5d4dae5632","other":"88b4c3d6ae6a1d4c39caef84d56f308b58e2ca0477cc5197f3f5be41c890bd7c","up":true},{"one":"b45f23d6ac8824b480f71f25abf5aa2c9db4c3a904660d391768168430355827","other":"df45befed47eec3248ce3b55e9cc56f8d718fc13dcb1a03755cdc0cda89e4dcb","up":true},{"one":"a416fa852bb425e00b805ffabb525ae45024403685a0e8a67815617fd1d078da","other":"b2a236a2acf5cf675c521a3e87ef847933e1736c220a54cf66db4ba03f369483","up":true},{"one":"647ccb25b23a917f611a46a72b12fd457610f07143155e1f828b41fd2d3e0b58","other":"461c7f8f8948693dea717679ffc4bbf0eb573f4597543993e3be39d3594fdd47","up":true},{"one":"c0d6b4950771888e9770051d341f2071fecdb09e28c289e251668327eb8ccf66","other":"cab41b44229682f11d6aca3d3136597524e9eb4e1640147b87f3f8c7227959f1","up":true},{"one":"cfc872fe7f213f4ada5ec413037450ae2b141157c83998b9520ac2ddb51707bd","other":"f3a57f887ddcf1793207948374ce67da9f6a1227f253e3a90acc5b36a48ee645","up":true},{"one":"e0205da225e77fa15332864eaef1c437be306090b0489faf3f6725cb3c5b4712","other":"d79a1e6f3dc0e3382eaccdc1fb0f49cd66f483addcc52d767fc12f4b6754e206","up":true},{"one":"cab41b44229682f11d6aca3d3136597524e9eb4e1640147b87f3f8c7227959f1","other":"cc708e47196a6435b13b18f213f50bf075f3f97100d52d18d70052efc301c91a","up":true},{"one":"71fe0f225ee23106cb6d6c57cdeb49ecc4ffb2b13441956eca6d94505aa2a72c","other":"461c7f8f8948693dea717679ffc4bbf0eb573f4597543993e3be39d3594fdd47","up":true},{"one":"5a4fa4f4482dbaf7bbe6c3d2ae28c35187fbd0f00d1293be41719aa8e5e4a388","other":"6d29dbed992f0f0008a72978045df08a4ae1753571fdefbb676f52b5ff843433","up":true},{"one":"2218170def9962d400bf932ced91f3c14595062b50ef86eb4d2f7fd384fc8597","other":"113d7850e8169761d357e0ad5a262560fe6137fc6a992b3b2cedcebbe5d5f6a2","up":true},{"one":"bece4d99fafdcebd22f03a4640026c55f7155ff814b295a2aec7a98d3973595a","other":"80d0a541f041fbab580164228d09af6be9e3beec5301e998398231362298835e","up":true},{"one":"b04c7f907691ac51adab6fe657dd4592044ca4222ee60eb86c7851c855057a8e","other":"85d645e3738c441f508671f52cde0aa7ae2032c082c86b7ca6af92598d7174d5","up":true},{"one":"4ff286f9e1f7d2d0a8e14a5547e03580d67dbedd1e2ca0c88ad01a2e56c0f45c","other":"5fd664467fac65594c3dd6f89bf2a5398e28aa95d248c3a3d21abaecfebfde5d","up":true},{"one":"66d586d2f06e0b26d051d3fed949bf8f36ca3ba4c64b8230987ae0c7d29c2dfd","other":"71fe0f225ee23106cb6d6c57cdeb49ecc4ffb2b13441956eca6d94505aa2a72c","up":true},{"one":"bd5b11dfb3bf903c4cb8ce44756e41fefb5582deb79055ccc65fe7b47b2a1a41","other":"85d645e3738c441f508671f52cde0aa7ae2032c082c86b7ca6af92598d7174d5","up":true},{"one":"eebe8082b0f6efbade062eb49f2592519524a8c3f22975ba901ed9755a9d0ead","other":"df45befed47eec3248ce3b55e9cc56f8d718fc13dcb1a03755cdc0cda89e4dcb","up":true},{"one":"f0517f6a1187d0f454aaf98506fdc830e14d31259fbf195f1694078dcaa5582f","other":"d79a1e6f3dc0e3382eaccdc1fb0f49cd66f483addcc52d767fc12f4b6754e206","up":true},{"one":"9045e912dc0acaf9d86ec75d98e93bc90832dbca1119641f8569a98040be940c","other":"80d0a541f041fbab580164228d09af6be9e3beec5301e998398231362298835e","up":true},{"one":"cc708e47196a6435b13b18f213f50bf075f3f97100d52d18d70052efc301c91a","other":"b2a236a2acf5cf675c521a3e87ef847933e1736c220a54cf66db4ba03f369483","up":true},{"one":"bece4d99fafdcebd22f03a4640026c55f7155ff814b295a2aec7a98d3973595a","other":"a416fa852bb425e00b805ffabb525ae45024403685a0e8a67815617fd1d078da","up":true},{"one":"b04c7f907691ac51adab6fe657dd4592044ca4222ee60eb86c7851c855057a8e","other":"a416fa852bb425e00b805ffabb525ae45024403685a0e8a67815617fd1d078da","up":true},{"one":"bd5b11dfb3bf903c4cb8ce44756e41fefb5582deb79055ccc65fe7b47b2a1a41","other":"a365bf427dc109429657033f4b7b22575b6a8d955d6eaf44288725e5e47b0a86","up":true},{"one":"f0517f6a1187d0f454aaf98506fdc830e14d31259fbf195f1694078dcaa5582f","other":"df45befed47eec3248ce3b55e9cc56f8d718fc13dcb1a03755cdc0cda89e4dcb","up":true},{"one":"c85b39668486c5eb3ea9f312d9897bda5688c4995318e4e64a0be2ac0cb47692","other":"d79a1e6f3dc0e3382eaccdc1fb0f49cd66f483addcc52d767fc12f4b6754e206","up":true},{"one":"f3a57f887ddcf1793207948374ce67da9f6a1227f253e3a90acc5b36a48ee645","other":"eebe8082b0f6efbade062eb49f2592519524a8c3f22975ba901ed9755a9d0ead","up":true},{"one":"2218170def9962d400bf932ced91f3c14595062b50ef86eb4d2f7fd384fc8597","other":"1102d287c72becf1bfa8567cf43cc2051aed16a79196f9109cdedf829a2a5a2d","up":true},{"one":"df45befed47eec3248ce3b55e9cc56f8d718fc13dcb1a03755cdc0cda89e4dcb","other":"cab41b44229682f11d6aca3d3136597524e9eb4e1640147b87f3f8c7227959f1","up":true},{"one":"c0d6b4950771888e9770051d341f2071fecdb09e28c289e251668327eb8ccf66","other":"c85b39668486c5eb3ea9f312d9897bda5688c4995318e4e64a0be2ac0cb47692","up":true},{"one":"cab41b44229682f11d6aca3d3136597524e9eb4e1640147b87f3f8c7227959f1","other":"113d7850e8169761d357e0ad5a262560fe6137fc6a992b3b2cedcebbe5d5f6a2","up":true},{"one":"4ff286f9e1f7d2d0a8e14a5547e03580d67dbedd1e2ca0c88ad01a2e56c0f45c","other":"5205bbd1c298864045f08fe7cc4c2409055db3ee4d790d2587c9ca3366acd977","up":true},{"one":"d79a1e6f3dc0e3382eaccdc1fb0f49cd66f483addcc52d767fc12f4b6754e206","other":"cab41b44229682f11d6aca3d3136597524e9eb4e1640147b87f3f8c7227959f1","up":true},{"one":"cab41b44229682f11d6aca3d3136597524e9eb4e1640147b87f3f8c7227959f1","other":"1102d287c72becf1bfa8567cf43cc2051aed16a79196f9109cdedf829a2a5a2d","up":true},{"one":"df45befed47eec3248ce3b55e9cc56f8d718fc13dcb1a03755cdc0cda89e4dcb","other":"c85b39668486c5eb3ea9f312d9897bda5688c4995318e4e64a0be2ac0cb47692","up":true},{"one":"d79a1e6f3dc0e3382eaccdc1fb0f49cd66f483addcc52d767fc12f4b6754e206","other":"c0d6b4950771888e9770051d341f2071fecdb09e28c289e251668327eb8ccf66","up":true},{"one":"c0d6b4950771888e9770051d341f2071fecdb09e28c289e251668327eb8ccf66","other":"2218170def9962d400bf932ced91f3c14595062b50ef86eb4d2f7fd384fc8597","up":true},{"one":"cab41b44229682f11d6aca3d3136597524e9eb4e1640147b87f3f8c7227959f1","other":"f3a57f887ddcf1793207948374ce67da9f6a1227f253e3a90acc5b36a48ee645","up":true},{"one":"df45befed47eec3248ce3b55e9cc56f8d718fc13dcb1a03755cdc0cda89e4dcb","other":"c0d6b4950771888e9770051d341f2071fecdb09e28c289e251668327eb8ccf66","up":true}]} \ No newline at end of file diff --git a/swarm/pss/testdata/snapshot_8.json b/swarm/pss/testdata/snapshot_8.json deleted file mode 100644 index 307afe5a137b..000000000000 --- a/swarm/pss/testdata/snapshot_8.json +++ /dev/null @@ -1 +0,0 @@ -{"nodes":[{"node":{"config":{"id":"73d6ad4a75069dced660fa4cb98143ee5573df7cb15d9a295acf1655e9683384","private_key":"e567b7d9c554e5102cdc99b6523bace02dbb8951415c8816d82ba2d2e97fa23b","name":"node01","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"6e8da86abb894ab35044c8c455147225df96cab498da067a118f1fb9a417f9e3","private_key":"c7526db70acd02f36d3b201ef3e1d85e38c52bee6931453213dbc5edec4d0976","name":"node02","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"8a1eb78ff13df318e7f8116dffee98cd7d9905650fa53f16766b754a63f387ac","private_key":"61b5728f59bc43080c3b8eb0458fb30d7723e2747355b6dc980f35f3ed431199","name":"node03","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"d7768334f79d626adb433f44b703a818555e3331056036ef3f8d1282586bf044","private_key":"075b07c29ceac4ffa2a114afd67b21dfc438126bc169bf7c154be6d81d86ed38","name":"node04","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"05dacbe069e452448fb7bee09b8270a0218089a6d441c461fc45d338d2b59492","private_key":"4882fdd34676c2158f7bfc761bf824fcf693736a8df294cc7e79ef1848c7bae6","name":"node05","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"3451df808a9e122ebbc6306f159ae90ccd34e5ef3e0457c501f54ac08457238a","private_key":"0470652ac57af40a43bc67b1b49699219fc35a805da167244f505d27858334c7","name":"node06","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"159c0bdb3c1638e66de52ec0c476282eb5a7b1fcf763dc33b938c5381ef5a149","private_key":"2cbf6256e92736e1b54279b79addbb830a607a71488cdd3462a44fcaa68c018e","name":"node07","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}},{"node":{"config":{"id":"dfd47d54492eac09708641a7115b1fda328e2dd8f75ced9026212d3699722f94","private_key":"e659774a5ff4f76b021bf4884ad359eadeb8ff33e843a3f76fcf4a38b0d82b35","name":"node08","services":["pss","bzz"],"enable_msg_events":false,"port":0},"up":true}}],"conns":[{"one":"6e8da86abb894ab35044c8c455147225df96cab498da067a118f1fb9a417f9e3","other":"8a1eb78ff13df318e7f8116dffee98cd7d9905650fa53f16766b754a63f387ac","up":true},{"one":"05dacbe069e452448fb7bee09b8270a0218089a6d441c461fc45d338d2b59492","other":"3451df808a9e122ebbc6306f159ae90ccd34e5ef3e0457c501f54ac08457238a","up":true},{"one":"73d6ad4a75069dced660fa4cb98143ee5573df7cb15d9a295acf1655e9683384","other":"6e8da86abb894ab35044c8c455147225df96cab498da067a118f1fb9a417f9e3","up":true},{"one":"159c0bdb3c1638e66de52ec0c476282eb5a7b1fcf763dc33b938c5381ef5a149","other":"dfd47d54492eac09708641a7115b1fda328e2dd8f75ced9026212d3699722f94","up":true},{"one":"dfd47d54492eac09708641a7115b1fda328e2dd8f75ced9026212d3699722f94","other":"73d6ad4a75069dced660fa4cb98143ee5573df7cb15d9a295acf1655e9683384","up":true},{"one":"3451df808a9e122ebbc6306f159ae90ccd34e5ef3e0457c501f54ac08457238a","other":"159c0bdb3c1638e66de52ec0c476282eb5a7b1fcf763dc33b938c5381ef5a149","up":true},{"one":"d7768334f79d626adb433f44b703a818555e3331056036ef3f8d1282586bf044","other":"05dacbe069e452448fb7bee09b8270a0218089a6d441c461fc45d338d2b59492","up":true},{"one":"8a1eb78ff13df318e7f8116dffee98cd7d9905650fa53f16766b754a63f387ac","other":"d7768334f79d626adb433f44b703a818555e3331056036ef3f8d1282586bf044","up":true},{"one":"73d6ad4a75069dced660fa4cb98143ee5573df7cb15d9a295acf1655e9683384","other":"3451df808a9e122ebbc6306f159ae90ccd34e5ef3e0457c501f54ac08457238a","up":true},{"one":"d7768334f79d626adb433f44b703a818555e3331056036ef3f8d1282586bf044","other":"159c0bdb3c1638e66de52ec0c476282eb5a7b1fcf763dc33b938c5381ef5a149","up":true},{"one":"05dacbe069e452448fb7bee09b8270a0218089a6d441c461fc45d338d2b59492","other":"159c0bdb3c1638e66de52ec0c476282eb5a7b1fcf763dc33b938c5381ef5a149","up":true},{"one":"159c0bdb3c1638e66de52ec0c476282eb5a7b1fcf763dc33b938c5381ef5a149","other":"73d6ad4a75069dced660fa4cb98143ee5573df7cb15d9a295acf1655e9683384","up":true},{"one":"3451df808a9e122ebbc6306f159ae90ccd34e5ef3e0457c501f54ac08457238a","other":"d7768334f79d626adb433f44b703a818555e3331056036ef3f8d1282586bf044","up":true},{"one":"dfd47d54492eac09708641a7115b1fda328e2dd8f75ced9026212d3699722f94","other":"d7768334f79d626adb433f44b703a818555e3331056036ef3f8d1282586bf044","up":true},{"one":"8a1eb78ff13df318e7f8116dffee98cd7d9905650fa53f16766b754a63f387ac","other":"dfd47d54492eac09708641a7115b1fda328e2dd8f75ced9026212d3699722f94","up":true},{"one":"6e8da86abb894ab35044c8c455147225df96cab498da067a118f1fb9a417f9e3","other":"05dacbe069e452448fb7bee09b8270a0218089a6d441c461fc45d338d2b59492","up":true},{"one":"73d6ad4a75069dced660fa4cb98143ee5573df7cb15d9a295acf1655e9683384","other":"05dacbe069e452448fb7bee09b8270a0218089a6d441c461fc45d338d2b59492","up":true},{"one":"6e8da86abb894ab35044c8c455147225df96cab498da067a118f1fb9a417f9e3","other":"159c0bdb3c1638e66de52ec0c476282eb5a7b1fcf763dc33b938c5381ef5a149","up":true},{"one":"6e8da86abb894ab35044c8c455147225df96cab498da067a118f1fb9a417f9e3","other":"3451df808a9e122ebbc6306f159ae90ccd34e5ef3e0457c501f54ac08457238a","up":true}]} \ No newline at end of file diff --git a/swarm/pss/types.go b/swarm/pss/types.go deleted file mode 100644 index ef138ff3a4b8..000000000000 --- a/swarm/pss/types.go +++ /dev/null @@ -1,217 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package pss - -import ( - "encoding/json" - "fmt" - "sync" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/common/hexutil" - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/rlp" - "github.com/ubiq/go-ubiq/swarm/storage" - whisper "github.com/ubiq/go-ubiq/whisper/whisperv6" -) - -const ( - defaultWhisperTTL = 6000 -) - -const ( - pssControlSym = 1 - pssControlRaw = 1 << 1 -) - -var ( - topicHashMutex = sync.Mutex{} - topicHashFunc = storage.MakeHashFunc("SHA256")() - rawTopic = Topic{} -) - -// Topic is the PSS encapsulation of the Whisper topic type -type Topic whisper.TopicType - -func (t *Topic) String() string { - return hexutil.Encode(t[:]) -} - -// MarshalJSON implements the json.Marshaler interface -func (t Topic) MarshalJSON() (b []byte, err error) { - return json.Marshal(t.String()) -} - -// MarshalJSON implements the json.Marshaler interface -func (t *Topic) UnmarshalJSON(input []byte) error { - topicbytes, err := hexutil.Decode(string(input[1 : len(input)-1])) - if err != nil { - return err - } - copy(t[:], topicbytes) - return nil -} - -// PssAddress is an alias for []byte. It represents a variable length address -type PssAddress []byte - -// MarshalJSON implements the json.Marshaler interface -func (a PssAddress) MarshalJSON() ([]byte, error) { - return json.Marshal(hexutil.Encode(a[:])) -} - -// UnmarshalJSON implements the json.Marshaler interface -func (a *PssAddress) UnmarshalJSON(input []byte) error { - b, err := hexutil.Decode(string(input[1 : len(input)-1])) - if err != nil { - return err - } - for _, bb := range b { - *a = append(*a, bb) - } - return nil -} - -// holds the digest of a message used for caching -type pssDigest [digestLength]byte - -// conceals bitwise operations on the control flags byte -type msgParams struct { - raw bool - sym bool -} - -func newMsgParamsFromBytes(paramBytes []byte) *msgParams { - if len(paramBytes) != 1 { - return nil - } - return &msgParams{ - raw: paramBytes[0]&pssControlRaw > 0, - sym: paramBytes[0]&pssControlSym > 0, - } -} - -func (m *msgParams) Bytes() (paramBytes []byte) { - var b byte - if m.raw { - b |= pssControlRaw - } - if m.sym { - b |= pssControlSym - } - paramBytes = append(paramBytes, b) - return paramBytes -} - -// PssMsg encapsulates messages transported over pss. -type PssMsg struct { - To []byte - Control []byte - Expire uint32 - Payload *whisper.Envelope -} - -func newPssMsg(param *msgParams) *PssMsg { - return &PssMsg{ - Control: param.Bytes(), - } -} - -// message is flagged as raw / external encryption -func (msg *PssMsg) isRaw() bool { - return msg.Control[0]&pssControlRaw > 0 -} - -// message is flagged as symmetrically encrypted -func (msg *PssMsg) isSym() bool { - return msg.Control[0]&pssControlSym > 0 -} - -// serializes the message for use in cache -func (msg *PssMsg) serialize() []byte { - rlpdata, _ := rlp.EncodeToBytes(struct { - To []byte - Payload *whisper.Envelope - }{ - To: msg.To, - Payload: msg.Payload, - }) - return rlpdata -} - -// String representation of PssMsg -func (msg *PssMsg) String() string { - return fmt.Sprintf("PssMsg: Recipient: %x", common.ToHex(msg.To)) -} - -// Signature for a message handler function for a PssMsg -// Implementations of this type are passed to Pss.Register together with a topic, -type HandlerFunc func(msg []byte, p *p2p.Peer, asymmetric bool, keyid string) error - -type handlerCaps struct { - raw bool - prox bool -} - -// Handler defines code to be executed upon reception of content. -type handler struct { - f HandlerFunc - caps *handlerCaps -} - -// NewHandler returns a new message handler -func NewHandler(f HandlerFunc) *handler { - return &handler{ - f: f, - caps: &handlerCaps{}, - } -} - -// WithRaw is a chainable method that allows raw messages to be handled. -func (h *handler) WithRaw() *handler { - h.caps.raw = true - return h -} - -// WithProxBin is a chainable method that allows sending messages with full addresses to neighbourhoods using the kademlia depth as reference -func (h *handler) WithProxBin() *handler { - h.caps.prox = true - return h -} - -// the stateStore handles saving and loading PSS peers and their corresponding keys -// it is currently unimplemented -type stateStore struct { - values map[string][]byte -} - -func (store *stateStore) Load(key string) ([]byte, error) { - return nil, nil -} - -func (store *stateStore) Save(key string, v []byte) error { - return nil -} - -// BytesToTopic hashes an arbitrary length byte slice and truncates it to the length of a topic, using only the first bytes of the digest -func BytesToTopic(b []byte) Topic { - topicHashMutex.Lock() - defer topicHashMutex.Unlock() - topicHashFunc.Reset() - topicHashFunc.Write(b) - return Topic(whisper.BytesToTopic(topicHashFunc.Sum(nil))) -} diff --git a/swarm/pss/writeup.md b/swarm/pss/writeup.md deleted file mode 100644 index 2ab917e7eccf..000000000000 --- a/swarm/pss/writeup.md +++ /dev/null @@ -1,125 +0,0 @@ -## PSS tests failures explanation - -This document aims to explain the changes in https://github.com/ethersphere/go-ethereum/pull/126 and how those changes affect the pss_test.go TestNetwork tests. - -### Problem - -When running the TestNetwork test, execution sometimes: - -* deadlocks -* panics -* failures with wrong result, such as: - -``` -$ go test -v ./swarm/pss -cpu 4 -run TestNetwork -``` - -``` ---- FAIL: TestNetwork (68.13s) - --- FAIL: TestNetwork/3/10/4/sim (68.13s) - pss_test.go:697: 7 of 10 messages received - pss_test.go:700: 3 messages were not received -FAIL -``` - -Moreover execution almost always deadlocks with `sim` adapter, and `sock` adapter (when buffer is low), but is mostly stable with `exec` and `tcp` adapters. - -### Findings and Fixes - -#### 1. Addressing panics - -Panics were caused due to concurrent map read/writes and unsynchronised access to shared memory by multiple goroutines. This is visible when running the test with the `-race` flag. - -``` -go test -race -v ./swarm/pss -cpu 4 -run TestNetwork - - 1 ================== - 2 WARNING: DATA RACE - 3 Read at 0x00c424d456a0 by goroutine 1089: - 4 github.com/ubiq/go-ubiq/swarm/pss.(*Pss).forward.func1() - 5 /Users/nonsense/code/src/github.com/ubiq/go-ubiq/swarm/pss/pss.go:654 +0x44f - 6 github.com/ubiq/go-ubiq/swarm/network.(*Kademlia).eachConn.func1() - 7 /Users/nonsense/code/src/github.com/ubiq/go-ubiq/swarm/network/kademlia.go:350 +0xc9 - 8 github.com/ubiq/go-ubiq/pot.(*Pot).eachNeighbour.func1() - 9 /Users/nonsense/code/src/github.com/ubiq/go-ubiq/pot/pot.go:599 +0x59 - ... - - 28 - 29 Previous write at 0x00c424d456a0 by goroutine 829: - 30 github.com/ubiq/go-ubiq/swarm/pss.(*Pss).Run() - 31 /Users/nonsense/code/src/github.com/ubiq/go-ubiq/swarm/pss/pss.go:192 +0x16a - 32 github.com/ubiq/go-ubiq/swarm/pss.(*Pss).Run-fm() - 33 /Users/nonsense/code/src/github.com/ubiq/go-ubiq/swarm/pss/pss.go:185 +0x63 - 34 github.com/ubiq/go-ubiq/p2p.(*Peer).startProtocols.func1() - 35 /Users/nonsense/code/src/github.com/ubiq/go-ubiq/p2p/peer.go:347 +0x8b - ... -``` - -##### Current solution - -Adding a mutex around all shared data. - -#### 2. Failures with wrong result - -The validation phase of the TestNetwork test is done using an RPC subscription: - -``` - ... - triggerChecks := func(trigger chan enode.ID, id enode.ID, rpcclient *rpc.Client) error { - msgC := make(chan APIMsg) - ctx, cancel := context.WithTimeout(context.Background(), time.Second) - defer cancel() - sub, err := rpcclient.Subscribe(ctx, "pss", msgC, "receive", hextopic) - ... -``` - -By design the RPC uses a subscription buffer with a max length. When this length is reached, the subscription is dropped. The current config value is not suitable for stress tests. - -##### Current solution - -Increase the max length of the RPC subscription buffer. - -``` -const ( - // Subscriptions are removed when the subscriber cannot keep up. - // - // This can be worked around by supplying a channel with sufficiently sized buffer, - // but this can be inconvenient and hard to explain in the docs. Another issue with - // buffered channels is that the buffer is static even though it might not be needed - // most of the time. - // - // The approach taken here is to maintain a per-subscription linked list buffer - // shrinks on demand. If the buffer reaches the size below, the subscription is - // dropped. - maxClientSubscriptionBuffer = 20000 -) -``` - -#### 3. Deadlocks - -Deadlocks are triggered when using: -* `sim` adapter - synchronous, unbuffered channel -* `sock` adapter - asynchronous, buffered channel (when using a 1K buffer) - -No deadlocks were triggered when using: -* `tcp` adapter - asynchronous, buffered channel -* `exec` adapter - asynchronous, buffered channel - -Ultimately the deadlocks happen due to blocking `pp.Send()` call at: - - // attempt to send the message - err := pp.Send(msg) - if err != nil { - log.Debug(fmt.Sprintf("%v: failed forwarding: %v", sendMsg, err)) - return true - } - - `p2p` request handling is synchronous (as discussed at https://github.com/ethersphere/go-ethereum/issues/130), `pss` is also synchronous, therefore if two nodes happen to be processing a request, while at the same time waiting for response on `pp.Send(msg)`, deadlock occurs. - - `pp.Send(msg)` is only blocking when the underlying adapter is blocking (read `sim` or `sock`) or the buffer of the connection is full. - -##### Current solution - -Make no assumption on the undelying connection, and call `pp.Send` asynchronously in a go-routine. - -Alternatively, get rid of the `sim` and `sock` adapters, and use `tcp` adapter for testing. diff --git a/swarm/sctx/sctx.go b/swarm/sctx/sctx.go deleted file mode 100644 index fb7d35b0005b..000000000000 --- a/swarm/sctx/sctx.go +++ /dev/null @@ -1,20 +0,0 @@ -package sctx - -import "context" - -type ( - HTTPRequestIDKey struct{} - requestHostKey struct{} -) - -func SetHost(ctx context.Context, domain string) context.Context { - return context.WithValue(ctx, requestHostKey{}, domain) -} - -func GetHost(ctx context.Context) string { - v, ok := ctx.Value(requestHostKey{}).(string) - if ok { - return v - } - return "" -} diff --git a/swarm/services/swap/swap.go b/swarm/services/swap/swap.go deleted file mode 100644 index 6031e36a142a..000000000000 --- a/swarm/services/swap/swap.go +++ /dev/null @@ -1,301 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package swap - -import ( - "context" - "crypto/ecdsa" - "errors" - "fmt" - "math/big" - "os" - "path/filepath" - "sync" - "time" - - "github.com/ubiq/go-ubiq/accounts/abi/bind" - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/contracts/chequebook" - "github.com/ubiq/go-ubiq/contracts/chequebook/contract" - "github.com/ubiq/go-ubiq/core/types" - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/services/swap/swap" -) - -// SwAP Swarm Accounting Protocol with -// SWAP^2 Strategies of Withholding Automatic Payments -// SWAP^3 Accreditation: payment via credit SWAP -// using chequebook pkg for delayed payments -// default parameters - -var ( - autoCashInterval = 300 * time.Second // default interval for autocash - autoCashThreshold = big.NewInt(50000000000000) // threshold that triggers autocash (wei) - autoDepositInterval = 300 * time.Second // default interval for autocash - autoDepositThreshold = big.NewInt(50000000000000) // threshold that triggers autodeposit (wei) - autoDepositBuffer = big.NewInt(100000000000000) // buffer that is surplus for fork protection etc (wei) - buyAt = big.NewInt(20000000000) // maximum chunk price host is willing to pay (wei) - sellAt = big.NewInt(20000000000) // minimum chunk price host requires (wei) - payAt = 100 // threshold that triggers payment {request} (units) - dropAt = 10000 // threshold that triggers disconnect (units) -) - -const ( - chequebookDeployRetries = 5 - chequebookDeployDelay = 1 * time.Second // delay between retries -) - -// LocalProfile combines a PayProfile with *swap.Params -type LocalProfile struct { - *swap.Params - *PayProfile -} - -// RemoteProfile combines a PayProfile with *swap.Profile -type RemoteProfile struct { - *swap.Profile - *PayProfile -} - -// PayProfile is a container for relevant chequebook and beneficiary options -type PayProfile struct { - PublicKey string // check against signature of promise - Contract common.Address // address of chequebook contract - Beneficiary common.Address // recipient address for swarm sales revenue - privateKey *ecdsa.PrivateKey - publicKey *ecdsa.PublicKey - owner common.Address - chbook *chequebook.Chequebook - lock sync.RWMutex -} - -// NewDefaultSwapParams create params with default values -func NewDefaultSwapParams() *LocalProfile { - return &LocalProfile{ - PayProfile: &PayProfile{}, - Params: &swap.Params{ - Profile: &swap.Profile{ - BuyAt: buyAt, - SellAt: sellAt, - PayAt: uint(payAt), - DropAt: uint(dropAt), - }, - Strategy: &swap.Strategy{ - AutoCashInterval: autoCashInterval, - AutoCashThreshold: autoCashThreshold, - AutoDepositInterval: autoDepositInterval, - AutoDepositThreshold: autoDepositThreshold, - AutoDepositBuffer: autoDepositBuffer, - }, - }, - } -} - -// Init this can only finally be set after all config options (file, cmd line, env vars) -// have been evaluated -func (lp *LocalProfile) Init(contract common.Address, prvkey *ecdsa.PrivateKey) { - pubkey := &prvkey.PublicKey - - lp.PayProfile = &PayProfile{ - PublicKey: common.ToHex(crypto.FromECDSAPub(pubkey)), - Contract: contract, - Beneficiary: crypto.PubkeyToAddress(*pubkey), - privateKey: prvkey, - publicKey: pubkey, - owner: crypto.PubkeyToAddress(*pubkey), - } -} - -// NewSwap constructor, parameters -// * global chequebook, assume deployed service and -// * the balance is at buffer. -// swap.Add(n) called in netstore -// n > 0 called when sending chunks = receiving retrieve requests -// OR sending cheques. -// n < 0 called when receiving chunks = receiving delivery responses -// OR receiving cheques. -func NewSwap(localProfile *LocalProfile, remoteProfile *RemoteProfile, backend chequebook.Backend, proto swap.Protocol) (swapInstance *swap.Swap, err error) { - var ( - ctx = context.TODO() - ok bool - in *chequebook.Inbox - out *chequebook.Outbox - ) - - remotekey, err := crypto.UnmarshalPubkey(common.FromHex(remoteProfile.PublicKey)) - if err != nil { - return nil, errors.New("invalid remote public key") - } - - // check if remoteProfile chequebook is valid - // insolvent chequebooks suicide so will signal as invalid - // TODO: monitoring a chequebooks events - ok, err = chequebook.ValidateCode(ctx, backend, remoteProfile.Contract) - if !ok { - log.Info(fmt.Sprintf("invalid contract %v for peer %v: %v)", remoteProfile.Contract.Hex()[:8], proto, err)) - } else { - // remoteProfile contract valid, create inbox - in, err = chequebook.NewInbox(localProfile.privateKey, remoteProfile.Contract, localProfile.Beneficiary, remotekey, backend) - if err != nil { - log.Warn(fmt.Sprintf("unable to set up inbox for chequebook contract %v for peer %v: %v)", remoteProfile.Contract.Hex()[:8], proto, err)) - } - } - - // check if localProfile chequebook contract is valid - ok, err = chequebook.ValidateCode(ctx, backend, localProfile.Contract) - if !ok { - log.Warn(fmt.Sprintf("unable to set up outbox for peer %v: chequebook contract (owner: %v): %v)", proto, localProfile.owner.Hex(), err)) - } else { - out = chequebook.NewOutbox(localProfile.Chequebook(), remoteProfile.Beneficiary) - } - - pm := swap.Payment{ - In: in, - Out: out, - Buys: out != nil, - Sells: in != nil, - } - swapInstance, err = swap.New(localProfile.Params, pm, proto) - if err != nil { - return - } - // remoteProfile profile given (first) in handshake - swapInstance.SetRemote(remoteProfile.Profile) - var buy, sell string - if swapInstance.Buys { - buy = "purchase from peer enabled at " + remoteProfile.SellAt.String() + " wei/chunk" - } else { - buy = "purchase from peer disabled" - } - if swapInstance.Sells { - sell = "selling to peer enabled at " + localProfile.SellAt.String() + " wei/chunk" - } else { - sell = "selling to peer disabled" - } - log.Warn(fmt.Sprintf("SWAP arrangement with <%v>: %v; %v)", proto, buy, sell)) - - return -} - -// Chequebook get's chequebook from the localProfile -func (lp *LocalProfile) Chequebook() *chequebook.Chequebook { - defer lp.lock.Unlock() - lp.lock.Lock() - return lp.chbook -} - -// PrivateKey accessor -func (lp *LocalProfile) PrivateKey() *ecdsa.PrivateKey { - return lp.privateKey -} - -// func (self *LocalProfile) PublicKey() *ecdsa.PublicKey { -// return self.publicKey -// } - -// SetKey set's private and public key on localProfile -func (lp *LocalProfile) SetKey(prvkey *ecdsa.PrivateKey) { - lp.privateKey = prvkey - lp.publicKey = &prvkey.PublicKey -} - -// SetChequebook wraps the chequebook initialiser and sets up autoDeposit to cover spending. -func (lp *LocalProfile) SetChequebook(ctx context.Context, backend chequebook.Backend, path string) error { - lp.lock.Lock() - swapContract := lp.Contract - lp.lock.Unlock() - - valid, err := chequebook.ValidateCode(ctx, backend, swapContract) - if err != nil { - return err - } else if valid { - return lp.newChequebookFromContract(path, backend) - } - return lp.deployChequebook(ctx, backend, path) -} - -// deployChequebook deploys the localProfile Chequebook -func (lp *LocalProfile) deployChequebook(ctx context.Context, backend chequebook.Backend, path string) error { - opts := bind.NewKeyedTransactor(lp.privateKey) - opts.Value = lp.AutoDepositBuffer - opts.Context = ctx - - log.Info(fmt.Sprintf("Deploying new chequebook (owner: %v)", opts.From.Hex())) - address, err := deployChequebookLoop(opts, backend) - if err != nil { - log.Error(fmt.Sprintf("unable to deploy new chequebook: %v", err)) - return err - } - log.Info(fmt.Sprintf("new chequebook deployed at %v (owner: %v)", address.Hex(), opts.From.Hex())) - - // need to save config at this point - lp.lock.Lock() - lp.Contract = address - err = lp.newChequebookFromContract(path, backend) - lp.lock.Unlock() - if err != nil { - log.Warn(fmt.Sprintf("error initialising cheque book (owner: %v): %v", opts.From.Hex(), err)) - } - return err -} - -// deployChequebookLoop repeatedly tries to deploy a chequebook. -func deployChequebookLoop(opts *bind.TransactOpts, backend chequebook.Backend) (addr common.Address, err error) { - var tx *types.Transaction - for try := 0; try < chequebookDeployRetries; try++ { - if try > 0 { - time.Sleep(chequebookDeployDelay) - } - if _, tx, _, err = contract.DeployChequebook(opts, backend); err != nil { - log.Warn(fmt.Sprintf("can't send chequebook deploy tx (try %d): %v", try, err)) - continue - } - if addr, err = bind.WaitDeployed(opts.Context, backend, tx); err != nil { - log.Warn(fmt.Sprintf("chequebook deploy error (try %d): %v", try, err)) - continue - } - return addr, nil - } - return addr, err -} - -// newChequebookFromContract - initialise the chequebook from a persisted json file or create a new one -// caller holds the lock -func (lp *LocalProfile) newChequebookFromContract(path string, backend chequebook.Backend) error { - hexkey := common.Bytes2Hex(lp.Contract.Bytes()) - err := os.MkdirAll(filepath.Join(path, "chequebooks"), os.ModePerm) - if err != nil { - return fmt.Errorf("unable to create directory for chequebooks: %v", err) - } - - chbookpath := filepath.Join(path, "chequebooks", hexkey+".json") - lp.chbook, err = chequebook.LoadChequebook(chbookpath, lp.privateKey, backend, true) - - if err != nil { - lp.chbook, err = chequebook.NewChequebook(chbookpath, lp.Contract, lp.privateKey, backend) - if err != nil { - log.Warn(fmt.Sprintf("unable to initialise chequebook (owner: %v): %v", lp.owner.Hex(), err)) - return fmt.Errorf("unable to initialise chequebook (owner: %v): %v", lp.owner.Hex(), err) - } - } - - lp.chbook.AutoDeposit(lp.AutoDepositInterval, lp.AutoDepositThreshold, lp.AutoDepositBuffer) - log.Info(fmt.Sprintf("auto deposit ON for %v -> %v: interval = %v, threshold = %v, buffer = %v)", crypto.PubkeyToAddress(*(lp.publicKey)).Hex()[:8], lp.Contract.Hex()[:8], lp.AutoDepositInterval, lp.AutoDepositThreshold, lp.AutoDepositBuffer)) - - return nil -} diff --git a/swarm/services/swap/swap/swap.go b/swarm/services/swap/swap/swap.go deleted file mode 100644 index e08323446373..000000000000 --- a/swarm/services/swap/swap/swap.go +++ /dev/null @@ -1,252 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package swap - -import ( - "fmt" - "math/big" - "sync" - "time" - - "github.com/ubiq/go-ubiq/swarm/log" -) - -// SwAP Swarm Accounting Protocol with -// Swift Automatic Payments -// a peer to peer micropayment system - -// Profile - public swap profile -// public parameters for SWAP, serializable config struct passed in handshake -type Profile struct { - BuyAt *big.Int // accepted max price for chunk - SellAt *big.Int // offered sale price for chunk - PayAt uint // threshold that triggers payment request - DropAt uint // threshold that triggers disconnect -} - -// Strategy encapsulates parameters relating to -// automatic deposit and automatic cashing -type Strategy struct { - AutoCashInterval time.Duration // default interval for autocash - AutoCashThreshold *big.Int // threshold that triggers autocash (wei) - AutoDepositInterval time.Duration // default interval for autocash - AutoDepositThreshold *big.Int // threshold that triggers autodeposit (wei) - AutoDepositBuffer *big.Int // buffer that is surplus for fork protection etc (wei) -} - -// Params extends the public profile with private parameters relating to -// automatic deposit and automatic cashing -type Params struct { - *Profile - *Strategy -} - -// Promise - 3rd party Provable Promise of Payment -// issued by outPayment -// serializable to send with Protocol -type Promise interface{} - -// Protocol interface for the peer protocol for testing or external alternative payment -type Protocol interface { - Pay(int, Promise) // units, payment proof - Drop() - String() string -} - -// OutPayment interface for the (delayed) outgoing payment system with auto-deposit -type OutPayment interface { - Issue(amount *big.Int) (promise Promise, err error) - AutoDeposit(interval time.Duration, threshold, buffer *big.Int) - Stop() -} - -// InPayment interface for the (delayed) incoming payment system with autocash -type InPayment interface { - Receive(promise Promise) (*big.Int, error) - AutoCash(cashInterval time.Duration, maxUncashed *big.Int) - Stop() -} - -// Swap is the swarm accounting protocol instance -// * pairwise accounting and payments -type Swap struct { - lock sync.Mutex // mutex for balance access - balance int // units of chunk/retrieval request - local *Params // local peer's swap parameters - remote *Profile // remote peer's swap profile - proto Protocol // peer communication protocol - Payment -} - -// Payment handlers -type Payment struct { - Out OutPayment // outgoing payment handler - In InPayment // incoming payment handler - Buys, Sells bool -} - -// New - swap constructor -func New(local *Params, pm Payment, proto Protocol) (swap *Swap, err error) { - - swap = &Swap{ - local: local, - Payment: pm, - proto: proto, - } - - swap.SetParams(local) - - return -} - -// SetRemote - entry point for setting remote swap profile (e.g from handshake or other message) -func (swap *Swap) SetRemote(remote *Profile) { - defer swap.lock.Unlock() - swap.lock.Lock() - - swap.remote = remote - if swap.Sells && (remote.BuyAt.Sign() <= 0 || swap.local.SellAt.Sign() <= 0 || remote.BuyAt.Cmp(swap.local.SellAt) < 0) { - swap.Out.Stop() - swap.Sells = false - } - if swap.Buys && (remote.SellAt.Sign() <= 0 || swap.local.BuyAt.Sign() <= 0 || swap.local.BuyAt.Cmp(swap.remote.SellAt) < 0) { - swap.In.Stop() - swap.Buys = false - } - - log.Debug(fmt.Sprintf("<%v> remote profile set: pay at: %v, drop at: %v, buy at: %v, sell at: %v", swap.proto, remote.PayAt, remote.DropAt, remote.BuyAt, remote.SellAt)) - -} - -// SetParams - to set strategy dynamically -func (swap *Swap) SetParams(local *Params) { - defer swap.lock.Unlock() - swap.lock.Lock() - swap.local = local - swap.setParams(local) -} - -// setParams - caller holds the lock -func (swap *Swap) setParams(local *Params) { - - if swap.Sells { - swap.In.AutoCash(local.AutoCashInterval, local.AutoCashThreshold) - log.Info(fmt.Sprintf("<%v> set autocash to every %v, max uncashed limit: %v", swap.proto, local.AutoCashInterval, local.AutoCashThreshold)) - } else { - log.Info(fmt.Sprintf("<%v> autocash off (not selling)", swap.proto)) - } - if swap.Buys { - swap.Out.AutoDeposit(local.AutoDepositInterval, local.AutoDepositThreshold, local.AutoDepositBuffer) - log.Info(fmt.Sprintf("<%v> set autodeposit to every %v, pay at: %v, buffer: %v", swap.proto, local.AutoDepositInterval, local.AutoDepositThreshold, local.AutoDepositBuffer)) - } else { - log.Info(fmt.Sprintf("<%v> autodeposit off (not buying)", swap.proto)) - } -} - -// Add (n) -// n > 0 called when promised/provided n units of service -// n < 0 called when used/requested n units of service -func (swap *Swap) Add(n int) error { - defer swap.lock.Unlock() - swap.lock.Lock() - swap.balance += n - if !swap.Sells && swap.balance > 0 { - log.Trace(fmt.Sprintf("<%v> remote peer cannot have debt (balance: %v)", swap.proto, swap.balance)) - swap.proto.Drop() - return fmt.Errorf("[SWAP] <%v> remote peer cannot have debt (balance: %v)", swap.proto, swap.balance) - } - if !swap.Buys && swap.balance < 0 { - log.Trace(fmt.Sprintf("<%v> we cannot have debt (balance: %v)", swap.proto, swap.balance)) - return fmt.Errorf("[SWAP] <%v> we cannot have debt (balance: %v)", swap.proto, swap.balance) - } - if swap.balance >= int(swap.local.DropAt) { - log.Trace(fmt.Sprintf("<%v> remote peer has too much debt (balance: %v, disconnect threshold: %v)", swap.proto, swap.balance, swap.local.DropAt)) - swap.proto.Drop() - return fmt.Errorf("[SWAP] <%v> remote peer has too much debt (balance: %v, disconnect threshold: %v)", swap.proto, swap.balance, swap.local.DropAt) - } else if swap.balance <= -int(swap.remote.PayAt) { - swap.send() - } - return nil -} - -// Balance accessor -func (swap *Swap) Balance() int { - defer swap.lock.Unlock() - swap.lock.Lock() - return swap.balance -} - -// send (units) is called when payment is due -// In case of insolvency no promise is issued and sent, safe against fraud -// No return value: no error = payment is opportunistic = hang in till dropped -func (swap *Swap) send() { - if swap.local.BuyAt != nil && swap.balance < 0 { - amount := big.NewInt(int64(-swap.balance)) - amount.Mul(amount, swap.remote.SellAt) - promise, err := swap.Out.Issue(amount) - if err != nil { - log.Warn(fmt.Sprintf("<%v> cannot issue cheque (amount: %v, channel: %v): %v", swap.proto, amount, swap.Out, err)) - } else { - log.Warn(fmt.Sprintf("<%v> cheque issued (amount: %v, channel: %v)", swap.proto, amount, swap.Out)) - swap.proto.Pay(-swap.balance, promise) - swap.balance = 0 - } - } -} - -// Receive (units, promise) is called by the protocol when a payment msg is received -// returns error if promise is invalid. -func (swap *Swap) Receive(units int, promise Promise) error { - if units <= 0 { - return fmt.Errorf("invalid units: %v <= 0", units) - } - - price := new(big.Int).SetInt64(int64(units)) - price.Mul(price, swap.local.SellAt) - - amount, err := swap.In.Receive(promise) - - if err != nil { - err = fmt.Errorf("invalid promise: %v", err) - } else if price.Cmp(amount) != 0 { - // verify amount = units * unit sale price - return fmt.Errorf("invalid amount: %v = %v * %v (units sent in msg * agreed sale unit price) != %v (signed in cheque)", price, units, swap.local.SellAt, amount) - } - if err != nil { - log.Trace(fmt.Sprintf("<%v> invalid promise (amount: %v, channel: %v): %v", swap.proto, amount, swap.In, err)) - return err - } - - // credit remote peer with units - swap.Add(-units) - log.Trace(fmt.Sprintf("<%v> received promise (amount: %v, channel: %v): %v", swap.proto, amount, swap.In, promise)) - - return nil -} - -// Stop causes autocash loop to terminate. -// Called after protocol handle loop terminates. -func (swap *Swap) Stop() { - defer swap.lock.Unlock() - swap.lock.Lock() - if swap.Buys { - swap.Out.Stop() - } - if swap.Sells { - swap.In.Stop() - } -} diff --git a/swarm/services/swap/swap/swap_test.go b/swarm/services/swap/swap/swap_test.go deleted file mode 100644 index 7c28f079a896..000000000000 --- a/swarm/services/swap/swap/swap_test.go +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package swap - -import ( - "math/big" - "testing" - "time" - - "github.com/ubiq/go-ubiq/common" -) - -type testInPayment struct { - received []*testPromise - autocashInterval time.Duration - autocashLimit *big.Int -} - -type testPromise struct { - amount *big.Int -} - -func (test *testInPayment) Receive(promise Promise) (*big.Int, error) { - p := promise.(*testPromise) - test.received = append(test.received, p) - return p.amount, nil -} - -func (test *testInPayment) AutoCash(interval time.Duration, limit *big.Int) { - test.autocashInterval = interval - test.autocashLimit = limit -} - -func (test *testInPayment) Cash() (string, error) { return "", nil } - -func (test *testInPayment) Stop() {} - -type testOutPayment struct { - deposits []*big.Int - autodepositInterval time.Duration - autodepositThreshold *big.Int - autodepositBuffer *big.Int -} - -func (test *testOutPayment) Issue(amount *big.Int) (promise Promise, err error) { - return &testPromise{amount}, nil -} - -func (test *testOutPayment) Deposit(amount *big.Int) (string, error) { - test.deposits = append(test.deposits, amount) - return "", nil -} - -func (test *testOutPayment) AutoDeposit(interval time.Duration, threshold, buffer *big.Int) { - test.autodepositInterval = interval - test.autodepositThreshold = threshold - test.autodepositBuffer = buffer -} - -func (test *testOutPayment) Stop() {} - -type testProtocol struct { - drop bool - amounts []int - promises []*testPromise -} - -func (test *testProtocol) Drop() { - test.drop = true -} - -func (test *testProtocol) String() string { - return "" -} - -func (test *testProtocol) Pay(amount int, promise Promise) { - p := promise.(*testPromise) - test.promises = append(test.promises, p) - test.amounts = append(test.amounts, amount) -} - -func TestSwap(t *testing.T) { - - strategy := &Strategy{ - AutoCashInterval: 1 * time.Second, - AutoCashThreshold: big.NewInt(20), - AutoDepositInterval: 1 * time.Second, - AutoDepositThreshold: big.NewInt(20), - AutoDepositBuffer: big.NewInt(40), - } - - local := &Params{ - Profile: &Profile{ - PayAt: 5, - DropAt: 10, - BuyAt: common.Big3, - SellAt: common.Big2, - }, - Strategy: strategy, - } - - in := &testInPayment{} - out := &testOutPayment{} - proto := &testProtocol{} - - swap, _ := New(local, Payment{In: in, Out: out, Buys: true, Sells: true}, proto) - - if in.autocashInterval != strategy.AutoCashInterval { - t.Fatalf("autocash interval not properly set, expect %v, got %v", strategy.AutoCashInterval, in.autocashInterval) - } - if out.autodepositInterval != strategy.AutoDepositInterval { - t.Fatalf("autodeposit interval not properly set, expect %v, got %v", strategy.AutoDepositInterval, out.autodepositInterval) - } - - remote := &Profile{ - PayAt: 3, - DropAt: 10, - BuyAt: common.Big2, - SellAt: common.Big3, - } - swap.SetRemote(remote) - - swap.Add(9) - if proto.drop { - t.Fatalf("not expected peer to be dropped") - } - swap.Add(1) - if !proto.drop { - t.Fatalf("expected peer to be dropped") - } - if !proto.drop { - t.Fatalf("expected peer to be dropped") - } - proto.drop = false - - swap.Receive(10, &testPromise{big.NewInt(20)}) - if swap.balance != 0 { - t.Fatalf("expected zero balance, got %v", swap.balance) - } - - if len(proto.amounts) != 0 { - t.Fatalf("expected zero balance, got %v", swap.balance) - } - - swap.Add(-2) - if len(proto.amounts) > 0 { - t.Fatalf("expected no payments yet, got %v", proto.amounts) - } - - swap.Add(-1) - if len(proto.amounts) != 1 { - t.Fatalf("expected one payment, got %v", len(proto.amounts)) - } - - if proto.amounts[0] != 3 { - t.Fatalf("expected payment for %v units, got %v", proto.amounts[0], 3) - } - - exp := new(big.Int).Mul(big.NewInt(int64(proto.amounts[0])), remote.SellAt) - if proto.promises[0].amount.Cmp(exp) != 0 { - t.Fatalf("expected payment amount %v, got %v", exp, proto.promises[0].amount) - } - - swap.SetParams(&Params{ - Profile: &Profile{ - PayAt: 5, - DropAt: 10, - BuyAt: common.Big3, - SellAt: common.Big2, - }, - Strategy: &Strategy{ - AutoCashInterval: 2 * time.Second, - AutoCashThreshold: big.NewInt(40), - AutoDepositInterval: 2 * time.Second, - AutoDepositThreshold: big.NewInt(40), - AutoDepositBuffer: big.NewInt(60), - }, - }) - -} diff --git a/swarm/shed/db.go b/swarm/shed/db.go deleted file mode 100644 index d94ac1ee7160..000000000000 --- a/swarm/shed/db.go +++ /dev/null @@ -1,329 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Package shed provides a simple abstraction components to compose -// more complex operations on storage data organized in fields and indexes. -// -// Only type which holds logical information about swarm storage chunks data -// and metadata is Item. This part is not generalized mostly for -// performance reasons. -package shed - -import ( - "errors" - "fmt" - "strconv" - "strings" - "time" - - "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/syndtr/goleveldb/leveldb" - "github.com/syndtr/goleveldb/leveldb/iterator" - "github.com/syndtr/goleveldb/leveldb/opt" -) - -const ( - openFileLimit = 128 // The limit for LevelDB OpenFilesCacheCapacity. - writePauseWarningThrottler = 1 * time.Minute -) - -// DB provides abstractions over LevelDB in order to -// implement complex structures using fields and ordered indexes. -// It provides a schema functionality to store fields and indexes -// information about naming and types. -type DB struct { - ldb *leveldb.DB - - compTimeMeter metrics.Meter // Meter for measuring the total time spent in database compaction - compReadMeter metrics.Meter // Meter for measuring the data read during compaction - compWriteMeter metrics.Meter // Meter for measuring the data written during compaction - writeDelayNMeter metrics.Meter // Meter for measuring the write delay number due to database compaction - writeDelayMeter metrics.Meter // Meter for measuring the write delay duration due to database compaction - diskReadMeter metrics.Meter // Meter for measuring the effective amount of data read - diskWriteMeter metrics.Meter // Meter for measuring the effective amount of data written - - quitChan chan chan error // Quit channel to stop the metrics collection before closing the database -} - -// NewDB constructs a new DB and validates the schema -// if it exists in database on the given path. -// metricsPrefix is used for metrics collection for the given DB. -func NewDB(path string, metricsPrefix string) (db *DB, err error) { - ldb, err := leveldb.OpenFile(path, &opt.Options{ - OpenFilesCacheCapacity: openFileLimit, - }) - if err != nil { - return nil, err - } - db = &DB{ - ldb: ldb, - } - - if _, err = db.getSchema(); err != nil { - if err == leveldb.ErrNotFound { - // save schema with initialized default fields - if err = db.putSchema(schema{ - Fields: make(map[string]fieldSpec), - Indexes: make(map[byte]indexSpec), - }); err != nil { - return nil, err - } - } else { - return nil, err - } - } - - // Configure meters for DB - db.configure(metricsPrefix) - - // Create a quit channel for the periodic metrics collector and run it - db.quitChan = make(chan chan error) - - go db.meter(10 * time.Second) - - return db, nil -} - -// Put wraps LevelDB Put method to increment metrics counter. -func (db *DB) Put(key []byte, value []byte) (err error) { - err = db.ldb.Put(key, value, nil) - if err != nil { - metrics.GetOrRegisterCounter("DB.putFail", nil).Inc(1) - return err - } - metrics.GetOrRegisterCounter("DB.put", nil).Inc(1) - return nil -} - -// Get wraps LevelDB Get method to increment metrics counter. -func (db *DB) Get(key []byte) (value []byte, err error) { - value, err = db.ldb.Get(key, nil) - if err != nil { - if err == leveldb.ErrNotFound { - metrics.GetOrRegisterCounter("DB.getNotFound", nil).Inc(1) - } else { - metrics.GetOrRegisterCounter("DB.getFail", nil).Inc(1) - } - return nil, err - } - metrics.GetOrRegisterCounter("DB.get", nil).Inc(1) - return value, nil -} - -// Delete wraps LevelDB Delete method to increment metrics counter. -func (db *DB) Delete(key []byte) (err error) { - err = db.ldb.Delete(key, nil) - if err != nil { - metrics.GetOrRegisterCounter("DB.deleteFail", nil).Inc(1) - return err - } - metrics.GetOrRegisterCounter("DB.delete", nil).Inc(1) - return nil -} - -// NewIterator wraps LevelDB NewIterator method to increment metrics counter. -func (db *DB) NewIterator() iterator.Iterator { - metrics.GetOrRegisterCounter("DB.newiterator", nil).Inc(1) - - return db.ldb.NewIterator(nil, nil) -} - -// WriteBatch wraps LevelDB Write method to increment metrics counter. -func (db *DB) WriteBatch(batch *leveldb.Batch) (err error) { - err = db.ldb.Write(batch, nil) - if err != nil { - metrics.GetOrRegisterCounter("DB.writebatchFail", nil).Inc(1) - return err - } - metrics.GetOrRegisterCounter("DB.writebatch", nil).Inc(1) - return nil -} - -// Close closes LevelDB database. -func (db *DB) Close() (err error) { - close(db.quitChan) - return db.ldb.Close() -} - -// Configure configures the database metrics collectors -func (db *DB) configure(prefix string) { - // Initialize all the metrics collector at the requested prefix - db.compTimeMeter = metrics.NewRegisteredMeter(prefix+"compact/time", nil) - db.compReadMeter = metrics.NewRegisteredMeter(prefix+"compact/input", nil) - db.compWriteMeter = metrics.NewRegisteredMeter(prefix+"compact/output", nil) - db.diskReadMeter = metrics.NewRegisteredMeter(prefix+"disk/read", nil) - db.diskWriteMeter = metrics.NewRegisteredMeter(prefix+"disk/write", nil) - db.writeDelayMeter = metrics.NewRegisteredMeter(prefix+"compact/writedelay/duration", nil) - db.writeDelayNMeter = metrics.NewRegisteredMeter(prefix+"compact/writedelay/counter", nil) -} - -func (db *DB) meter(refresh time.Duration) { - // Create the counters to store current and previous compaction values - compactions := make([][]float64, 2) - for i := 0; i < 2; i++ { - compactions[i] = make([]float64, 3) - } - // Create storage for iostats. - var iostats [2]float64 - - // Create storage and warning log tracer for write delay. - var ( - delaystats [2]int64 - lastWritePaused time.Time - ) - - var ( - errc chan error - merr error - ) - - // Iterate ad infinitum and collect the stats - for i := 1; errc == nil && merr == nil; i++ { - // Retrieve the database stats - stats, err := db.ldb.GetProperty("leveldb.stats") - if err != nil { - log.Error("Failed to read database stats", "err", err) - merr = err - continue - } - // Find the compaction table, skip the header - lines := strings.Split(stats, "\n") - for len(lines) > 0 && strings.TrimSpace(lines[0]) != "Compactions" { - lines = lines[1:] - } - if len(lines) <= 3 { - log.Error("Compaction table not found") - merr = errors.New("compaction table not found") - continue - } - lines = lines[3:] - - // Iterate over all the table rows, and accumulate the entries - for j := 0; j < len(compactions[i%2]); j++ { - compactions[i%2][j] = 0 - } - for _, line := range lines { - parts := strings.Split(line, "|") - if len(parts) != 6 { - break - } - for idx, counter := range parts[3:] { - value, err := strconv.ParseFloat(strings.TrimSpace(counter), 64) - if err != nil { - log.Error("Compaction entry parsing failed", "err", err) - merr = err - continue - } - compactions[i%2][idx] += value - } - } - // Update all the requested meters - if db.compTimeMeter != nil { - db.compTimeMeter.Mark(int64((compactions[i%2][0] - compactions[(i-1)%2][0]) * 1000 * 1000 * 1000)) - } - if db.compReadMeter != nil { - db.compReadMeter.Mark(int64((compactions[i%2][1] - compactions[(i-1)%2][1]) * 1024 * 1024)) - } - if db.compWriteMeter != nil { - db.compWriteMeter.Mark(int64((compactions[i%2][2] - compactions[(i-1)%2][2]) * 1024 * 1024)) - } - - // Retrieve the write delay statistic - writedelay, err := db.ldb.GetProperty("leveldb.writedelay") - if err != nil { - log.Error("Failed to read database write delay statistic", "err", err) - merr = err - continue - } - var ( - delayN int64 - delayDuration string - duration time.Duration - paused bool - ) - if n, err := fmt.Sscanf(writedelay, "DelayN:%d Delay:%s Paused:%t", &delayN, &delayDuration, &paused); n != 3 || err != nil { - log.Error("Write delay statistic not found") - merr = err - continue - } - duration, err = time.ParseDuration(delayDuration) - if err != nil { - log.Error("Failed to parse delay duration", "err", err) - merr = err - continue - } - if db.writeDelayNMeter != nil { - db.writeDelayNMeter.Mark(delayN - delaystats[0]) - } - if db.writeDelayMeter != nil { - db.writeDelayMeter.Mark(duration.Nanoseconds() - delaystats[1]) - } - // If a warning that db is performing compaction has been displayed, any subsequent - // warnings will be withheld for one minute not to overwhelm the user. - if paused && delayN-delaystats[0] == 0 && duration.Nanoseconds()-delaystats[1] == 0 && - time.Now().After(lastWritePaused.Add(writePauseWarningThrottler)) { - log.Warn("Database compacting, degraded performance") - lastWritePaused = time.Now() - } - delaystats[0], delaystats[1] = delayN, duration.Nanoseconds() - - // Retrieve the database iostats. - ioStats, err := db.ldb.GetProperty("leveldb.iostats") - if err != nil { - log.Error("Failed to read database iostats", "err", err) - merr = err - continue - } - var nRead, nWrite float64 - parts := strings.Split(ioStats, " ") - if len(parts) < 2 { - log.Error("Bad syntax of ioStats", "ioStats", ioStats) - merr = fmt.Errorf("bad syntax of ioStats %s", ioStats) - continue - } - if n, err := fmt.Sscanf(parts[0], "Read(MB):%f", &nRead); n != 1 || err != nil { - log.Error("Bad syntax of read entry", "entry", parts[0]) - merr = err - continue - } - if n, err := fmt.Sscanf(parts[1], "Write(MB):%f", &nWrite); n != 1 || err != nil { - log.Error("Bad syntax of write entry", "entry", parts[1]) - merr = err - continue - } - if db.diskReadMeter != nil { - db.diskReadMeter.Mark(int64((nRead - iostats[0]) * 1024 * 1024)) - } - if db.diskWriteMeter != nil { - db.diskWriteMeter.Mark(int64((nWrite - iostats[1]) * 1024 * 1024)) - } - iostats[0], iostats[1] = nRead, nWrite - - // Sleep a bit, then repeat the stats collection - select { - case errc = <-db.quitChan: - // Quit requesting, stop hammering the database - case <-time.After(refresh): - // Timeout, gather a new set of stats - } - } - - if errc == nil { - errc = <-db.quitChan - } - errc <- merr -} diff --git a/swarm/shed/db_test.go b/swarm/shed/db_test.go deleted file mode 100644 index 65fdac4a6186..000000000000 --- a/swarm/shed/db_test.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package shed - -import ( - "io/ioutil" - "os" - "testing" -) - -// TestNewDB constructs a new DB -// and validates if the schema is initialized properly. -func TestNewDB(t *testing.T) { - db, cleanupFunc := newTestDB(t) - defer cleanupFunc() - - s, err := db.getSchema() - if err != nil { - t.Fatal(err) - } - if s.Fields == nil { - t.Error("schema fields are empty") - } - if len(s.Fields) != 0 { - t.Errorf("got schema fields length %v, want %v", len(s.Fields), 0) - } - if s.Indexes == nil { - t.Error("schema indexes are empty") - } - if len(s.Indexes) != 0 { - t.Errorf("got schema indexes length %v, want %v", len(s.Indexes), 0) - } -} - -// TestDB_persistence creates one DB, saves a field and closes that DB. -// Then, it constructs another DB and trues to retrieve the saved value. -func TestDB_persistence(t *testing.T) { - dir, err := ioutil.TempDir("", "shed-test-persistence") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - - db, err := NewDB(dir, "") - if err != nil { - t.Fatal(err) - } - stringField, err := db.NewStringField("preserve-me") - if err != nil { - t.Fatal(err) - } - want := "persistent value" - err = stringField.Put(want) - if err != nil { - t.Fatal(err) - } - err = db.Close() - if err != nil { - t.Fatal(err) - } - - db2, err := NewDB(dir, "") - if err != nil { - t.Fatal(err) - } - stringField2, err := db2.NewStringField("preserve-me") - if err != nil { - t.Fatal(err) - } - got, err := stringField2.Get() - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("got string %q, want %q", got, want) - } -} - -// newTestDB is a helper function that constructs a -// temporary database and returns a cleanup function that must -// be called to remove the data. -func newTestDB(t *testing.T) (db *DB, cleanupFunc func()) { - t.Helper() - - dir, err := ioutil.TempDir("", "shed-test") - if err != nil { - t.Fatal(err) - } - cleanupFunc = func() { os.RemoveAll(dir) } - db, err = NewDB(dir, "") - if err != nil { - cleanupFunc() - t.Fatal(err) - } - return db, cleanupFunc -} diff --git a/swarm/shed/example_store_test.go b/swarm/shed/example_store_test.go deleted file mode 100644 index 7d290bc021d9..000000000000 --- a/swarm/shed/example_store_test.go +++ /dev/null @@ -1,332 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package shed_test - -import ( - "bytes" - "context" - "encoding/binary" - "fmt" - "io/ioutil" - "log" - "os" - "time" - - "github.com/ubiq/go-ubiq/swarm/shed" - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/syndtr/goleveldb/leveldb" -) - -// Store holds fields and indexes (including their encoding functions) -// and defines operations on them by composing data from them. -// It implements storage.ChunkStore interface. -// It is just an example without any support for parallel operations -// or real world implementation. -type Store struct { - db *shed.DB - - // fields and indexes - schemaName shed.StringField - sizeCounter shed.Uint64Field - accessCounter shed.Uint64Field - retrievalIndex shed.Index - accessIndex shed.Index - gcIndex shed.Index -} - -// New returns new Store. All fields and indexes are initialized -// and possible conflicts with schema from existing database is checked -// automatically. -func New(path string) (s *Store, err error) { - db, err := shed.NewDB(path, "") - if err != nil { - return nil, err - } - s = &Store{ - db: db, - } - // Identify current storage schema by arbitrary name. - s.schemaName, err = db.NewStringField("schema-name") - if err != nil { - return nil, err - } - // Global ever incrementing index of chunk accesses. - s.accessCounter, err = db.NewUint64Field("access-counter") - if err != nil { - return nil, err - } - // Index storing actual chunk address, data and store timestamp. - s.retrievalIndex, err = db.NewIndex("Address->StoreTimestamp|Data", shed.IndexFuncs{ - EncodeKey: func(fields shed.Item) (key []byte, err error) { - return fields.Address, nil - }, - DecodeKey: func(key []byte) (e shed.Item, err error) { - e.Address = key - return e, nil - }, - EncodeValue: func(fields shed.Item) (value []byte, err error) { - b := make([]byte, 8) - binary.BigEndian.PutUint64(b, uint64(fields.StoreTimestamp)) - value = append(b, fields.Data...) - return value, nil - }, - DecodeValue: func(keyItem shed.Item, value []byte) (e shed.Item, err error) { - e.StoreTimestamp = int64(binary.BigEndian.Uint64(value[:8])) - e.Data = value[8:] - return e, nil - }, - }) - if err != nil { - return nil, err - } - // Index storing access timestamp for a particular address. - // It is needed in order to update gc index keys for iteration order. - s.accessIndex, err = db.NewIndex("Address->AccessTimestamp", shed.IndexFuncs{ - EncodeKey: func(fields shed.Item) (key []byte, err error) { - return fields.Address, nil - }, - DecodeKey: func(key []byte) (e shed.Item, err error) { - e.Address = key - return e, nil - }, - EncodeValue: func(fields shed.Item) (value []byte, err error) { - b := make([]byte, 8) - binary.BigEndian.PutUint64(b, uint64(fields.AccessTimestamp)) - return b, nil - }, - DecodeValue: func(keyItem shed.Item, value []byte) (e shed.Item, err error) { - e.AccessTimestamp = int64(binary.BigEndian.Uint64(value)) - return e, nil - }, - }) - if err != nil { - return nil, err - } - // Index with keys ordered by access timestamp for garbage collection prioritization. - s.gcIndex, err = db.NewIndex("AccessTimestamp|StoredTimestamp|Address->nil", shed.IndexFuncs{ - EncodeKey: func(fields shed.Item) (key []byte, err error) { - b := make([]byte, 16, 16+len(fields.Address)) - binary.BigEndian.PutUint64(b[:8], uint64(fields.AccessTimestamp)) - binary.BigEndian.PutUint64(b[8:16], uint64(fields.StoreTimestamp)) - key = append(b, fields.Address...) - return key, nil - }, - DecodeKey: func(key []byte) (e shed.Item, err error) { - e.AccessTimestamp = int64(binary.BigEndian.Uint64(key[:8])) - e.StoreTimestamp = int64(binary.BigEndian.Uint64(key[8:16])) - e.Address = key[16:] - return e, nil - }, - EncodeValue: func(fields shed.Item) (value []byte, err error) { - return nil, nil - }, - DecodeValue: func(keyItem shed.Item, value []byte) (e shed.Item, err error) { - return e, nil - }, - }) - if err != nil { - return nil, err - } - return s, nil -} - -// Put stores the chunk and sets it store timestamp. -func (s *Store) Put(_ context.Context, ch storage.Chunk) (err error) { - return s.retrievalIndex.Put(shed.Item{ - Address: ch.Address(), - Data: ch.Data(), - StoreTimestamp: time.Now().UTC().UnixNano(), - }) -} - -// Get retrieves a chunk with the provided address. -// It updates access and gc indexes by removing the previous -// items from them and adding new items as keys of index entries -// are changed. -func (s *Store) Get(_ context.Context, addr storage.Address) (c storage.Chunk, err error) { - batch := new(leveldb.Batch) - - // Get the chunk data and storage timestamp. - item, err := s.retrievalIndex.Get(shed.Item{ - Address: addr, - }) - if err != nil { - if err == leveldb.ErrNotFound { - return nil, storage.ErrChunkNotFound - } - return nil, err - } - - // Get the chunk access timestamp. - accessItem, err := s.accessIndex.Get(shed.Item{ - Address: addr, - }) - switch err { - case nil: - // Remove gc index entry if access timestamp is found. - err = s.gcIndex.DeleteInBatch(batch, shed.Item{ - Address: item.Address, - StoreTimestamp: accessItem.AccessTimestamp, - AccessTimestamp: item.StoreTimestamp, - }) - if err != nil { - return nil, err - } - case leveldb.ErrNotFound: - // Access timestamp is not found. Do not do anything. - // This is the firs get request. - default: - return nil, err - } - - // Specify new access timestamp - accessTimestamp := time.Now().UTC().UnixNano() - - // Put new access timestamp in access index. - err = s.accessIndex.PutInBatch(batch, shed.Item{ - Address: addr, - AccessTimestamp: accessTimestamp, - }) - if err != nil { - return nil, err - } - - // Put new access timestamp in gc index. - err = s.gcIndex.PutInBatch(batch, shed.Item{ - Address: item.Address, - AccessTimestamp: accessTimestamp, - StoreTimestamp: item.StoreTimestamp, - }) - if err != nil { - return nil, err - } - - // Increment access counter. - // Currently this information is not used anywhere. - _, err = s.accessCounter.IncInBatch(batch) - if err != nil { - return nil, err - } - - // Write the batch. - err = s.db.WriteBatch(batch) - if err != nil { - return nil, err - } - - // Return the chunk. - return storage.NewChunk(item.Address, item.Data), nil -} - -// CollectGarbage is an example of index iteration. -// It provides no reliable garbage collection functionality. -func (s *Store) CollectGarbage() (err error) { - const maxTrashSize = 100 - maxRounds := 10 // arbitrary number, needs to be calculated - - // Run a few gc rounds. - for roundCount := 0; roundCount < maxRounds; roundCount++ { - var garbageCount int - // New batch for a new cg round. - trash := new(leveldb.Batch) - // Iterate through all index items and break when needed. - err = s.gcIndex.Iterate(func(item shed.Item) (stop bool, err error) { - // Remove the chunk. - err = s.retrievalIndex.DeleteInBatch(trash, item) - if err != nil { - return false, err - } - // Remove the element in gc index. - err = s.gcIndex.DeleteInBatch(trash, item) - if err != nil { - return false, err - } - // Remove the relation in access index. - err = s.accessIndex.DeleteInBatch(trash, item) - if err != nil { - return false, err - } - garbageCount++ - if garbageCount >= maxTrashSize { - return true, nil - } - return false, nil - }, nil) - if err != nil { - return err - } - if garbageCount == 0 { - return nil - } - err = s.db.WriteBatch(trash) - if err != nil { - return err - } - } - return nil -} - -// GetSchema is an example of retrieveing the most simple -// string from a database field. -func (s *Store) GetSchema() (name string, err error) { - name, err = s.schemaName.Get() - if err == leveldb.ErrNotFound { - return "", nil - } - return name, err -} - -// GetSchema is an example of storing the most simple -// string in a database field. -func (s *Store) PutSchema(name string) (err error) { - return s.schemaName.Put(name) -} - -// Close closes the underlying database. -func (s *Store) Close() error { - return s.db.Close() -} - -// Example_store constructs a simple storage implementation using shed package. -func Example_store() { - dir, err := ioutil.TempDir("", "ephemeral") - if err != nil { - log.Fatal(err) - } - defer os.RemoveAll(dir) - - s, err := New(dir) - if err != nil { - log.Fatal(err) - } - defer s.Close() - - ch := storage.GenerateRandomChunk(1024) - err = s.Put(context.Background(), ch) - if err != nil { - log.Fatal(err) - } - - got, err := s.Get(context.Background(), ch.Address()) - if err != nil { - log.Fatal(err) - } - - fmt.Println(bytes.Equal(got.Data(), ch.Data())) - - //Output: true -} diff --git a/swarm/shed/field_string.go b/swarm/shed/field_string.go deleted file mode 100644 index a7e8f0c75474..000000000000 --- a/swarm/shed/field_string.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package shed - -import ( - "github.com/syndtr/goleveldb/leveldb" -) - -// StringField is the most simple field implementation -// that stores an arbitrary string under a specific LevelDB key. -type StringField struct { - db *DB - key []byte -} - -// NewStringField retruns a new Instance of StringField. -// It validates its name and type against the database schema. -func (db *DB) NewStringField(name string) (f StringField, err error) { - key, err := db.schemaFieldKey(name, "string") - if err != nil { - return f, err - } - return StringField{ - db: db, - key: key, - }, nil -} - -// Get returns a string value from database. -// If the value is not found, an empty string is returned -// an no error. -func (f StringField) Get() (val string, err error) { - b, err := f.db.Get(f.key) - if err != nil { - if err == leveldb.ErrNotFound { - return "", nil - } - return "", err - } - return string(b), nil -} - -// Put stores a string in the database. -func (f StringField) Put(val string) (err error) { - return f.db.Put(f.key, []byte(val)) -} - -// PutInBatch stores a string in a batch that can be -// saved later in database. -func (f StringField) PutInBatch(batch *leveldb.Batch, val string) { - batch.Put(f.key, []byte(val)) -} diff --git a/swarm/shed/field_string_test.go b/swarm/shed/field_string_test.go deleted file mode 100644 index 4215075bca64..000000000000 --- a/swarm/shed/field_string_test.go +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package shed - -import ( - "testing" - - "github.com/syndtr/goleveldb/leveldb" -) - -// TestStringField validates put and get operations -// of the StringField. -func TestStringField(t *testing.T) { - db, cleanupFunc := newTestDB(t) - defer cleanupFunc() - - simpleString, err := db.NewStringField("simple-string") - if err != nil { - t.Fatal(err) - } - - t.Run("get empty", func(t *testing.T) { - got, err := simpleString.Get() - if err != nil { - t.Fatal(err) - } - want := "" - if got != want { - t.Errorf("got string %q, want %q", got, want) - } - }) - - t.Run("put", func(t *testing.T) { - want := "simple string value" - err = simpleString.Put(want) - if err != nil { - t.Fatal(err) - } - got, err := simpleString.Get() - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("got string %q, want %q", got, want) - } - - t.Run("overwrite", func(t *testing.T) { - want := "overwritten string value" - err = simpleString.Put(want) - if err != nil { - t.Fatal(err) - } - got, err := simpleString.Get() - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("got string %q, want %q", got, want) - } - }) - }) - - t.Run("put in batch", func(t *testing.T) { - batch := new(leveldb.Batch) - want := "simple string batch value" - simpleString.PutInBatch(batch, want) - err = db.WriteBatch(batch) - if err != nil { - t.Fatal(err) - } - got, err := simpleString.Get() - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("got string %q, want %q", got, want) - } - - t.Run("overwrite", func(t *testing.T) { - batch := new(leveldb.Batch) - want := "overwritten string batch value" - simpleString.PutInBatch(batch, want) - err = db.WriteBatch(batch) - if err != nil { - t.Fatal(err) - } - got, err := simpleString.Get() - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("got string %q, want %q", got, want) - } - }) - }) -} diff --git a/swarm/shed/field_struct.go b/swarm/shed/field_struct.go deleted file mode 100644 index df9f8765a853..000000000000 --- a/swarm/shed/field_struct.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package shed - -import ( - "github.com/ubiq/go-ubiq/rlp" - "github.com/syndtr/goleveldb/leveldb" -) - -// StructField is a helper to store complex structure by -// encoding it in RLP format. -type StructField struct { - db *DB - key []byte -} - -// NewStructField returns a new StructField. -// It validates its name and type against the database schema. -func (db *DB) NewStructField(name string) (f StructField, err error) { - key, err := db.schemaFieldKey(name, "struct-rlp") - if err != nil { - return f, err - } - return StructField{ - db: db, - key: key, - }, nil -} - -// Get unmarshals data from the database to a provided val. -// If the data is not found leveldb.ErrNotFound is returned. -func (f StructField) Get(val interface{}) (err error) { - b, err := f.db.Get(f.key) - if err != nil { - return err - } - return rlp.DecodeBytes(b, val) -} - -// Put marshals provided val and saves it to the database. -func (f StructField) Put(val interface{}) (err error) { - b, err := rlp.EncodeToBytes(val) - if err != nil { - return err - } - return f.db.Put(f.key, b) -} - -// PutInBatch marshals provided val and puts it into the batch. -func (f StructField) PutInBatch(batch *leveldb.Batch, val interface{}) (err error) { - b, err := rlp.EncodeToBytes(val) - if err != nil { - return err - } - batch.Put(f.key, b) - return nil -} diff --git a/swarm/shed/field_struct_test.go b/swarm/shed/field_struct_test.go deleted file mode 100644 index cc0be01863fc..000000000000 --- a/swarm/shed/field_struct_test.go +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package shed - -import ( - "testing" - - "github.com/syndtr/goleveldb/leveldb" -) - -// TestStructField validates put and get operations -// of the StructField. -func TestStructField(t *testing.T) { - db, cleanupFunc := newTestDB(t) - defer cleanupFunc() - - complexField, err := db.NewStructField("complex-field") - if err != nil { - t.Fatal(err) - } - - type complexStructure struct { - A string - } - - t.Run("get empty", func(t *testing.T) { - var s complexStructure - err := complexField.Get(&s) - if err != leveldb.ErrNotFound { - t.Fatalf("got error %v, want %v", err, leveldb.ErrNotFound) - } - want := "" - if s.A != want { - t.Errorf("got string %q, want %q", s.A, want) - } - }) - - t.Run("put", func(t *testing.T) { - want := complexStructure{ - A: "simple string value", - } - err = complexField.Put(want) - if err != nil { - t.Fatal(err) - } - var got complexStructure - err = complexField.Get(&got) - if err != nil { - t.Fatal(err) - } - if got.A != want.A { - t.Errorf("got string %q, want %q", got.A, want.A) - } - - t.Run("overwrite", func(t *testing.T) { - want := complexStructure{ - A: "overwritten string value", - } - err = complexField.Put(want) - if err != nil { - t.Fatal(err) - } - var got complexStructure - err = complexField.Get(&got) - if err != nil { - t.Fatal(err) - } - if got.A != want.A { - t.Errorf("got string %q, want %q", got.A, want.A) - } - }) - }) - - t.Run("put in batch", func(t *testing.T) { - batch := new(leveldb.Batch) - want := complexStructure{ - A: "simple string batch value", - } - complexField.PutInBatch(batch, want) - err = db.WriteBatch(batch) - if err != nil { - t.Fatal(err) - } - var got complexStructure - err := complexField.Get(&got) - if err != nil { - t.Fatal(err) - } - if got.A != want.A { - t.Errorf("got string %q, want %q", got, want) - } - - t.Run("overwrite", func(t *testing.T) { - batch := new(leveldb.Batch) - want := complexStructure{ - A: "overwritten string batch value", - } - complexField.PutInBatch(batch, want) - err = db.WriteBatch(batch) - if err != nil { - t.Fatal(err) - } - var got complexStructure - err := complexField.Get(&got) - if err != nil { - t.Fatal(err) - } - if got.A != want.A { - t.Errorf("got string %q, want %q", got, want) - } - }) - }) -} diff --git a/swarm/shed/field_uint64.go b/swarm/shed/field_uint64.go deleted file mode 100644 index 0417583ac315..000000000000 --- a/swarm/shed/field_uint64.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package shed - -import ( - "encoding/binary" - - "github.com/syndtr/goleveldb/leveldb" -) - -// Uint64Field provides a way to have a simple counter in the database. -// It transparently encodes uint64 type value to bytes. -type Uint64Field struct { - db *DB - key []byte -} - -// NewUint64Field returns a new Uint64Field. -// It validates its name and type against the database schema. -func (db *DB) NewUint64Field(name string) (f Uint64Field, err error) { - key, err := db.schemaFieldKey(name, "uint64") - if err != nil { - return f, err - } - return Uint64Field{ - db: db, - key: key, - }, nil -} - -// Get retrieves a uint64 value from the database. -// If the value is not found in the database a 0 value -// is returned and no error. -func (f Uint64Field) Get() (val uint64, err error) { - b, err := f.db.Get(f.key) - if err != nil { - if err == leveldb.ErrNotFound { - return 0, nil - } - return 0, err - } - return binary.BigEndian.Uint64(b), nil -} - -// Put encodes uin64 value and stores it in the database. -func (f Uint64Field) Put(val uint64) (err error) { - return f.db.Put(f.key, encodeUint64(val)) -} - -// PutInBatch stores a uint64 value in a batch -// that can be saved later in the database. -func (f Uint64Field) PutInBatch(batch *leveldb.Batch, val uint64) { - batch.Put(f.key, encodeUint64(val)) -} - -// Inc increments a uint64 value in the database. -// This operation is not goroutine save. -func (f Uint64Field) Inc() (val uint64, err error) { - val, err = f.Get() - if err != nil { - if err == leveldb.ErrNotFound { - val = 0 - } else { - return 0, err - } - } - val++ - return val, f.Put(val) -} - -// IncInBatch increments a uint64 value in the batch -// by retreiving a value from the database, not the same batch. -// This operation is not goroutine save. -func (f Uint64Field) IncInBatch(batch *leveldb.Batch) (val uint64, err error) { - val, err = f.Get() - if err != nil { - if err == leveldb.ErrNotFound { - val = 0 - } else { - return 0, err - } - } - val++ - f.PutInBatch(batch, val) - return val, nil -} - -// Dec decrements a uint64 value in the database. -// This operation is not goroutine save. -// The field is protected from overflow to a negative value. -func (f Uint64Field) Dec() (val uint64, err error) { - val, err = f.Get() - if err != nil { - if err == leveldb.ErrNotFound { - val = 0 - } else { - return 0, err - } - } - if val != 0 { - val-- - } - return val, f.Put(val) -} - -// DecInBatch decrements a uint64 value in the batch -// by retreiving a value from the database, not the same batch. -// This operation is not goroutine save. -// The field is protected from overflow to a negative value. -func (f Uint64Field) DecInBatch(batch *leveldb.Batch) (val uint64, err error) { - val, err = f.Get() - if err != nil { - if err == leveldb.ErrNotFound { - val = 0 - } else { - return 0, err - } - } - if val != 0 { - val-- - } - f.PutInBatch(batch, val) - return val, nil -} - -// encode transforms uint64 to 8 byte long -// slice in big endian encoding. -func encodeUint64(val uint64) (b []byte) { - b = make([]byte, 8) - binary.BigEndian.PutUint64(b, val) - return b -} diff --git a/swarm/shed/field_uint64_test.go b/swarm/shed/field_uint64_test.go deleted file mode 100644 index 9462b56dd184..000000000000 --- a/swarm/shed/field_uint64_test.go +++ /dev/null @@ -1,300 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package shed - -import ( - "testing" - - "github.com/syndtr/goleveldb/leveldb" -) - -// TestUint64Field validates put and get operations -// of the Uint64Field. -func TestUint64Field(t *testing.T) { - db, cleanupFunc := newTestDB(t) - defer cleanupFunc() - - counter, err := db.NewUint64Field("counter") - if err != nil { - t.Fatal(err) - } - - t.Run("get empty", func(t *testing.T) { - got, err := counter.Get() - if err != nil { - t.Fatal(err) - } - var want uint64 - if got != want { - t.Errorf("got uint64 %v, want %v", got, want) - } - }) - - t.Run("put", func(t *testing.T) { - var want uint64 = 42 - err = counter.Put(want) - if err != nil { - t.Fatal(err) - } - got, err := counter.Get() - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("got uint64 %v, want %v", got, want) - } - - t.Run("overwrite", func(t *testing.T) { - var want uint64 = 84 - err = counter.Put(want) - if err != nil { - t.Fatal(err) - } - got, err := counter.Get() - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("got uint64 %v, want %v", got, want) - } - }) - }) - - t.Run("put in batch", func(t *testing.T) { - batch := new(leveldb.Batch) - var want uint64 = 42 - counter.PutInBatch(batch, want) - err = db.WriteBatch(batch) - if err != nil { - t.Fatal(err) - } - got, err := counter.Get() - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("got uint64 %v, want %v", got, want) - } - - t.Run("overwrite", func(t *testing.T) { - batch := new(leveldb.Batch) - var want uint64 = 84 - counter.PutInBatch(batch, want) - err = db.WriteBatch(batch) - if err != nil { - t.Fatal(err) - } - got, err := counter.Get() - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("got uint64 %v, want %v", got, want) - } - }) - }) -} - -// TestUint64Field_Inc validates Inc operation -// of the Uint64Field. -func TestUint64Field_Inc(t *testing.T) { - db, cleanupFunc := newTestDB(t) - defer cleanupFunc() - - counter, err := db.NewUint64Field("counter") - if err != nil { - t.Fatal(err) - } - - var want uint64 = 1 - got, err := counter.Inc() - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("got uint64 %v, want %v", got, want) - } - - want = 2 - got, err = counter.Inc() - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("got uint64 %v, want %v", got, want) - } -} - -// TestUint64Field_IncInBatch validates IncInBatch operation -// of the Uint64Field. -func TestUint64Field_IncInBatch(t *testing.T) { - db, cleanupFunc := newTestDB(t) - defer cleanupFunc() - - counter, err := db.NewUint64Field("counter") - if err != nil { - t.Fatal(err) - } - - batch := new(leveldb.Batch) - var want uint64 = 1 - got, err := counter.IncInBatch(batch) - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("got uint64 %v, want %v", got, want) - } - err = db.WriteBatch(batch) - if err != nil { - t.Fatal(err) - } - got, err = counter.Get() - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("got uint64 %v, want %v", got, want) - } - - batch2 := new(leveldb.Batch) - want = 2 - got, err = counter.IncInBatch(batch2) - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("got uint64 %v, want %v", got, want) - } - err = db.WriteBatch(batch2) - if err != nil { - t.Fatal(err) - } - got, err = counter.Get() - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("got uint64 %v, want %v", got, want) - } -} - -// TestUint64Field_Dec validates Dec operation -// of the Uint64Field. -func TestUint64Field_Dec(t *testing.T) { - db, cleanupFunc := newTestDB(t) - defer cleanupFunc() - - counter, err := db.NewUint64Field("counter") - if err != nil { - t.Fatal(err) - } - - // test overflow protection - var want uint64 - got, err := counter.Dec() - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("got uint64 %v, want %v", got, want) - } - - want = 32 - err = counter.Put(want) - if err != nil { - t.Fatal(err) - } - - want = 31 - got, err = counter.Dec() - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("got uint64 %v, want %v", got, want) - } -} - -// TestUint64Field_DecInBatch validates DecInBatch operation -// of the Uint64Field. -func TestUint64Field_DecInBatch(t *testing.T) { - db, cleanupFunc := newTestDB(t) - defer cleanupFunc() - - counter, err := db.NewUint64Field("counter") - if err != nil { - t.Fatal(err) - } - - batch := new(leveldb.Batch) - var want uint64 - got, err := counter.DecInBatch(batch) - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("got uint64 %v, want %v", got, want) - } - err = db.WriteBatch(batch) - if err != nil { - t.Fatal(err) - } - got, err = counter.Get() - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("got uint64 %v, want %v", got, want) - } - - batch2 := new(leveldb.Batch) - want = 42 - counter.PutInBatch(batch2, want) - err = db.WriteBatch(batch2) - if err != nil { - t.Fatal(err) - } - got, err = counter.Get() - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("got uint64 %v, want %v", got, want) - } - - batch3 := new(leveldb.Batch) - want = 41 - got, err = counter.DecInBatch(batch3) - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("got uint64 %v, want %v", got, want) - } - err = db.WriteBatch(batch3) - if err != nil { - t.Fatal(err) - } - got, err = counter.Get() - if err != nil { - t.Fatal(err) - } - if got != want { - t.Errorf("got uint64 %v, want %v", got, want) - } -} diff --git a/swarm/shed/index.go b/swarm/shed/index.go deleted file mode 100644 index df88b1b62dc3..000000000000 --- a/swarm/shed/index.go +++ /dev/null @@ -1,306 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package shed - -import ( - "bytes" - - "github.com/syndtr/goleveldb/leveldb" -) - -// Item holds fields relevant to Swarm Chunk data and metadata. -// All information required for swarm storage and operations -// on that storage must be defined here. -// This structure is logically connected to swarm storage, -// the only part of this package that is not generalized, -// mostly for performance reasons. -// -// Item is a type that is used for retrieving, storing and encoding -// chunk data and metadata. It is passed as an argument to Index encoding -// functions, get function and put function. -// But it is also returned with additional data from get function call -// and as the argument in iterator function definition. -type Item struct { - Address []byte - Data []byte - AccessTimestamp int64 - StoreTimestamp int64 - // UseMockStore is a pointer to identify - // an unset state of the field in Join function. - UseMockStore *bool -} - -// Merge is a helper method to construct a new -// Item by filling up fields with default values -// of a particular Item with values from another one. -func (i Item) Merge(i2 Item) (new Item) { - if i.Address == nil { - i.Address = i2.Address - } - if i.Data == nil { - i.Data = i2.Data - } - if i.AccessTimestamp == 0 { - i.AccessTimestamp = i2.AccessTimestamp - } - if i.StoreTimestamp == 0 { - i.StoreTimestamp = i2.StoreTimestamp - } - if i.UseMockStore == nil { - i.UseMockStore = i2.UseMockStore - } - return i -} - -// Index represents a set of LevelDB key value pairs that have common -// prefix. It holds functions for encoding and decoding keys and values -// to provide transparent actions on saved data which inclide: -// - getting a particular Item -// - saving a particular Item -// - iterating over a sorted LevelDB keys -// It implements IndexIteratorInterface interface. -type Index struct { - db *DB - prefix []byte - encodeKeyFunc func(fields Item) (key []byte, err error) - decodeKeyFunc func(key []byte) (e Item, err error) - encodeValueFunc func(fields Item) (value []byte, err error) - decodeValueFunc func(keyFields Item, value []byte) (e Item, err error) -} - -// IndexFuncs structure defines functions for encoding and decoding -// LevelDB keys and values for a specific index. -type IndexFuncs struct { - EncodeKey func(fields Item) (key []byte, err error) - DecodeKey func(key []byte) (e Item, err error) - EncodeValue func(fields Item) (value []byte, err error) - DecodeValue func(keyFields Item, value []byte) (e Item, err error) -} - -// NewIndex returns a new Index instance with defined name and -// encoding functions. The name must be unique and will be validated -// on database schema for a key prefix byte. -func (db *DB) NewIndex(name string, funcs IndexFuncs) (f Index, err error) { - id, err := db.schemaIndexPrefix(name) - if err != nil { - return f, err - } - prefix := []byte{id} - return Index{ - db: db, - prefix: prefix, - // This function adjusts Index LevelDB key - // by appending the provided index id byte. - // This is needed to avoid collisions between keys of different - // indexes as all index ids are unique. - encodeKeyFunc: func(e Item) (key []byte, err error) { - key, err = funcs.EncodeKey(e) - if err != nil { - return nil, err - } - return append(append(make([]byte, 0, len(key)+1), prefix...), key...), nil - }, - // This function reverses the encodeKeyFunc constructed key - // to transparently work with index keys without their index ids. - // It assumes that index keys are prefixed with only one byte. - decodeKeyFunc: func(key []byte) (e Item, err error) { - return funcs.DecodeKey(key[1:]) - }, - encodeValueFunc: funcs.EncodeValue, - decodeValueFunc: funcs.DecodeValue, - }, nil -} - -// Get accepts key fields represented as Item to retrieve a -// value from the index and return maximum available information -// from the index represented as another Item. -func (f Index) Get(keyFields Item) (out Item, err error) { - key, err := f.encodeKeyFunc(keyFields) - if err != nil { - return out, err - } - value, err := f.db.Get(key) - if err != nil { - return out, err - } - out, err = f.decodeValueFunc(keyFields, value) - if err != nil { - return out, err - } - return out.Merge(keyFields), nil -} - -// Put accepts Item to encode information from it -// and save it to the database. -func (f Index) Put(i Item) (err error) { - key, err := f.encodeKeyFunc(i) - if err != nil { - return err - } - value, err := f.encodeValueFunc(i) - if err != nil { - return err - } - return f.db.Put(key, value) -} - -// PutInBatch is the same as Put method, but it just -// saves the key/value pair to the batch instead -// directly to the database. -func (f Index) PutInBatch(batch *leveldb.Batch, i Item) (err error) { - key, err := f.encodeKeyFunc(i) - if err != nil { - return err - } - value, err := f.encodeValueFunc(i) - if err != nil { - return err - } - batch.Put(key, value) - return nil -} - -// Delete accepts Item to remove a key/value pair -// from the database based on its fields. -func (f Index) Delete(keyFields Item) (err error) { - key, err := f.encodeKeyFunc(keyFields) - if err != nil { - return err - } - return f.db.Delete(key) -} - -// DeleteInBatch is the same as Delete just the operation -// is performed on the batch instead on the database. -func (f Index) DeleteInBatch(batch *leveldb.Batch, keyFields Item) (err error) { - key, err := f.encodeKeyFunc(keyFields) - if err != nil { - return err - } - batch.Delete(key) - return nil -} - -// IndexIterFunc is a callback on every Item that is decoded -// by iterating on an Index keys. -// By returning a true for stop variable, iteration will -// stop, and by returning the error, that error will be -// propagated to the called iterator method on Index. -type IndexIterFunc func(item Item) (stop bool, err error) - -// IterateOptions defines optional parameters for Iterate function. -type IterateOptions struct { - // StartFrom is the Item to start the iteration from. - StartFrom *Item - // If SkipStartFromItem is true, StartFrom item will not - // be iterated on. - SkipStartFromItem bool - // Iterate over items which keys have a common prefix. - Prefix []byte -} - -// Iterate function iterates over keys of the Index. -// If IterateOptions is nil, the iterations is over all keys. -func (f Index) Iterate(fn IndexIterFunc, options *IterateOptions) (err error) { - if options == nil { - options = new(IterateOptions) - } - // construct a prefix with Index prefix and optional common key prefix - prefix := append(f.prefix, options.Prefix...) - // start from the prefix - startKey := prefix - if options.StartFrom != nil { - // start from the provided StartFrom Item key value - startKey, err = f.encodeKeyFunc(*options.StartFrom) - if err != nil { - return err - } - } - it := f.db.NewIterator() - defer it.Release() - - // move the cursor to the start key - ok := it.Seek(startKey) - if !ok { - // stop iterator if seek has failed - return it.Error() - } - if options.SkipStartFromItem && bytes.Equal(startKey, it.Key()) { - // skip the start from Item if it is the first key - // and it is explicitly configured to skip it - ok = it.Next() - } - for ; ok; ok = it.Next() { - key := it.Key() - if !bytes.HasPrefix(key, prefix) { - break - } - // create a copy of key byte slice not to share leveldb underlaying slice array - keyItem, err := f.decodeKeyFunc(append([]byte(nil), key...)) - if err != nil { - return err - } - // create a copy of value byte slice not to share leveldb underlaying slice array - valueItem, err := f.decodeValueFunc(keyItem, append([]byte(nil), it.Value()...)) - if err != nil { - return err - } - stop, err := fn(keyItem.Merge(valueItem)) - if err != nil { - return err - } - if stop { - break - } - } - return it.Error() -} - -// Count returns the number of items in index. -func (f Index) Count() (count int, err error) { - it := f.db.NewIterator() - defer it.Release() - - for ok := it.Seek(f.prefix); ok; ok = it.Next() { - key := it.Key() - if key[0] != f.prefix[0] { - break - } - count++ - } - return count, it.Error() -} - -// CountFrom returns the number of items in index keys -// starting from the key encoded from the provided Item. -func (f Index) CountFrom(start Item) (count int, err error) { - startKey, err := f.encodeKeyFunc(start) - if err != nil { - return 0, err - } - it := f.db.NewIterator() - defer it.Release() - - for ok := it.Seek(startKey); ok; ok = it.Next() { - key := it.Key() - if key[0] != f.prefix[0] { - break - } - count++ - } - return count, it.Error() -} diff --git a/swarm/shed/index_test.go b/swarm/shed/index_test.go deleted file mode 100644 index 97d7c91f439f..000000000000 --- a/swarm/shed/index_test.go +++ /dev/null @@ -1,781 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package shed - -import ( - "bytes" - "encoding/binary" - "fmt" - "sort" - "testing" - "time" - - "github.com/syndtr/goleveldb/leveldb" -) - -// Index functions for the index that is used in tests in this file. -var retrievalIndexFuncs = IndexFuncs{ - EncodeKey: func(fields Item) (key []byte, err error) { - return fields.Address, nil - }, - DecodeKey: func(key []byte) (e Item, err error) { - e.Address = key - return e, nil - }, - EncodeValue: func(fields Item) (value []byte, err error) { - b := make([]byte, 8) - binary.BigEndian.PutUint64(b, uint64(fields.StoreTimestamp)) - value = append(b, fields.Data...) - return value, nil - }, - DecodeValue: func(keyItem Item, value []byte) (e Item, err error) { - e.StoreTimestamp = int64(binary.BigEndian.Uint64(value[:8])) - e.Data = value[8:] - return e, nil - }, -} - -// TestIndex validates put, get and delete functions of the Index implementation. -func TestIndex(t *testing.T) { - db, cleanupFunc := newTestDB(t) - defer cleanupFunc() - - index, err := db.NewIndex("retrieval", retrievalIndexFuncs) - if err != nil { - t.Fatal(err) - } - - t.Run("put", func(t *testing.T) { - want := Item{ - Address: []byte("put-hash"), - Data: []byte("DATA"), - StoreTimestamp: time.Now().UTC().UnixNano(), - } - - err := index.Put(want) - if err != nil { - t.Fatal(err) - } - got, err := index.Get(Item{ - Address: want.Address, - }) - if err != nil { - t.Fatal(err) - } - checkItem(t, got, want) - - t.Run("overwrite", func(t *testing.T) { - want := Item{ - Address: []byte("put-hash"), - Data: []byte("New DATA"), - StoreTimestamp: time.Now().UTC().UnixNano(), - } - - err = index.Put(want) - if err != nil { - t.Fatal(err) - } - got, err := index.Get(Item{ - Address: want.Address, - }) - if err != nil { - t.Fatal(err) - } - checkItem(t, got, want) - }) - }) - - t.Run("put in batch", func(t *testing.T) { - want := Item{ - Address: []byte("put-in-batch-hash"), - Data: []byte("DATA"), - StoreTimestamp: time.Now().UTC().UnixNano(), - } - - batch := new(leveldb.Batch) - index.PutInBatch(batch, want) - err := db.WriteBatch(batch) - if err != nil { - t.Fatal(err) - } - got, err := index.Get(Item{ - Address: want.Address, - }) - if err != nil { - t.Fatal(err) - } - checkItem(t, got, want) - - t.Run("overwrite", func(t *testing.T) { - want := Item{ - Address: []byte("put-in-batch-hash"), - Data: []byte("New DATA"), - StoreTimestamp: time.Now().UTC().UnixNano(), - } - - batch := new(leveldb.Batch) - index.PutInBatch(batch, want) - db.WriteBatch(batch) - if err != nil { - t.Fatal(err) - } - got, err := index.Get(Item{ - Address: want.Address, - }) - if err != nil { - t.Fatal(err) - } - checkItem(t, got, want) - }) - }) - - t.Run("put in batch twice", func(t *testing.T) { - // ensure that the last item of items with the same db keys - // is actually saved - batch := new(leveldb.Batch) - address := []byte("put-in-batch-twice-hash") - - // put the first item - index.PutInBatch(batch, Item{ - Address: address, - Data: []byte("DATA"), - StoreTimestamp: time.Now().UTC().UnixNano(), - }) - - want := Item{ - Address: address, - Data: []byte("New DATA"), - StoreTimestamp: time.Now().UTC().UnixNano(), - } - // then put the item that will produce the same key - // but different value in the database - index.PutInBatch(batch, want) - db.WriteBatch(batch) - if err != nil { - t.Fatal(err) - } - got, err := index.Get(Item{ - Address: address, - }) - if err != nil { - t.Fatal(err) - } - checkItem(t, got, want) - }) - - t.Run("delete", func(t *testing.T) { - want := Item{ - Address: []byte("delete-hash"), - Data: []byte("DATA"), - StoreTimestamp: time.Now().UTC().UnixNano(), - } - - err := index.Put(want) - if err != nil { - t.Fatal(err) - } - got, err := index.Get(Item{ - Address: want.Address, - }) - if err != nil { - t.Fatal(err) - } - checkItem(t, got, want) - - err = index.Delete(Item{ - Address: want.Address, - }) - if err != nil { - t.Fatal(err) - } - - wantErr := leveldb.ErrNotFound - got, err = index.Get(Item{ - Address: want.Address, - }) - if err != wantErr { - t.Fatalf("got error %v, want %v", err, wantErr) - } - }) - - t.Run("delete in batch", func(t *testing.T) { - want := Item{ - Address: []byte("delete-in-batch-hash"), - Data: []byte("DATA"), - StoreTimestamp: time.Now().UTC().UnixNano(), - } - - err := index.Put(want) - if err != nil { - t.Fatal(err) - } - got, err := index.Get(Item{ - Address: want.Address, - }) - if err != nil { - t.Fatal(err) - } - checkItem(t, got, want) - - batch := new(leveldb.Batch) - index.DeleteInBatch(batch, Item{ - Address: want.Address, - }) - err = db.WriteBatch(batch) - if err != nil { - t.Fatal(err) - } - - wantErr := leveldb.ErrNotFound - got, err = index.Get(Item{ - Address: want.Address, - }) - if err != wantErr { - t.Fatalf("got error %v, want %v", err, wantErr) - } - }) -} - -// TestIndex_Iterate validates index Iterate -// functions for correctness. -func TestIndex_Iterate(t *testing.T) { - db, cleanupFunc := newTestDB(t) - defer cleanupFunc() - - index, err := db.NewIndex("retrieval", retrievalIndexFuncs) - if err != nil { - t.Fatal(err) - } - - items := []Item{ - { - Address: []byte("iterate-hash-01"), - Data: []byte("data80"), - }, - { - Address: []byte("iterate-hash-03"), - Data: []byte("data22"), - }, - { - Address: []byte("iterate-hash-05"), - Data: []byte("data41"), - }, - { - Address: []byte("iterate-hash-02"), - Data: []byte("data84"), - }, - { - Address: []byte("iterate-hash-06"), - Data: []byte("data1"), - }, - } - batch := new(leveldb.Batch) - for _, i := range items { - index.PutInBatch(batch, i) - } - err = db.WriteBatch(batch) - if err != nil { - t.Fatal(err) - } - item04 := Item{ - Address: []byte("iterate-hash-04"), - Data: []byte("data0"), - } - err = index.Put(item04) - if err != nil { - t.Fatal(err) - } - items = append(items, item04) - - sort.SliceStable(items, func(i, j int) bool { - return bytes.Compare(items[i].Address, items[j].Address) < 0 - }) - - t.Run("all", func(t *testing.T) { - var i int - err := index.Iterate(func(item Item) (stop bool, err error) { - if i > len(items)-1 { - return true, fmt.Errorf("got unexpected index item: %#v", item) - } - want := items[i] - checkItem(t, item, want) - i++ - return false, nil - }, nil) - if err != nil { - t.Fatal(err) - } - }) - - t.Run("start from", func(t *testing.T) { - startIndex := 2 - i := startIndex - err := index.Iterate(func(item Item) (stop bool, err error) { - if i > len(items)-1 { - return true, fmt.Errorf("got unexpected index item: %#v", item) - } - want := items[i] - checkItem(t, item, want) - i++ - return false, nil - }, &IterateOptions{ - StartFrom: &items[startIndex], - }) - if err != nil { - t.Fatal(err) - } - }) - - t.Run("skip start from", func(t *testing.T) { - startIndex := 2 - i := startIndex + 1 - err := index.Iterate(func(item Item) (stop bool, err error) { - if i > len(items)-1 { - return true, fmt.Errorf("got unexpected index item: %#v", item) - } - want := items[i] - checkItem(t, item, want) - i++ - return false, nil - }, &IterateOptions{ - StartFrom: &items[startIndex], - SkipStartFromItem: true, - }) - if err != nil { - t.Fatal(err) - } - }) - - t.Run("stop", func(t *testing.T) { - var i int - stopIndex := 3 - var count int - err := index.Iterate(func(item Item) (stop bool, err error) { - if i > len(items)-1 { - return true, fmt.Errorf("got unexpected index item: %#v", item) - } - want := items[i] - checkItem(t, item, want) - count++ - if i == stopIndex { - return true, nil - } - i++ - return false, nil - }, nil) - if err != nil { - t.Fatal(err) - } - wantItemsCount := stopIndex + 1 - if count != wantItemsCount { - t.Errorf("got %v items, expected %v", count, wantItemsCount) - } - }) - - t.Run("no overflow", func(t *testing.T) { - secondIndex, err := db.NewIndex("second-index", retrievalIndexFuncs) - if err != nil { - t.Fatal(err) - } - - secondItem := Item{ - Address: []byte("iterate-hash-10"), - Data: []byte("data-second"), - } - err = secondIndex.Put(secondItem) - if err != nil { - t.Fatal(err) - } - - var i int - err = index.Iterate(func(item Item) (stop bool, err error) { - if i > len(items)-1 { - return true, fmt.Errorf("got unexpected index item: %#v", item) - } - want := items[i] - checkItem(t, item, want) - i++ - return false, nil - }, nil) - if err != nil { - t.Fatal(err) - } - - i = 0 - err = secondIndex.Iterate(func(item Item) (stop bool, err error) { - if i > 1 { - return true, fmt.Errorf("got unexpected index item: %#v", item) - } - checkItem(t, item, secondItem) - i++ - return false, nil - }, nil) - if err != nil { - t.Fatal(err) - } - }) -} - -// TestIndex_Iterate_withPrefix validates index Iterate -// function for correctness. -func TestIndex_Iterate_withPrefix(t *testing.T) { - db, cleanupFunc := newTestDB(t) - defer cleanupFunc() - - index, err := db.NewIndex("retrieval", retrievalIndexFuncs) - if err != nil { - t.Fatal(err) - } - - allItems := []Item{ - {Address: []byte("want-hash-00"), Data: []byte("data80")}, - {Address: []byte("skip-hash-01"), Data: []byte("data81")}, - {Address: []byte("skip-hash-02"), Data: []byte("data82")}, - {Address: []byte("skip-hash-03"), Data: []byte("data83")}, - {Address: []byte("want-hash-04"), Data: []byte("data84")}, - {Address: []byte("want-hash-05"), Data: []byte("data85")}, - {Address: []byte("want-hash-06"), Data: []byte("data86")}, - {Address: []byte("want-hash-07"), Data: []byte("data87")}, - {Address: []byte("want-hash-08"), Data: []byte("data88")}, - {Address: []byte("want-hash-09"), Data: []byte("data89")}, - {Address: []byte("skip-hash-10"), Data: []byte("data90")}, - } - batch := new(leveldb.Batch) - for _, i := range allItems { - index.PutInBatch(batch, i) - } - err = db.WriteBatch(batch) - if err != nil { - t.Fatal(err) - } - - prefix := []byte("want") - - items := make([]Item, 0) - for _, item := range allItems { - if bytes.HasPrefix(item.Address, prefix) { - items = append(items, item) - } - } - sort.SliceStable(items, func(i, j int) bool { - return bytes.Compare(items[i].Address, items[j].Address) < 0 - }) - - t.Run("with prefix", func(t *testing.T) { - var i int - err := index.Iterate(func(item Item) (stop bool, err error) { - if i > len(items)-1 { - return true, fmt.Errorf("got unexpected index item: %#v", item) - } - want := items[i] - checkItem(t, item, want) - i++ - return false, nil - }, &IterateOptions{ - Prefix: prefix, - }) - if err != nil { - t.Fatal(err) - } - if i != len(items) { - t.Errorf("got %v items, want %v", i, len(items)) - } - }) - - t.Run("with prefix and start from", func(t *testing.T) { - startIndex := 2 - var count int - i := startIndex - err := index.Iterate(func(item Item) (stop bool, err error) { - if i > len(items)-1 { - return true, fmt.Errorf("got unexpected index item: %#v", item) - } - want := items[i] - checkItem(t, item, want) - i++ - count++ - return false, nil - }, &IterateOptions{ - StartFrom: &items[startIndex], - Prefix: prefix, - }) - if err != nil { - t.Fatal(err) - } - wantCount := len(items) - startIndex - if count != wantCount { - t.Errorf("got %v items, want %v", count, wantCount) - } - }) - - t.Run("with prefix and skip start from", func(t *testing.T) { - startIndex := 2 - var count int - i := startIndex + 1 - err := index.Iterate(func(item Item) (stop bool, err error) { - if i > len(items)-1 { - return true, fmt.Errorf("got unexpected index item: %#v", item) - } - want := items[i] - checkItem(t, item, want) - i++ - count++ - return false, nil - }, &IterateOptions{ - StartFrom: &items[startIndex], - SkipStartFromItem: true, - Prefix: prefix, - }) - if err != nil { - t.Fatal(err) - } - wantCount := len(items) - startIndex - 1 - if count != wantCount { - t.Errorf("got %v items, want %v", count, wantCount) - } - }) - - t.Run("stop", func(t *testing.T) { - var i int - stopIndex := 3 - var count int - err := index.Iterate(func(item Item) (stop bool, err error) { - if i > len(items)-1 { - return true, fmt.Errorf("got unexpected index item: %#v", item) - } - want := items[i] - checkItem(t, item, want) - count++ - if i == stopIndex { - return true, nil - } - i++ - return false, nil - }, &IterateOptions{ - Prefix: prefix, - }) - if err != nil { - t.Fatal(err) - } - wantItemsCount := stopIndex + 1 - if count != wantItemsCount { - t.Errorf("got %v items, expected %v", count, wantItemsCount) - } - }) - - t.Run("no overflow", func(t *testing.T) { - secondIndex, err := db.NewIndex("second-index", retrievalIndexFuncs) - if err != nil { - t.Fatal(err) - } - - secondItem := Item{ - Address: []byte("iterate-hash-10"), - Data: []byte("data-second"), - } - err = secondIndex.Put(secondItem) - if err != nil { - t.Fatal(err) - } - - var i int - err = index.Iterate(func(item Item) (stop bool, err error) { - if i > len(items)-1 { - return true, fmt.Errorf("got unexpected index item: %#v", item) - } - want := items[i] - checkItem(t, item, want) - i++ - return false, nil - }, &IterateOptions{ - Prefix: prefix, - }) - if err != nil { - t.Fatal(err) - } - if i != len(items) { - t.Errorf("got %v items, want %v", i, len(items)) - } - }) -} - -// TestIndex_count tests if Index.Count and Index.CountFrom -// returns the correct number of items. -func TestIndex_count(t *testing.T) { - db, cleanupFunc := newTestDB(t) - defer cleanupFunc() - - index, err := db.NewIndex("retrieval", retrievalIndexFuncs) - if err != nil { - t.Fatal(err) - } - - items := []Item{ - { - Address: []byte("iterate-hash-01"), - Data: []byte("data80"), - }, - { - Address: []byte("iterate-hash-02"), - Data: []byte("data84"), - }, - { - Address: []byte("iterate-hash-03"), - Data: []byte("data22"), - }, - { - Address: []byte("iterate-hash-04"), - Data: []byte("data41"), - }, - { - Address: []byte("iterate-hash-05"), - Data: []byte("data1"), - }, - } - batch := new(leveldb.Batch) - for _, i := range items { - index.PutInBatch(batch, i) - } - err = db.WriteBatch(batch) - if err != nil { - t.Fatal(err) - } - - t.Run("Count", func(t *testing.T) { - got, err := index.Count() - if err != nil { - t.Fatal(err) - } - - want := len(items) - if got != want { - t.Errorf("got %v items count, want %v", got, want) - } - }) - - t.Run("CountFrom", func(t *testing.T) { - got, err := index.CountFrom(Item{ - Address: items[1].Address, - }) - if err != nil { - t.Fatal(err) - } - - want := len(items) - 1 - if got != want { - t.Errorf("got %v items count, want %v", got, want) - } - }) - - // update the index with another item - t.Run("add item", func(t *testing.T) { - item04 := Item{ - Address: []byte("iterate-hash-06"), - Data: []byte("data0"), - } - err = index.Put(item04) - if err != nil { - t.Fatal(err) - } - - count := len(items) + 1 - - t.Run("Count", func(t *testing.T) { - got, err := index.Count() - if err != nil { - t.Fatal(err) - } - - want := count - if got != want { - t.Errorf("got %v items count, want %v", got, want) - } - }) - - t.Run("CountFrom", func(t *testing.T) { - got, err := index.CountFrom(Item{ - Address: items[1].Address, - }) - if err != nil { - t.Fatal(err) - } - - want := count - 1 - if got != want { - t.Errorf("got %v items count, want %v", got, want) - } - }) - }) - - // delete some items - t.Run("delete items", func(t *testing.T) { - deleteCount := 3 - - for _, item := range items[:deleteCount] { - err := index.Delete(item) - if err != nil { - t.Fatal(err) - } - } - - count := len(items) + 1 - deleteCount - - t.Run("Count", func(t *testing.T) { - got, err := index.Count() - if err != nil { - t.Fatal(err) - } - - want := count - if got != want { - t.Errorf("got %v items count, want %v", got, want) - } - }) - - t.Run("CountFrom", func(t *testing.T) { - got, err := index.CountFrom(Item{ - Address: items[deleteCount+1].Address, - }) - if err != nil { - t.Fatal(err) - } - - want := count - 1 - if got != want { - t.Errorf("got %v items count, want %v", got, want) - } - }) - }) -} - -// checkItem is a test helper function that compares if two Index items are the same. -func checkItem(t *testing.T, got, want Item) { - t.Helper() - - if !bytes.Equal(got.Address, want.Address) { - t.Errorf("got hash %q, expected %q", string(got.Address), string(want.Address)) - } - if !bytes.Equal(got.Data, want.Data) { - t.Errorf("got data %q, expected %q", string(got.Data), string(want.Data)) - } - if got.StoreTimestamp != want.StoreTimestamp { - t.Errorf("got store timestamp %v, expected %v", got.StoreTimestamp, want.StoreTimestamp) - } - if got.AccessTimestamp != want.AccessTimestamp { - t.Errorf("got access timestamp %v, expected %v", got.AccessTimestamp, want.AccessTimestamp) - } -} diff --git a/swarm/shed/schema.go b/swarm/shed/schema.go deleted file mode 100644 index cfb7c6d64269..000000000000 --- a/swarm/shed/schema.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package shed - -import ( - "encoding/json" - "errors" - "fmt" -) - -var ( - // LevelDB key value for storing the schema. - keySchema = []byte{0} - // LevelDB key prefix for all field type. - // LevelDB keys will be constructed by appending name values to this prefix. - keyPrefixFields byte = 1 - // LevelDB key prefix from which indexing keys start. - // Every index has its own key prefix and this value defines the first one. - keyPrefixIndexStart byte = 2 // Q: or maybe a higher number like 7, to have more space for potential specific perfixes -) - -// schema is used to serialize known database structure information. -type schema struct { - Fields map[string]fieldSpec `json:"fields"` // keys are field names - Indexes map[byte]indexSpec `json:"indexes"` // keys are index prefix bytes -} - -// fieldSpec holds information about a particular field. -// It does not need Name field as it is contained in the -// schema.Field map key. -type fieldSpec struct { - Type string `json:"type"` -} - -// indxSpec holds information about a particular index. -// It does not contain index type, as indexes do not have type. -type indexSpec struct { - Name string `json:"name"` -} - -// schemaFieldKey retrives the complete LevelDB key for -// a particular field form the schema definition. -func (db *DB) schemaFieldKey(name, fieldType string) (key []byte, err error) { - if name == "" { - return nil, errors.New("field name can not be blank") - } - if fieldType == "" { - return nil, errors.New("field type can not be blank") - } - s, err := db.getSchema() - if err != nil { - return nil, err - } - var found bool - for n, f := range s.Fields { - if n == name { - if f.Type != fieldType { - return nil, fmt.Errorf("field %q of type %q stored as %q in db", name, fieldType, f.Type) - } - break - } - } - if !found { - s.Fields[name] = fieldSpec{ - Type: fieldType, - } - err := db.putSchema(s) - if err != nil { - return nil, err - } - } - return append([]byte{keyPrefixFields}, []byte(name)...), nil -} - -// schemaIndexID retrieves the complete LevelDB prefix for -// a particular index. -func (db *DB) schemaIndexPrefix(name string) (id byte, err error) { - if name == "" { - return 0, errors.New("index name can not be blank") - } - s, err := db.getSchema() - if err != nil { - return 0, err - } - nextID := keyPrefixIndexStart - for i, f := range s.Indexes { - if i >= nextID { - nextID = i + 1 - } - if f.Name == name { - return i, nil - } - } - id = nextID - s.Indexes[id] = indexSpec{ - Name: name, - } - return id, db.putSchema(s) -} - -// getSchema retrieves the complete schema from -// the database. -func (db *DB) getSchema() (s schema, err error) { - b, err := db.Get(keySchema) - if err != nil { - return s, err - } - err = json.Unmarshal(b, &s) - return s, err -} - -// putSchema stores the complete schema to -// the database. -func (db *DB) putSchema(s schema) (err error) { - b, err := json.Marshal(s) - if err != nil { - return err - } - return db.Put(keySchema, b) -} diff --git a/swarm/shed/schema_test.go b/swarm/shed/schema_test.go deleted file mode 100644 index a0c1838c8bc8..000000000000 --- a/swarm/shed/schema_test.go +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package shed - -import ( - "bytes" - "testing" -) - -// TestDB_schemaFieldKey validates correctness of schemaFieldKey. -func TestDB_schemaFieldKey(t *testing.T) { - db, cleanupFunc := newTestDB(t) - defer cleanupFunc() - - t.Run("empty name or type", func(t *testing.T) { - _, err := db.schemaFieldKey("", "") - if err == nil { - t.Errorf("error not returned, but expected") - } - _, err = db.schemaFieldKey("", "type") - if err == nil { - t.Errorf("error not returned, but expected") - } - - _, err = db.schemaFieldKey("test", "") - if err == nil { - t.Errorf("error not returned, but expected") - } - }) - - t.Run("same field", func(t *testing.T) { - key1, err := db.schemaFieldKey("test", "undefined") - if err != nil { - t.Fatal(err) - } - - key2, err := db.schemaFieldKey("test", "undefined") - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(key1, key2) { - t.Errorf("schema keys for the same field name are not the same: %q, %q", string(key1), string(key2)) - } - }) - - t.Run("different fields", func(t *testing.T) { - key1, err := db.schemaFieldKey("test1", "undefined") - if err != nil { - t.Fatal(err) - } - - key2, err := db.schemaFieldKey("test2", "undefined") - if err != nil { - t.Fatal(err) - } - - if bytes.Equal(key1, key2) { - t.Error("schema keys for the same field name are the same, but must not be") - } - }) - - t.Run("same field name different types", func(t *testing.T) { - _, err := db.schemaFieldKey("the-field", "one-type") - if err != nil { - t.Fatal(err) - } - - _, err = db.schemaFieldKey("the-field", "another-type") - if err == nil { - t.Errorf("error not returned, but expected") - } - }) -} - -// TestDB_schemaIndexPrefix validates correctness of schemaIndexPrefix. -func TestDB_schemaIndexPrefix(t *testing.T) { - db, cleanupFunc := newTestDB(t) - defer cleanupFunc() - - t.Run("same name", func(t *testing.T) { - id1, err := db.schemaIndexPrefix("test") - if err != nil { - t.Fatal(err) - } - - id2, err := db.schemaIndexPrefix("test") - if err != nil { - t.Fatal(err) - } - - if id1 != id2 { - t.Errorf("schema keys for the same field name are not the same: %v, %v", id1, id2) - } - }) - - t.Run("different names", func(t *testing.T) { - id1, err := db.schemaIndexPrefix("test1") - if err != nil { - t.Fatal(err) - } - - id2, err := db.schemaIndexPrefix("test2") - if err != nil { - t.Fatal(err) - } - - if id1 == id2 { - t.Error("schema ids for the same index name are the same, but must not be") - } - }) -} diff --git a/swarm/state/dbstore.go b/swarm/state/dbstore.go deleted file mode 100644 index 1b541e78547b..000000000000 --- a/swarm/state/dbstore.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package state - -import ( - "encoding" - "encoding/json" - "errors" - - "github.com/syndtr/goleveldb/leveldb" - "github.com/syndtr/goleveldb/leveldb/storage" -) - -// ErrNotFound is returned when no results are returned from the database -var ErrNotFound = errors.New("ErrorNotFound") - -// Store defines methods required to get, set, delete values for different keys -// and close the underlying resources. -type Store interface { - Get(key string, i interface{}) (err error) - Put(key string, i interface{}) (err error) - Delete(key string) (err error) - Close() error -} - -// DBStore uses LevelDB to store values. -type DBStore struct { - db *leveldb.DB -} - -// NewDBStore creates a new instance of DBStore. -func NewDBStore(path string) (s *DBStore, err error) { - db, err := leveldb.OpenFile(path, nil) - if err != nil { - return nil, err - } - return &DBStore{ - db: db, - }, nil -} - -// NewInmemoryStore returns a new instance of DBStore. To be used only in tests and simulations. -func NewInmemoryStore() *DBStore { - db, err := leveldb.Open(storage.NewMemStorage(), nil) - if err != nil { - panic(err) - } - return &DBStore{ - db: db, - } -} - -// Get retrieves a persisted value for a specific key. If there is no results -// ErrNotFound is returned. The provided parameter should be either a byte slice or -// a struct that implements the encoding.BinaryUnmarshaler interface -func (s *DBStore) Get(key string, i interface{}) (err error) { - has, err := s.db.Has([]byte(key), nil) - if err != nil || !has { - return ErrNotFound - } - - data, err := s.db.Get([]byte(key), nil) - if err == leveldb.ErrNotFound { - return ErrNotFound - } - - unmarshaler, ok := i.(encoding.BinaryUnmarshaler) - if !ok { - return json.Unmarshal(data, i) - } - return unmarshaler.UnmarshalBinary(data) -} - -// Put stores an object that implements Binary for a specific key. -func (s *DBStore) Put(key string, i interface{}) (err error) { - var bytes []byte - if marshaler, ok := i.(encoding.BinaryMarshaler); ok { - if bytes, err = marshaler.MarshalBinary(); err != nil { - return err - } - } else { - if bytes, err = json.Marshal(i); err != nil { - return err - } - } - return s.db.Put([]byte(key), bytes, nil) -} - -// Delete removes entries stored under a specific key. -func (s *DBStore) Delete(key string) (err error) { - return s.db.Delete([]byte(key), nil) -} - -// Close releases the resources used by the underlying LevelDB. -func (s *DBStore) Close() error { - return s.db.Close() -} diff --git a/swarm/state/dbstore_test.go b/swarm/state/dbstore_test.go deleted file mode 100644 index f7098956d038..000000000000 --- a/swarm/state/dbstore_test.go +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package state - -import ( - "bytes" - "errors" - "io/ioutil" - "os" - "strings" - "testing" -) - -var ErrInvalidArraySize = errors.New("invalid byte array size") -var ErrInvalidValuePersisted = errors.New("invalid value was persisted to the db") - -type SerializingType struct { - key string - value string -} - -func (st *SerializingType) MarshalBinary() (data []byte, err error) { - d := []byte(strings.Join([]string{st.key, st.value}, ";")) - - return d, nil -} - -func (st *SerializingType) UnmarshalBinary(data []byte) (err error) { - d := bytes.Split(data, []byte(";")) - l := len(d) - if l == 0 { - return ErrInvalidArraySize - } - if l == 2 { - keyLen := len(d[0]) - st.key = string(d[0][:keyLen]) - - valLen := len(d[1]) - st.value = string(d[1][:valLen]) - } - - return nil -} - -// TestDBStore tests basic functionality of DBStore. -func TestDBStore(t *testing.T) { - dir, err := ioutil.TempDir("", "db_store_test") - if err != nil { - panic(err) - } - defer os.RemoveAll(dir) - - store, err := NewDBStore(dir) - if err != nil { - t.Fatal(err) - } - - testStore(t, store) - - store.Close() - - persistedStore, err := NewDBStore(dir) - if err != nil { - t.Fatal(err) - } - defer persistedStore.Close() - - testPersistedStore(t, persistedStore) -} - -func testStore(t *testing.T, store Store) { - ser := &SerializingType{key: "key1", value: "value1"} - jsonify := []string{"a", "b", "c"} - - err := store.Put(ser.key, ser) - if err != nil { - t.Fatal(err) - } - - err = store.Put("key2", jsonify) - if err != nil { - t.Fatal(err) - } - -} - -func testPersistedStore(t *testing.T, store Store) { - ser := &SerializingType{} - - err := store.Get("key1", ser) - if err != nil { - t.Fatal(err) - } - - if ser.key != "key1" || ser.value != "value1" { - t.Fatal(ErrInvalidValuePersisted) - } - - as := []string{} - err = store.Get("key2", &as) - if err != nil { - t.Fatal(err) - } - - if len(as) != 3 { - t.Fatalf("serialized array did not match expectation") - } - if as[0] != "a" || as[1] != "b" || as[2] != "c" { - t.Fatalf("elements serialized did not match expected values") - } -} diff --git a/swarm/storage/chunker.go b/swarm/storage/chunker.go deleted file mode 100644 index 44d3f74983d0..000000000000 --- a/swarm/storage/chunker.go +++ /dev/null @@ -1,613 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . -package storage - -import ( - "context" - "encoding/binary" - "errors" - "fmt" - "io" - "sync" - "time" - - "github.com/ubiq/go-ubiq/metrics" - ch "github.com/ubiq/go-ubiq/swarm/chunk" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/spancontext" - opentracing "github.com/opentracing/opentracing-go" - olog "github.com/opentracing/opentracing-go/log" -) - -/* -The distributed storage implemented in this package requires fix sized chunks of content. - -Chunker is the interface to a component that is responsible for disassembling and assembling larger data. - -TreeChunker implements a Chunker based on a tree structure defined as follows: - -1 each node in the tree including the root and other branching nodes are stored as a chunk. - -2 branching nodes encode data contents that includes the size of the dataslice covered by its entire subtree under the node as well as the hash keys of all its children : -data_{i} := size(subtree_{i}) || key_{j} || key_{j+1} .... || key_{j+n-1} - -3 Leaf nodes encode an actual subslice of the input data. - -4 if data size is not more than maximum chunksize, the data is stored in a single chunk - key = hash(int64(size) + data) - -5 if data size is more than chunksize*branches^l, but no more than chunksize* - branches^(l+1), the data vector is split into slices of chunksize* - branches^l length (except the last one). - key = hash(int64(size) + key(slice0) + key(slice1) + ...) - - The underlying hash function is configurable -*/ - -/* -Tree chunker is a concrete implementation of data chunking. -This chunker works in a simple way, it builds a tree out of the document so that each node either represents a chunk of real data or a chunk of data representing an branching non-leaf node of the tree. In particular each such non-leaf chunk will represent is a concatenation of the hash of its respective children. This scheme simultaneously guarantees data integrity as well as self addressing. Abstract nodes are transparent since their represented size component is strictly greater than their maximum data size, since they encode a subtree. - -If all is well it is possible to implement this by simply composing readers so that no extra allocation or buffering is necessary for the data splitting and joining. This means that in principle there can be direct IO between : memory, file system, network socket (bzz peers storage request is read from the socket). In practice there may be need for several stages of internal buffering. -The hashing itself does use extra copies and allocation though, since it does need it. -*/ - -type ChunkerParams struct { - chunkSize int64 - hashSize int64 -} - -type SplitterParams struct { - ChunkerParams - reader io.Reader - putter Putter - addr Address -} - -type TreeSplitterParams struct { - SplitterParams - size int64 -} - -type JoinerParams struct { - ChunkerParams - addr Address - getter Getter - // TODO: there is a bug, so depth can only be 0 today, see: https://github.com/ethersphere/go-ethereum/issues/344 - depth int - ctx context.Context -} - -type TreeChunker struct { - ctx context.Context - - branches int64 - dataSize int64 - data io.Reader - // calculated - addr Address - depth int - hashSize int64 // self.hashFunc.New().Size() - chunkSize int64 // hashSize* branches - workerCount int64 // the number of worker routines used - workerLock sync.RWMutex // lock for the worker count - jobC chan *hashJob - wg *sync.WaitGroup - putter Putter - getter Getter - errC chan error - quitC chan bool -} - -/* - Join reconstructs original content based on a root key. - When joining, the caller gets returned a Lazy SectionReader, which is - seekable and implements on-demand fetching of chunks as and where it is read. - New chunks to retrieve are coming from the getter, which the caller provides. - If an error is encountered during joining, it appears as a reader error. - The SectionReader. - As a result, partial reads from a document are possible even if other parts - are corrupt or lost. - The chunks are not meant to be validated by the chunker when joining. This - is because it is left to the DPA to decide which sources are trusted. -*/ -func TreeJoin(ctx context.Context, addr Address, getter Getter, depth int) *LazyChunkReader { - jp := &JoinerParams{ - ChunkerParams: ChunkerParams{ - chunkSize: ch.DefaultSize, - hashSize: int64(len(addr)), - }, - addr: addr, - getter: getter, - depth: depth, - ctx: ctx, - } - - return NewTreeJoiner(jp).Join(ctx) -} - -/* - When splitting, data is given as a SectionReader, and the key is a hashSize long byte slice (Key), the root hash of the entire content will fill this once processing finishes. - New chunks to store are store using the putter which the caller provides. -*/ -func TreeSplit(ctx context.Context, data io.Reader, size int64, putter Putter) (k Address, wait func(context.Context) error, err error) { - tsp := &TreeSplitterParams{ - SplitterParams: SplitterParams{ - ChunkerParams: ChunkerParams{ - chunkSize: ch.DefaultSize, - hashSize: putter.RefSize(), - }, - reader: data, - putter: putter, - }, - size: size, - } - return NewTreeSplitter(tsp).Split(ctx) -} - -func NewTreeJoiner(params *JoinerParams) *TreeChunker { - tc := &TreeChunker{} - tc.hashSize = params.hashSize - tc.branches = params.chunkSize / params.hashSize - tc.addr = params.addr - tc.getter = params.getter - tc.depth = params.depth - tc.chunkSize = params.chunkSize - tc.workerCount = 0 - tc.jobC = make(chan *hashJob, 2*ChunkProcessors) - tc.wg = &sync.WaitGroup{} - tc.errC = make(chan error) - tc.quitC = make(chan bool) - - tc.ctx = params.ctx - - return tc -} - -func NewTreeSplitter(params *TreeSplitterParams) *TreeChunker { - tc := &TreeChunker{} - tc.data = params.reader - tc.dataSize = params.size - tc.hashSize = params.hashSize - tc.branches = params.chunkSize / params.hashSize - tc.addr = params.addr - tc.chunkSize = params.chunkSize - tc.putter = params.putter - tc.workerCount = 0 - tc.jobC = make(chan *hashJob, 2*ChunkProcessors) - tc.wg = &sync.WaitGroup{} - tc.errC = make(chan error) - tc.quitC = make(chan bool) - - return tc -} - -type hashJob struct { - key Address - chunk []byte - size int64 - parentWg *sync.WaitGroup -} - -func (tc *TreeChunker) incrementWorkerCount() { - tc.workerLock.Lock() - defer tc.workerLock.Unlock() - tc.workerCount += 1 -} - -func (tc *TreeChunker) getWorkerCount() int64 { - tc.workerLock.RLock() - defer tc.workerLock.RUnlock() - return tc.workerCount -} - -func (tc *TreeChunker) decrementWorkerCount() { - tc.workerLock.Lock() - defer tc.workerLock.Unlock() - tc.workerCount -= 1 -} - -func (tc *TreeChunker) Split(ctx context.Context) (k Address, wait func(context.Context) error, err error) { - if tc.chunkSize <= 0 { - panic("chunker must be initialised") - } - - tc.runWorker(ctx) - - depth := 0 - treeSize := tc.chunkSize - - // takes lowest depth such that chunksize*HashCount^(depth+1) > size - // power series, will find the order of magnitude of the data size in base hashCount or numbers of levels of branching in the resulting tree. - for ; treeSize < tc.dataSize; treeSize *= tc.branches { - depth++ - } - - key := make([]byte, tc.hashSize) - // this waitgroup member is released after the root hash is calculated - tc.wg.Add(1) - //launch actual recursive function passing the waitgroups - go tc.split(ctx, depth, treeSize/tc.branches, key, tc.dataSize, tc.wg) - - // closes internal error channel if all subprocesses in the workgroup finished - go func() { - // waiting for all threads to finish - tc.wg.Wait() - close(tc.errC) - }() - - defer close(tc.quitC) - defer tc.putter.Close() - select { - case err := <-tc.errC: - if err != nil { - return nil, nil, err - } - case <-ctx.Done(): - return nil, nil, ctx.Err() - } - - return key, tc.putter.Wait, nil -} - -func (tc *TreeChunker) split(ctx context.Context, depth int, treeSize int64, addr Address, size int64, parentWg *sync.WaitGroup) { - - // - - for depth > 0 && size < treeSize { - treeSize /= tc.branches - depth-- - } - - if depth == 0 { - // leaf nodes -> content chunks - chunkData := make([]byte, size+8) - binary.LittleEndian.PutUint64(chunkData[0:8], uint64(size)) - var readBytes int64 - for readBytes < size { - n, err := tc.data.Read(chunkData[8+readBytes:]) - readBytes += int64(n) - if err != nil && !(err == io.EOF && readBytes == size) { - tc.errC <- err - return - } - } - select { - case tc.jobC <- &hashJob{addr, chunkData, size, parentWg}: - case <-tc.quitC: - } - return - } - // dept > 0 - // intermediate chunk containing child nodes hashes - branchCnt := (size + treeSize - 1) / treeSize - - var chunk = make([]byte, branchCnt*tc.hashSize+8) - var pos, i int64 - - binary.LittleEndian.PutUint64(chunk[0:8], uint64(size)) - - childrenWg := &sync.WaitGroup{} - var secSize int64 - for i < branchCnt { - // the last item can have shorter data - if size-pos < treeSize { - secSize = size - pos - } else { - secSize = treeSize - } - // the hash of that data - subTreeAddress := chunk[8+i*tc.hashSize : 8+(i+1)*tc.hashSize] - - childrenWg.Add(1) - tc.split(ctx, depth-1, treeSize/tc.branches, subTreeAddress, secSize, childrenWg) - - i++ - pos += treeSize - } - // wait for all the children to complete calculating their hashes and copying them onto sections of the chunk - // parentWg.Add(1) - // go func() { - childrenWg.Wait() - - worker := tc.getWorkerCount() - if int64(len(tc.jobC)) > worker && worker < ChunkProcessors { - tc.runWorker(ctx) - - } - select { - case tc.jobC <- &hashJob{addr, chunk, size, parentWg}: - case <-tc.quitC: - } -} - -func (tc *TreeChunker) runWorker(ctx context.Context) { - tc.incrementWorkerCount() - go func() { - defer tc.decrementWorkerCount() - for { - select { - - case job, ok := <-tc.jobC: - if !ok { - return - } - - h, err := tc.putter.Put(ctx, job.chunk) - if err != nil { - tc.errC <- err - return - } - copy(job.key, h) - job.parentWg.Done() - case <-tc.quitC: - return - } - } - }() -} - -// LazyChunkReader implements LazySectionReader -type LazyChunkReader struct { - ctx context.Context - addr Address // root address - chunkData ChunkData - off int64 // offset - chunkSize int64 // inherit from chunker - branches int64 // inherit from chunker - hashSize int64 // inherit from chunker - depth int - getter Getter -} - -func (tc *TreeChunker) Join(ctx context.Context) *LazyChunkReader { - return &LazyChunkReader{ - addr: tc.addr, - chunkSize: tc.chunkSize, - branches: tc.branches, - hashSize: tc.hashSize, - depth: tc.depth, - getter: tc.getter, - ctx: tc.ctx, - } -} - -func (r *LazyChunkReader) Context() context.Context { - return r.ctx -} - -// Size is meant to be called on the LazySectionReader -func (r *LazyChunkReader) Size(ctx context.Context, quitC chan bool) (n int64, err error) { - metrics.GetOrRegisterCounter("lazychunkreader.size", nil).Inc(1) - - var sp opentracing.Span - var cctx context.Context - cctx, sp = spancontext.StartSpan( - ctx, - "lcr.size") - defer sp.Finish() - - log.Debug("lazychunkreader.size", "addr", r.addr) - if r.chunkData == nil { - startTime := time.Now() - chunkData, err := r.getter.Get(cctx, Reference(r.addr)) - if err != nil { - metrics.GetOrRegisterResettingTimer("lcr.getter.get.err", nil).UpdateSince(startTime) - return 0, err - } - metrics.GetOrRegisterResettingTimer("lcr.getter.get", nil).UpdateSince(startTime) - r.chunkData = chunkData - } - - s := r.chunkData.Size() - log.Debug("lazychunkreader.size", "key", r.addr, "size", s) - - return int64(s), nil -} - -// read at can be called numerous times -// concurrent reads are allowed -// Size() needs to be called synchronously on the LazyChunkReader first -func (r *LazyChunkReader) ReadAt(b []byte, off int64) (read int, err error) { - metrics.GetOrRegisterCounter("lazychunkreader.readat", nil).Inc(1) - - var sp opentracing.Span - var cctx context.Context - cctx, sp = spancontext.StartSpan( - r.ctx, - "lcr.read") - defer sp.Finish() - - defer func() { - sp.LogFields( - olog.Int("off", int(off)), - olog.Int("read", read)) - }() - - // this is correct, a swarm doc cannot be zero length, so no EOF is expected - if len(b) == 0 { - return 0, nil - } - quitC := make(chan bool) - size, err := r.Size(cctx, quitC) - if err != nil { - log.Debug("lazychunkreader.readat.size", "size", size, "err", err) - return 0, err - } - - errC := make(chan error) - - // } - var treeSize int64 - var depth int - // calculate depth and max treeSize - treeSize = r.chunkSize - for ; treeSize < size; treeSize *= r.branches { - depth++ - } - wg := sync.WaitGroup{} - length := int64(len(b)) - for d := 0; d < r.depth; d++ { - off *= r.chunkSize - length *= r.chunkSize - } - wg.Add(1) - go r.join(cctx, b, off, off+length, depth, treeSize/r.branches, r.chunkData, &wg, errC, quitC) - go func() { - wg.Wait() - close(errC) - }() - - err = <-errC - if err != nil { - log.Debug("lazychunkreader.readat.errc", "err", err) - close(quitC) - return 0, err - } - if off+int64(len(b)) >= size { - log.Debug("lazychunkreader.readat.return at end", "size", size, "off", off) - return int(size - off), io.EOF - } - log.Debug("lazychunkreader.readat.errc", "buff", len(b)) - return len(b), nil -} - -func (r *LazyChunkReader) join(ctx context.Context, b []byte, off int64, eoff int64, depth int, treeSize int64, chunkData ChunkData, parentWg *sync.WaitGroup, errC chan error, quitC chan bool) { - defer parentWg.Done() - // find appropriate block level - for chunkData.Size() < uint64(treeSize) && depth > r.depth { - treeSize /= r.branches - depth-- - } - - // leaf chunk found - if depth == r.depth { - extra := 8 + eoff - int64(len(chunkData)) - if extra > 0 { - eoff -= extra - } - copy(b, chunkData[8+off:8+eoff]) - return // simply give back the chunks reader for content chunks - } - - // subtree - start := off / treeSize - end := (eoff + treeSize - 1) / treeSize - - // last non-leaf chunk can be shorter than default chunk size, let's not read it further then its end - currentBranches := int64(len(chunkData)-8) / r.hashSize - if end > currentBranches { - end = currentBranches - } - - wg := &sync.WaitGroup{} - defer wg.Wait() - for i := start; i < end; i++ { - soff := i * treeSize - roff := soff - seoff := soff + treeSize - - if soff < off { - soff = off - } - if seoff > eoff { - seoff = eoff - } - if depth > 1 { - wg.Wait() - } - wg.Add(1) - go func(j int64) { - childAddress := chunkData[8+j*r.hashSize : 8+(j+1)*r.hashSize] - startTime := time.Now() - chunkData, err := r.getter.Get(ctx, Reference(childAddress)) - if err != nil { - metrics.GetOrRegisterResettingTimer("lcr.getter.get.err", nil).UpdateSince(startTime) - log.Debug("lazychunkreader.join", "key", fmt.Sprintf("%x", childAddress), "err", err) - select { - case errC <- fmt.Errorf("chunk %v-%v not found; key: %s", off, off+treeSize, fmt.Sprintf("%x", childAddress)): - case <-quitC: - } - return - } - metrics.GetOrRegisterResettingTimer("lcr.getter.get", nil).UpdateSince(startTime) - if l := len(chunkData); l < 9 { - select { - case errC <- fmt.Errorf("chunk %v-%v incomplete; key: %s, data length %v", off, off+treeSize, fmt.Sprintf("%x", childAddress), l): - case <-quitC: - } - return - } - if soff < off { - soff = off - } - r.join(ctx, b[soff-off:seoff-off], soff-roff, seoff-roff, depth-1, treeSize/r.branches, chunkData, wg, errC, quitC) - }(i) - } //for -} - -// Read keeps a cursor so cannot be called simulateously, see ReadAt -func (r *LazyChunkReader) Read(b []byte) (read int, err error) { - log.Debug("lazychunkreader.read", "key", r.addr) - metrics.GetOrRegisterCounter("lazychunkreader.read", nil).Inc(1) - - read, err = r.ReadAt(b, r.off) - if err != nil && err != io.EOF { - log.Debug("lazychunkreader.readat", "read", read, "err", err) - metrics.GetOrRegisterCounter("lazychunkreader.read.err", nil).Inc(1) - } - - metrics.GetOrRegisterCounter("lazychunkreader.read.bytes", nil).Inc(int64(read)) - - r.off += int64(read) - return read, err -} - -// completely analogous to standard SectionReader implementation -var errWhence = errors.New("Seek: invalid whence") -var errOffset = errors.New("Seek: invalid offset") - -func (r *LazyChunkReader) Seek(offset int64, whence int) (int64, error) { - cctx, sp := spancontext.StartSpan( - r.ctx, - "lcr.seek") - defer sp.Finish() - - log.Debug("lazychunkreader.seek", "key", r.addr, "offset", offset) - switch whence { - default: - return 0, errWhence - case 0: - offset += 0 - case 1: - offset += r.off - case 2: - - if r.chunkData == nil { //seek from the end requires rootchunk for size. call Size first - _, err := r.Size(cctx, nil) - if err != nil { - return 0, fmt.Errorf("can't get size: %v", err) - } - } - offset += int64(r.chunkData.Size()) - } - - if offset < 0 { - return 0, errOffset - } - r.off = offset - return offset, nil -} diff --git a/swarm/storage/chunker_test.go b/swarm/storage/chunker_test.go deleted file mode 100644 index b6e4c320d925..000000000000 --- a/swarm/storage/chunker_test.go +++ /dev/null @@ -1,467 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package storage - -import ( - "bytes" - "context" - "encoding/binary" - "fmt" - "io" - "testing" - - "github.com/ubiq/go-ubiq/swarm/testutil" - "golang.org/x/crypto/sha3" -) - -/* -Tests TreeChunker by splitting and joining a random byte slice -*/ - -type test interface { - Fatalf(string, ...interface{}) - Logf(string, ...interface{}) -} - -type chunkerTester struct { - inputs map[uint64][]byte - t test -} - -func newTestHasherStore(store ChunkStore, hash string) *hasherStore { - return NewHasherStore(store, MakeHashFunc(hash), false) -} - -func testRandomBrokenData(n int, tester *chunkerTester) { - data := testutil.RandomReader(1, n) - brokendata := brokenLimitReader(data, n, n/2) - - buf := make([]byte, n) - _, err := brokendata.Read(buf) - if err == nil || err.Error() != "Broken reader" { - tester.t.Fatalf("Broken reader is not broken, hence broken. Returns: %v", err) - } - - data = testutil.RandomReader(2, n) - brokendata = brokenLimitReader(data, n, n/2) - - putGetter := newTestHasherStore(NewMapChunkStore(), SHA3Hash) - - expectedError := fmt.Errorf("Broken reader") - ctx := context.Background() - key, _, err := TreeSplit(ctx, brokendata, int64(n), putGetter) - if err == nil || err.Error() != expectedError.Error() { - tester.t.Fatalf("Not receiving the correct error! Expected %v, received %v", expectedError, err) - } - tester.t.Logf(" Address = %v\n", key) -} - -func testRandomData(usePyramid bool, hash string, n int, tester *chunkerTester) Address { - if tester.inputs == nil { - tester.inputs = make(map[uint64][]byte) - } - input, found := tester.inputs[uint64(n)] - var data io.Reader - if !found { - input = testutil.RandomBytes(1, n) - data = bytes.NewReader(input) - tester.inputs[uint64(n)] = input - } else { - data = io.LimitReader(bytes.NewReader(input), int64(n)) - } - - putGetter := newTestHasherStore(NewMapChunkStore(), hash) - - var addr Address - var wait func(context.Context) error - var err error - ctx := context.TODO() - if usePyramid { - addr, wait, err = PyramidSplit(ctx, data, putGetter, putGetter) - } else { - addr, wait, err = TreeSplit(ctx, data, int64(n), putGetter) - } - if err != nil { - tester.t.Fatalf(err.Error()) - } - tester.t.Logf(" Address = %v\n", addr) - err = wait(ctx) - if err != nil { - tester.t.Fatalf(err.Error()) - } - - reader := TreeJoin(ctx, addr, putGetter, 0) - output := make([]byte, n) - r, err := reader.Read(output) - if r != n || err != io.EOF { - tester.t.Fatalf("read error read: %v n = %v err = %v\n", r, n, err) - } - if input != nil { - if !bytes.Equal(output, input) { - tester.t.Fatalf("input and output mismatch\n IN: %v\nOUT: %v\n", input, output) - } - } - - // testing partial read - for i := 1; i < n; i += 10000 { - readableLength := n - i - r, err := reader.ReadAt(output, int64(i)) - if r != readableLength || err != io.EOF { - tester.t.Fatalf("readAt error with offset %v read: %v n = %v err = %v\n", i, r, readableLength, err) - } - if input != nil { - if !bytes.Equal(output[:readableLength], input[i:]) { - tester.t.Fatalf("input and output mismatch\n IN: %v\nOUT: %v\n", input[i:], output[:readableLength]) - } - } - } - - return addr -} - -func TestSha3ForCorrectness(t *testing.T) { - tester := &chunkerTester{t: t} - - size := 4096 - input := make([]byte, size+8) - binary.LittleEndian.PutUint64(input[:8], uint64(size)) - - io.LimitReader(bytes.NewReader(input[8:]), int64(size)) - - rawSha3 := sha3.NewLegacyKeccak256() - rawSha3.Reset() - rawSha3.Write(input) - rawSha3Output := rawSha3.Sum(nil) - - sha3FromMakeFunc := MakeHashFunc(SHA3Hash)() - sha3FromMakeFunc.ResetWithLength(input[:8]) - sha3FromMakeFunc.Write(input[8:]) - sha3FromMakeFuncOutput := sha3FromMakeFunc.Sum(nil) - - if len(rawSha3Output) != len(sha3FromMakeFuncOutput) { - tester.t.Fatalf("Original SHA3 and abstracted Sha3 has different length %v:%v\n", len(rawSha3Output), len(sha3FromMakeFuncOutput)) - } - - if !bytes.Equal(rawSha3Output, sha3FromMakeFuncOutput) { - tester.t.Fatalf("Original SHA3 and abstracted Sha3 mismatch %v:%v\n", rawSha3Output, sha3FromMakeFuncOutput) - } - -} - -func TestDataAppend(t *testing.T) { - sizes := []int{1, 1, 1, 4095, 4096, 4097, 1, 1, 1, 123456, 2345678, 2345678} - appendSizes := []int{4095, 4096, 4097, 1, 1, 1, 8191, 8192, 8193, 9000, 3000, 5000} - - tester := &chunkerTester{t: t} - for i := range sizes { - n := sizes[i] - m := appendSizes[i] - - if tester.inputs == nil { - tester.inputs = make(map[uint64][]byte) - } - input, found := tester.inputs[uint64(n)] - var data io.Reader - if !found { - input = testutil.RandomBytes(i, n) - data = bytes.NewReader(input) - tester.inputs[uint64(n)] = input - } else { - data = io.LimitReader(bytes.NewReader(input), int64(n)) - } - - store := NewMapChunkStore() - putGetter := newTestHasherStore(store, SHA3Hash) - - ctx := context.TODO() - addr, wait, err := PyramidSplit(ctx, data, putGetter, putGetter) - if err != nil { - tester.t.Fatalf(err.Error()) - } - err = wait(ctx) - if err != nil { - tester.t.Fatalf(err.Error()) - } - //create a append data stream - appendInput, found := tester.inputs[uint64(m)] - var appendData io.Reader - if !found { - appendInput = testutil.RandomBytes(i, m) - appendData = bytes.NewReader(appendInput) - tester.inputs[uint64(m)] = appendInput - } else { - appendData = io.LimitReader(bytes.NewReader(appendInput), int64(m)) - } - - putGetter = newTestHasherStore(store, SHA3Hash) - newAddr, wait, err := PyramidAppend(ctx, addr, appendData, putGetter, putGetter) - if err != nil { - tester.t.Fatalf(err.Error()) - } - err = wait(ctx) - if err != nil { - tester.t.Fatalf(err.Error()) - } - - reader := TreeJoin(ctx, newAddr, putGetter, 0) - newOutput := make([]byte, n+m) - r, err := reader.Read(newOutput) - if r != (n + m) { - tester.t.Fatalf("read error read: %v n = %v m = %v err = %v\n", r, n, m, err) - } - - newInput := append(input, appendInput...) - if !bytes.Equal(newOutput, newInput) { - tester.t.Fatalf("input and output mismatch\n IN: %v\nOUT: %v\n", newInput, newOutput) - } - } -} - -func TestRandomData(t *testing.T) { - // This test can validate files up to a relatively short length, as tree chunker slows down drastically. - // Validation of longer files is done by TestLocalStoreAndRetrieve in swarm package. - //sizes := []int{1, 60, 83, 179, 253, 1024, 4095, 4096, 4097, 8191, 8192, 8193, 12287, 12288, 12289, 524288, 524288 + 1, 524288 + 4097, 7 * 524288, 7*524288 + 1, 7*524288 + 4097} - sizes := []int{1, 60, 83, 179, 253, 1024, 4095, 4097, 8191, 8192, 12288, 12289, 524288} - tester := &chunkerTester{t: t} - - for _, s := range sizes { - treeChunkerAddress := testRandomData(false, SHA3Hash, s, tester) - pyramidChunkerAddress := testRandomData(true, SHA3Hash, s, tester) - if treeChunkerAddress.String() != pyramidChunkerAddress.String() { - tester.t.Fatalf("tree chunker and pyramid chunker key mismatch for size %v\n TC: %v\n PC: %v\n", s, treeChunkerAddress.String(), pyramidChunkerAddress.String()) - } - } - - for _, s := range sizes { - treeChunkerAddress := testRandomData(false, BMTHash, s, tester) - pyramidChunkerAddress := testRandomData(true, BMTHash, s, tester) - if treeChunkerAddress.String() != pyramidChunkerAddress.String() { - tester.t.Fatalf("tree chunker and pyramid chunker key mismatch for size %v\n TC: %v\n PC: %v\n", s, treeChunkerAddress.String(), pyramidChunkerAddress.String()) - } - } -} - -func TestRandomBrokenData(t *testing.T) { - sizes := []int{1, 60, 83, 179, 253, 1024, 4095, 4096, 4097, 8191, 8192, 8193, 12287, 12288, 12289, 123456, 2345678} - tester := &chunkerTester{t: t} - for _, s := range sizes { - testRandomBrokenData(s, tester) - } -} - -func benchReadAll(reader LazySectionReader) { - size, _ := reader.Size(context.TODO(), nil) - output := make([]byte, 1000) - for pos := int64(0); pos < size; pos += 1000 { - reader.ReadAt(output, pos) - } -} - -func benchmarkSplitJoin(n int, t *testing.B) { - t.ReportAllocs() - for i := 0; i < t.N; i++ { - data := testutil.RandomReader(i, n) - - putGetter := newTestHasherStore(NewMapChunkStore(), SHA3Hash) - ctx := context.TODO() - key, wait, err := PyramidSplit(ctx, data, putGetter, putGetter) - if err != nil { - t.Fatalf(err.Error()) - } - err = wait(ctx) - if err != nil { - t.Fatalf(err.Error()) - } - reader := TreeJoin(ctx, key, putGetter, 0) - benchReadAll(reader) - } -} - -func benchmarkSplitTreeSHA3(n int, t *testing.B) { - t.ReportAllocs() - for i := 0; i < t.N; i++ { - data := testutil.RandomReader(i, n) - putGetter := newTestHasherStore(&FakeChunkStore{}, SHA3Hash) - - ctx := context.Background() - _, wait, err := TreeSplit(ctx, data, int64(n), putGetter) - if err != nil { - t.Fatalf(err.Error()) - } - err = wait(ctx) - if err != nil { - t.Fatalf(err.Error()) - } - - } -} - -func benchmarkSplitTreeBMT(n int, t *testing.B) { - t.ReportAllocs() - for i := 0; i < t.N; i++ { - data := testutil.RandomReader(i, n) - putGetter := newTestHasherStore(&FakeChunkStore{}, BMTHash) - - ctx := context.Background() - _, wait, err := TreeSplit(ctx, data, int64(n), putGetter) - if err != nil { - t.Fatalf(err.Error()) - } - err = wait(ctx) - if err != nil { - t.Fatalf(err.Error()) - } - } -} - -func benchmarkSplitPyramidBMT(n int, t *testing.B) { - t.ReportAllocs() - for i := 0; i < t.N; i++ { - data := testutil.RandomReader(i, n) - putGetter := newTestHasherStore(&FakeChunkStore{}, BMTHash) - - ctx := context.Background() - _, wait, err := PyramidSplit(ctx, data, putGetter, putGetter) - if err != nil { - t.Fatalf(err.Error()) - } - err = wait(ctx) - if err != nil { - t.Fatalf(err.Error()) - } - } -} - -func benchmarkSplitPyramidSHA3(n int, t *testing.B) { - t.ReportAllocs() - for i := 0; i < t.N; i++ { - data := testutil.RandomReader(i, n) - putGetter := newTestHasherStore(&FakeChunkStore{}, SHA3Hash) - - ctx := context.Background() - _, wait, err := PyramidSplit(ctx, data, putGetter, putGetter) - if err != nil { - t.Fatalf(err.Error()) - } - err = wait(ctx) - if err != nil { - t.Fatalf(err.Error()) - } - } -} - -func benchmarkSplitAppendPyramid(n, m int, t *testing.B) { - t.ReportAllocs() - for i := 0; i < t.N; i++ { - data := testutil.RandomReader(i, n) - data1 := testutil.RandomReader(t.N+i, m) - - store := NewMapChunkStore() - putGetter := newTestHasherStore(store, SHA3Hash) - - ctx := context.Background() - key, wait, err := PyramidSplit(ctx, data, putGetter, putGetter) - if err != nil { - t.Fatalf(err.Error()) - } - err = wait(ctx) - if err != nil { - t.Fatalf(err.Error()) - } - - putGetter = newTestHasherStore(store, SHA3Hash) - _, wait, err = PyramidAppend(ctx, key, data1, putGetter, putGetter) - if err != nil { - t.Fatalf(err.Error()) - } - err = wait(ctx) - if err != nil { - t.Fatalf(err.Error()) - } - } -} - -func BenchmarkSplitJoin_2(t *testing.B) { benchmarkSplitJoin(100, t) } -func BenchmarkSplitJoin_3(t *testing.B) { benchmarkSplitJoin(1000, t) } -func BenchmarkSplitJoin_4(t *testing.B) { benchmarkSplitJoin(10000, t) } -func BenchmarkSplitJoin_5(t *testing.B) { benchmarkSplitJoin(100000, t) } -func BenchmarkSplitJoin_6(t *testing.B) { benchmarkSplitJoin(1000000, t) } -func BenchmarkSplitJoin_7(t *testing.B) { benchmarkSplitJoin(10000000, t) } - -// func BenchmarkSplitJoin_8(t *testing.B) { benchmarkJoin(100000000, t) } - -func BenchmarkSplitTreeSHA3_2(t *testing.B) { benchmarkSplitTreeSHA3(100, t) } -func BenchmarkSplitTreeSHA3_2h(t *testing.B) { benchmarkSplitTreeSHA3(500, t) } -func BenchmarkSplitTreeSHA3_3(t *testing.B) { benchmarkSplitTreeSHA3(1000, t) } -func BenchmarkSplitTreeSHA3_3h(t *testing.B) { benchmarkSplitTreeSHA3(5000, t) } -func BenchmarkSplitTreeSHA3_4(t *testing.B) { benchmarkSplitTreeSHA3(10000, t) } -func BenchmarkSplitTreeSHA3_4h(t *testing.B) { benchmarkSplitTreeSHA3(50000, t) } -func BenchmarkSplitTreeSHA3_5(t *testing.B) { benchmarkSplitTreeSHA3(100000, t) } -func BenchmarkSplitTreeSHA3_6(t *testing.B) { benchmarkSplitTreeSHA3(1000000, t) } -func BenchmarkSplitTreeSHA3_7(t *testing.B) { benchmarkSplitTreeSHA3(10000000, t) } - -// func BenchmarkSplitTreeSHA3_8(t *testing.B) { benchmarkSplitTreeSHA3(100000000, t) } - -func BenchmarkSplitTreeBMT_2(t *testing.B) { benchmarkSplitTreeBMT(100, t) } -func BenchmarkSplitTreeBMT_2h(t *testing.B) { benchmarkSplitTreeBMT(500, t) } -func BenchmarkSplitTreeBMT_3(t *testing.B) { benchmarkSplitTreeBMT(1000, t) } -func BenchmarkSplitTreeBMT_3h(t *testing.B) { benchmarkSplitTreeBMT(5000, t) } -func BenchmarkSplitTreeBMT_4(t *testing.B) { benchmarkSplitTreeBMT(10000, t) } -func BenchmarkSplitTreeBMT_4h(t *testing.B) { benchmarkSplitTreeBMT(50000, t) } -func BenchmarkSplitTreeBMT_5(t *testing.B) { benchmarkSplitTreeBMT(100000, t) } -func BenchmarkSplitTreeBMT_6(t *testing.B) { benchmarkSplitTreeBMT(1000000, t) } -func BenchmarkSplitTreeBMT_7(t *testing.B) { benchmarkSplitTreeBMT(10000000, t) } - -// func BenchmarkSplitTreeBMT_8(t *testing.B) { benchmarkSplitTreeBMT(100000000, t) } - -func BenchmarkSplitPyramidSHA3_2(t *testing.B) { benchmarkSplitPyramidSHA3(100, t) } -func BenchmarkSplitPyramidSHA3_2h(t *testing.B) { benchmarkSplitPyramidSHA3(500, t) } -func BenchmarkSplitPyramidSHA3_3(t *testing.B) { benchmarkSplitPyramidSHA3(1000, t) } -func BenchmarkSplitPyramidSHA3_3h(t *testing.B) { benchmarkSplitPyramidSHA3(5000, t) } -func BenchmarkSplitPyramidSHA3_4(t *testing.B) { benchmarkSplitPyramidSHA3(10000, t) } -func BenchmarkSplitPyramidSHA3_4h(t *testing.B) { benchmarkSplitPyramidSHA3(50000, t) } -func BenchmarkSplitPyramidSHA3_5(t *testing.B) { benchmarkSplitPyramidSHA3(100000, t) } -func BenchmarkSplitPyramidSHA3_6(t *testing.B) { benchmarkSplitPyramidSHA3(1000000, t) } -func BenchmarkSplitPyramidSHA3_7(t *testing.B) { benchmarkSplitPyramidSHA3(10000000, t) } - -// func BenchmarkSplitPyramidSHA3_8(t *testing.B) { benchmarkSplitPyramidSHA3(100000000, t) } - -func BenchmarkSplitPyramidBMT_2(t *testing.B) { benchmarkSplitPyramidBMT(100, t) } -func BenchmarkSplitPyramidBMT_2h(t *testing.B) { benchmarkSplitPyramidBMT(500, t) } -func BenchmarkSplitPyramidBMT_3(t *testing.B) { benchmarkSplitPyramidBMT(1000, t) } -func BenchmarkSplitPyramidBMT_3h(t *testing.B) { benchmarkSplitPyramidBMT(5000, t) } -func BenchmarkSplitPyramidBMT_4(t *testing.B) { benchmarkSplitPyramidBMT(10000, t) } -func BenchmarkSplitPyramidBMT_4h(t *testing.B) { benchmarkSplitPyramidBMT(50000, t) } -func BenchmarkSplitPyramidBMT_5(t *testing.B) { benchmarkSplitPyramidBMT(100000, t) } -func BenchmarkSplitPyramidBMT_6(t *testing.B) { benchmarkSplitPyramidBMT(1000000, t) } -func BenchmarkSplitPyramidBMT_7(t *testing.B) { benchmarkSplitPyramidBMT(10000000, t) } - -// func BenchmarkSplitPyramidBMT_8(t *testing.B) { benchmarkSplitPyramidBMT(100000000, t) } - -func BenchmarkSplitAppendPyramid_2(t *testing.B) { benchmarkSplitAppendPyramid(100, 1000, t) } -func BenchmarkSplitAppendPyramid_2h(t *testing.B) { benchmarkSplitAppendPyramid(500, 1000, t) } -func BenchmarkSplitAppendPyramid_3(t *testing.B) { benchmarkSplitAppendPyramid(1000, 1000, t) } -func BenchmarkSplitAppendPyramid_4(t *testing.B) { benchmarkSplitAppendPyramid(10000, 1000, t) } -func BenchmarkSplitAppendPyramid_4h(t *testing.B) { benchmarkSplitAppendPyramid(50000, 1000, t) } -func BenchmarkSplitAppendPyramid_5(t *testing.B) { benchmarkSplitAppendPyramid(1000000, 1000, t) } -func BenchmarkSplitAppendPyramid_6(t *testing.B) { benchmarkSplitAppendPyramid(1000000, 1000, t) } -func BenchmarkSplitAppendPyramid_7(t *testing.B) { benchmarkSplitAppendPyramid(10000000, 1000, t) } - -// func BenchmarkAppendPyramid_8(t *testing.B) { benchmarkAppendPyramid(100000000, 1000, t) } - -// go test -timeout 20m -cpu 4 -bench=./swarm/storage -run no -// If you dont add the timeout argument above .. the benchmark will timeout and dump diff --git a/swarm/storage/common_test.go b/swarm/storage/common_test.go deleted file mode 100644 index dab080ce3dc1..000000000000 --- a/swarm/storage/common_test.go +++ /dev/null @@ -1,288 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package storage - -import ( - "bytes" - "context" - "flag" - "fmt" - "io" - "io/ioutil" - "os" - "sync" - "testing" - "time" - - "github.com/ubiq/go-ubiq/log" - ch "github.com/ubiq/go-ubiq/swarm/chunk" - "github.com/mattn/go-colorable" -) - -var ( - loglevel = flag.Int("loglevel", 3, "verbosity of logs") - getTimeout = 30 * time.Second -) - -func init() { - flag.Parse() - log.PrintOrigins(true) - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))) -} - -type brokenLimitedReader struct { - lr io.Reader - errAt int - off int - size int -} - -func brokenLimitReader(data io.Reader, size int, errAt int) *brokenLimitedReader { - return &brokenLimitedReader{ - lr: data, - errAt: errAt, - size: size, - } -} - -func newLDBStore(t *testing.T) (*LDBStore, func()) { - dir, err := ioutil.TempDir("", "bzz-storage-test") - if err != nil { - t.Fatal(err) - } - log.Trace("memstore.tempdir", "dir", dir) - - ldbparams := NewLDBStoreParams(NewDefaultStoreParams(), dir) - db, err := NewLDBStore(ldbparams) - if err != nil { - t.Fatal(err) - } - - cleanup := func() { - db.Close() - err := os.RemoveAll(dir) - if err != nil { - t.Fatal(err) - } - } - - return db, cleanup -} - -func mputRandomChunks(store ChunkStore, n int) ([]Chunk, error) { - return mput(store, n, GenerateRandomChunk) -} - -func mput(store ChunkStore, n int, f func(i int64) Chunk) (hs []Chunk, err error) { - // put to localstore and wait for stored channel - // does not check delivery error state - errc := make(chan error) - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) - defer cancel() - for i := int64(0); i < int64(n); i++ { - chunk := f(ch.DefaultSize) - go func() { - select { - case errc <- store.Put(ctx, chunk): - case <-ctx.Done(): - } - }() - hs = append(hs, chunk) - } - - // wait for all chunks to be stored - for i := 0; i < n; i++ { - err := <-errc - if err != nil { - return nil, err - } - } - return hs, nil -} - -func mget(store ChunkStore, hs []Address, f func(h Address, chunk Chunk) error) error { - wg := sync.WaitGroup{} - wg.Add(len(hs)) - errc := make(chan error) - - for _, k := range hs { - go func(h Address) { - defer wg.Done() - // TODO: write timeout with context - chunk, err := store.Get(context.TODO(), h) - if err != nil { - errc <- err - return - } - if f != nil { - err = f(h, chunk) - if err != nil { - errc <- err - return - } - } - }(k) - } - go func() { - wg.Wait() - close(errc) - }() - var err error - timeout := 10 * time.Second - select { - case err = <-errc: - case <-time.NewTimer(timeout).C: - err = fmt.Errorf("timed out after %v", timeout) - } - return err -} - -func (r *brokenLimitedReader) Read(buf []byte) (int, error) { - if r.off+len(buf) > r.errAt { - return 0, fmt.Errorf("Broken reader") - } - r.off += len(buf) - return r.lr.Read(buf) -} - -func testStoreRandom(m ChunkStore, n int, t *testing.T) { - chunks, err := mputRandomChunks(m, n) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - err = mget(m, chunkAddresses(chunks), nil) - if err != nil { - t.Fatalf("testStore failed: %v", err) - } -} - -func testStoreCorrect(m ChunkStore, n int, t *testing.T) { - chunks, err := mputRandomChunks(m, n) - if err != nil { - t.Fatalf("expected no error, got %v", err) - } - f := func(h Address, chunk Chunk) error { - if !bytes.Equal(h, chunk.Address()) { - return fmt.Errorf("key does not match retrieved chunk Address") - } - hasher := MakeHashFunc(DefaultHash)() - data := chunk.Data() - hasher.ResetWithLength(data[:8]) - hasher.Write(data[8:]) - exp := hasher.Sum(nil) - if !bytes.Equal(h, exp) { - return fmt.Errorf("key is not hash of chunk data") - } - return nil - } - err = mget(m, chunkAddresses(chunks), f) - if err != nil { - t.Fatalf("testStore failed: %v", err) - } -} - -func benchmarkStorePut(store ChunkStore, n int, b *testing.B) { - chunks := make([]Chunk, n) - i := 0 - f := func(dataSize int64) Chunk { - chunk := GenerateRandomChunk(dataSize) - chunks[i] = chunk - i++ - return chunk - } - - mput(store, n, f) - - f = func(dataSize int64) Chunk { - chunk := chunks[i] - i++ - return chunk - } - - b.ReportAllocs() - b.ResetTimer() - - for j := 0; j < b.N; j++ { - i = 0 - mput(store, n, f) - } -} - -func benchmarkStoreGet(store ChunkStore, n int, b *testing.B) { - chunks, err := mputRandomChunks(store, n) - if err != nil { - b.Fatalf("expected no error, got %v", err) - } - b.ReportAllocs() - b.ResetTimer() - addrs := chunkAddresses(chunks) - for i := 0; i < b.N; i++ { - err := mget(store, addrs, nil) - if err != nil { - b.Fatalf("mget failed: %v", err) - } - } -} - -// MapChunkStore is a very simple ChunkStore implementation to store chunks in a map in memory. -type MapChunkStore struct { - chunks map[string]Chunk - mu sync.RWMutex -} - -func NewMapChunkStore() *MapChunkStore { - return &MapChunkStore{ - chunks: make(map[string]Chunk), - } -} - -func (m *MapChunkStore) Put(_ context.Context, ch Chunk) error { - m.mu.Lock() - defer m.mu.Unlock() - m.chunks[ch.Address().Hex()] = ch - return nil -} - -func (m *MapChunkStore) Get(_ context.Context, ref Address) (Chunk, error) { - m.mu.RLock() - defer m.mu.RUnlock() - chunk := m.chunks[ref.Hex()] - if chunk == nil { - return nil, ErrChunkNotFound - } - return chunk, nil -} - -// Need to implement Has from SyncChunkStore -func (m *MapChunkStore) Has(ctx context.Context, ref Address) bool { - m.mu.RLock() - defer m.mu.RUnlock() - - _, has := m.chunks[ref.Hex()] - return has -} - -func (m *MapChunkStore) Close() { -} - -func chunkAddresses(chunks []Chunk) []Address { - addrs := make([]Address, len(chunks)) - for i, ch := range chunks { - addrs[i] = ch.Address() - } - return addrs -} diff --git a/swarm/storage/database.go b/swarm/storage/database.go deleted file mode 100644 index 3f01d65c68d4..000000000000 --- a/swarm/storage/database.go +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package storage - -// this is a clone of an earlier state of the ethereum ethdb/database -// no need for queueing/caching - -import ( - "github.com/ubiq/go-ubiq/metrics" - "github.com/syndtr/goleveldb/leveldb" - "github.com/syndtr/goleveldb/leveldb/iterator" - "github.com/syndtr/goleveldb/leveldb/opt" -) - -const openFileLimit = 128 - -type LDBDatabase struct { - db *leveldb.DB -} - -func NewLDBDatabase(file string) (*LDBDatabase, error) { - // Open the db - db, err := leveldb.OpenFile(file, &opt.Options{OpenFilesCacheCapacity: openFileLimit}) - if err != nil { - return nil, err - } - - database := &LDBDatabase{db: db} - - return database, nil -} - -func (db *LDBDatabase) Put(key []byte, value []byte) error { - metrics.GetOrRegisterCounter("ldbdatabase.put", nil).Inc(1) - - return db.db.Put(key, value, nil) -} - -func (db *LDBDatabase) Get(key []byte) ([]byte, error) { - metrics.GetOrRegisterCounter("ldbdatabase.get", nil).Inc(1) - - dat, err := db.db.Get(key, nil) - if err != nil { - return nil, err - } - return dat, nil -} - -func (db *LDBDatabase) Delete(key []byte) error { - return db.db.Delete(key, nil) -} - -func (db *LDBDatabase) NewIterator() iterator.Iterator { - metrics.GetOrRegisterCounter("ldbdatabase.newiterator", nil).Inc(1) - - return db.db.NewIterator(nil, nil) -} - -func (db *LDBDatabase) Write(batch *leveldb.Batch) error { - metrics.GetOrRegisterCounter("ldbdatabase.write", nil).Inc(1) - - return db.db.Write(batch, nil) -} - -func (db *LDBDatabase) Close() { - // Close the leveldb database - db.db.Close() -} diff --git a/swarm/storage/encryption/encryption.go b/swarm/storage/encryption/encryption.go deleted file mode 100644 index 6fbdab062b33..000000000000 --- a/swarm/storage/encryption/encryption.go +++ /dev/null @@ -1,152 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package encryption - -import ( - "crypto/rand" - "encoding/binary" - "fmt" - "hash" - "sync" -) - -const KeyLength = 32 - -type Key []byte - -type Encryption interface { - Encrypt(data []byte) ([]byte, error) - Decrypt(data []byte) ([]byte, error) -} - -type encryption struct { - key Key // the encryption key (hashSize bytes long) - keyLen int // length of the key = length of blockcipher block - padding int // encryption will pad the data upto this if > 0 - initCtr uint32 // initial counter used for counter mode blockcipher - hashFunc func() hash.Hash // hasher constructor function -} - -// New constructs a new encryptor/decryptor -func New(key Key, padding int, initCtr uint32, hashFunc func() hash.Hash) *encryption { - return &encryption{ - key: key, - keyLen: len(key), - padding: padding, - initCtr: initCtr, - hashFunc: hashFunc, - } -} - -// Encrypt encrypts the data and does padding if specified -func (e *encryption) Encrypt(data []byte) ([]byte, error) { - length := len(data) - outLength := length - isFixedPadding := e.padding > 0 - if isFixedPadding { - if length > e.padding { - return nil, fmt.Errorf("Data length longer than padding, data length %v padding %v", length, e.padding) - } - outLength = e.padding - } - out := make([]byte, outLength) - e.transform(data, out) - return out, nil -} - -// Decrypt decrypts the data, if padding was used caller must know original length and truncate -func (e *encryption) Decrypt(data []byte) ([]byte, error) { - length := len(data) - if e.padding > 0 && length != e.padding { - return nil, fmt.Errorf("Data length different than padding, data length %v padding %v", length, e.padding) - } - out := make([]byte, length) - e.transform(data, out) - return out, nil -} - -// -func (e *encryption) transform(in, out []byte) { - inLength := len(in) - wg := sync.WaitGroup{} - wg.Add((inLength-1)/e.keyLen + 1) - for i := 0; i < inLength; i += e.keyLen { - l := min(e.keyLen, inLength-i) - // call transformations per segment (asyncronously) - go func(i int, x, y []byte) { - defer wg.Done() - e.Transcrypt(i, x, y) - }(i/e.keyLen, in[i:i+l], out[i:i+l]) - } - // pad the rest if out is longer - pad(out[inLength:]) - wg.Wait() -} - -// used for segmentwise transformation -// if in is shorter than out, padding is used -func (e *encryption) Transcrypt(i int, in []byte, out []byte) { - // first hash key with counter (initial counter + i) - hasher := e.hashFunc() - hasher.Write(e.key) - - ctrBytes := make([]byte, 4) - binary.LittleEndian.PutUint32(ctrBytes, uint32(i)+e.initCtr) - hasher.Write(ctrBytes) - - ctrHash := hasher.Sum(nil) - hasher.Reset() - - // second round of hashing for selective disclosure - hasher.Write(ctrHash) - segmentKey := hasher.Sum(nil) - hasher.Reset() - - // XOR bytes uptil length of in (out must be at least as long) - inLength := len(in) - for j := 0; j < inLength; j++ { - out[j] = in[j] ^ segmentKey[j] - } - // insert padding if out is longer - pad(out[inLength:]) -} - -func pad(b []byte) { - l := len(b) - for total := 0; total < l; { - read, _ := rand.Read(b[total:]) - total += read - } -} - -// GenerateRandomKey generates a random key of length l -func GenerateRandomKey(l int) Key { - key := make([]byte, l) - var total int - for total < l { - read, _ := rand.Read(key[total:]) - total += read - } - return key -} - -func min(x, y int) int { - if x < y { - return x - } - return y -} diff --git a/swarm/storage/encryption/encryption_test.go b/swarm/storage/encryption/encryption_test.go deleted file mode 100644 index f16b7f860b26..000000000000 --- a/swarm/storage/encryption/encryption_test.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package encryption - -import ( - "bytes" - "testing" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/common/hexutil" - "github.com/ubiq/go-ubiq/swarm/testutil" - "golang.org/x/crypto/sha3" -) - -var expectedTransformedHex = "352187af3a843decc63ceca6cb01ea39dbcf77caf0a8f705f5c30d557044ceec9392b94a79376f1e5c10cd0c0f2a98e5353bf22b3ea4fdac6677ee553dec192e3db64e179d0474e96088fb4abd2babd67de123fb398bdf84d818f7bda2c1ab60b3ea0e0569ae54aa969658eb4844e6960d2ff44d7c087ee3aaffa1c0ee5df7e50b615f7ad90190f022934ad5300c7d1809bfe71a11cc04cece5274eb97a5f20350630522c1dbb7cebaf4f97f84e03f5cfd88f2b48880b25d12f4d5e75c150f704ef6b46c72e07db2b705ac3644569dccd22fd8f964f6ef787fda63c46759af334e6f665f70eac775a7017acea49f3c7696151cb1b9434fa4ac27fb803921ffb5ec58dafa168098d7d5b97e384be3384cf5bc235c3d887fef89fe76c0065f9b8d6ad837b442340d9e797b46ef5709ea3358bc415df11e4830de986ef0f1c418ffdcc80e9a3cda9bea0ab5676c0d4240465c43ba527e3b4ea50b4f6255b510e5d25774a75449b0bd71e56c537ade4fcf0f4d63c99ae1dbb5a844971e2c19941b8facfcfc8ee3056e7cb3c7114c5357e845b52f7103cb6e00d2308c37b12baa5b769e1cc7b00fc06f2d16e70cc27a82cb9c1a4e40cb0d43907f73df2c9db44f1b51a6b0bc6d09f77ac3be14041fae3f9df2da42df43ae110904f9ecee278030185254d7c6e918a5512024d047f77a992088cb3190a6587aa54d0c7231c1cd2e455e0d4c07f74bece68e29cd8ba0190c0bcfb26d24634af5d91a81ef5d4dd3d614836ce942ddbf7bb1399317f4c03faa675f325f18324bf9433844bfe5c4cc04130c8d5c329562b7cd66e72f7355de8f5375a72202971613c32bd7f3fcdcd51080758cd1d0a46dbe8f0374381dbc359f5864250c63dde8131cbd7c98ae2b0147d6ea4bf65d1443d511b18e6d608bbb46ac036353b4c51df306a10a6f6939c38629a5c18aaf89cac04bd3ad5156e6b92011c88341cb08551bab0a89e6a46538f5af33b86121dba17e3a434c273f385cd2e8cb90bdd32747d8425d929ccbd9b0815c73325988855549a8489dfd047daf777aaa3099e54cf997175a5d9e1edfe363e3b68c70e02f6bf4fcde6a0f3f7d0e7e98bde1a72ae8b6cd27b32990680cc4a04fc467f41c5adcaddabfc71928a3f6872c360c1d765260690dd28b269864c8e380d9c92ef6b89b0094c8f9bb22608b4156381b19b920e9583c9616ce5693b4d2a6c689f02e6a91584a8e501e107403d2689dd0045269dd9946c0e969fb656a3b39d84a798831f5f9290f163eb2f97d3ae25071324e95e2256d9c1e56eb83c26397855323edc202d56ad05894333b7f0ed3c1e4734782eb8bd5477242fd80d7a89b12866f85cfae476322f032465d6b1253993033fccd4723530630ab97a1566460af9c90c9da843c229406e65f3fa578bd6bf04dee9b6153807ddadb8ceefc5c601a8ab26023c67b1ab1e8e0f29ce94c78c308005a781853e7a2e0e51738939a657c987b5e611f32f47b5ff461c52e63e0ea390515a8e1f5393dae54ea526934b5f310b76e3fa050e40718cb4c8a20e58946d6ee1879f08c52764422fe542b3240e75eccb7aa75b1f8a651e37a3bc56b0932cdae0e985948468db1f98eb4b77b82081ea25d8a762db00f7898864984bd80e2f3f35f236bf57291dec28f550769943bcfb6f884b7687589b673642ef7fe5d7d5a87d3eca5017f83ccb9a3310520474479464cb3f433440e7e2f1e28c0aef700a45848573409e7ab66e0cfd4fe5d2147ace81bc65fd8891f6245cd69246bbf5c27830e5ab882dd1d02aba34ff6ca9af88df00fd602892f02fedbdc65dedec203faf3f8ff4a97314e0ddb58b9ab756a61a562597f4088b445fcc3b28a708ca7b1485dcd791b779fbf2b3ef1ec5c6205f595fbe45a02105034147e5a146089c200a49dae33ae051a08ea5f974a21540aaeffa7f9d9e3d35478016fb27b871036eb27217a5b834b461f535752fb5f1c8dded3ae14ce3a2ef6639e2fe41939e3509e46e347a95d50b2080f1ba42c804b290ddc912c952d1cec3f2661369f738feacc0dbf1ea27429c644e45f9e26f30c341acd34c7519b2a1663e334621691e810767e9918c2c547b2e23cce915f97d26aac8d0d2fcd3edb7986ad4e2b8a852edebad534cb6c0e9f0797d3563e5409d7e068e48356c67ce519246cd9c560e881453df97cbba562018811e6cf8c327f399d1d1253ab47a19f4a0ccc7c6d86a9603e0551da310ea595d71305c4aad96819120a92cdbaf1f77ec8df9cc7c838c0d4de1e8692dd81da38268d1d71324bcffdafbe5122e4b81828e021e936d83ae8021eac592aa52cd296b5ce392c7173d622f8e07d18f59bb1b08ba15211af6703463b09b593af3c37735296816d9f2e7a369354a5374ea3955e14ca8ac56d5bfe4aef7a21bd825d6ae85530bee5d2aaaa4914981b3dfdb2e92ec2a27c83d74b59e84ff5c056f7d8945745f2efc3dcf28f288c6cd8383700fb2312f7001f24dd40015e436ae23e052fe9070ea9535b9c989898a9bda3d5382cf10e432fae6ccf0c825b3e6436edd3a9f8846e5606f8563931b5f29ba407c5236e5730225dda211a8504ec1817bc935e1fd9a532b648c502df302ed2063aed008fd5676131ac9e95998e9447b02bd29d77e38fcfd2959f2de929b31970335eb2a74348cc6918bc35b9bf749eab0fe304c946cd9e1ca284e6853c42646e60b6b39e0d3fb3c260abfc5c1b4ca3c3770f344118ca7c7f5c1ad1f123f8f369cd60afc3cdb3e9e81968c5c9fa7c8b014ffe0508dd4f0a2a976d5d1ca8fc9ad7a237d92cfe7b41413d934d6e142824b252699397e48e4bac4e91ebc10602720684bd0863773c548f9a2f9724245e47b129ecf65afd7252aac48c8a8d6fd3d888af592a01fb02dc71ed7538a700d3d16243e4621e0fcf9f8ed2b4e11c9fa9a95338bb1dac74a7d9bc4eb8cbf900b634a2a56469c00f5994e4f0934bdb947640e6d67e47d0b621aacd632bfd3c800bd7d93bd329f494a90e06ed51535831bd6e07ac1b4b11434ef3918fa9511813a002913f33f836454798b8d1787fea9a4c4743ba091ed192ed92f4d33e43a226bf9503e1a83a16dd340b3cbbf38af6db0d99201da8de529b4225f3d2fa2aad6621afc6c79ef3537720591edfc681ae6d00ede53ed724fc71b23b90d2e9b7158aaee98d626a4fe029107df2cb5f90147e07ebe423b1519d848af18af365c71bfd0665db46be493bbe99b79a188de0cf3594aef2299f0324075bdce9eb0b87bc29d62401ba4fd6ae48b1ba33261b5b845279becf38ee03e3dc5c45303321c5fac96fd02a3ad8c9e3b02127b320501333c9e6360440d1ad5e64a6239501502dde1a49c9abe33b66098458eee3d611bb06ffcd234a1b9aef4af5021cd61f0de6789f822ee116b5078aae8c129e8391d8987500d322b58edd1595dc570b57341f2df221b94a96ab7fbcf32a8ca9684196455694024623d7ed49f7d66e8dd453c0bae50e0d8b34377b22d0ece059e2c385dfc70b9089fcd27577c51f4d870b5738ee2b68c361a67809c105c7848b68860a829f29930857a9f9d40b14fd2384ac43bafdf43c0661103794c4bd07d1cfdd4681b6aeaefad53d4c1473359bcc5a83b09189352e5bb9a7498dd0effb89c35aad26954551f8b0621374b449bf515630bd3974dca982279733470fdd059aa9c3df403d8f22b38c4709c82d8f12b888e22990350490e16179caf406293cc9e65f116bafcbe96af132f679877061107a2f690a82a8cb46eea57a90abd23798c5937c6fe6b17be3f9bfa01ce117d2c268181b9095bf49f395fea07ca03838de0588c5e2db633e836d64488c1421e653ea52d810d096048c092d0da6e02fa6613890219f51a76148c8588c2487b171a28f17b7a299204874af0131725d793481333be5f08e86ca837a226850b0c1060891603bfecf9e55cddd22c0dbb28d495342d9cc3de8409f72e52a0115141cffe755c74f061c1a770428ccb0ae59536ee6fc074fbfc6cacb51a549d327527e20f8407477e60355863f1153f9ce95641198663c968874e7fdb29407bd771d94fdda8180cbb0358f5874738db705924b8cbe0cd5e1484aeb64542fe8f38667b7c34baf818c63b1e18440e9fba575254d063fd49f24ef26432f4eb323f3836972dca87473e3e9bb26dc3be236c3aae6bc8a6da567442309da0e8450e242fc9db836e2964f2c76a3b80a2c677979882dda7d7ebf62c93664018bcf4ec431fe6b403d49b3b36618b9c07c2d0d4569cb8d52223903debc72ec113955b206c34f1ae5300990ccfc0180f47d91afdb542b6312d12aeff7e19c645dc0b9fe6e3288e9539f6d5870f99882df187bfa6d24d179dfd1dac22212c8b5339f7171a3efc15b760fed8f68538bc5cbd845c2d1ab41f3a6c692820653eaef7930c02fbe6061d93805d73decdbb945572a7c44ed0241982a6e4d2d730898f82b3d9877cb7bca41cc6dcee67aa0c3d6db76f0b0a708ace0031113e48429de5d886c10e9200f68f32263a2fbf44a5992c2459fda7b8796ba796e3a0804fc25992ed2c9a5fe0580a6b809200ecde6caa0364b58be11564dcb9a616766dd7906db5636ee708b0204f38d309466d8d4a162965dd727e29f5a6c133e9b4ed5bafe803e479f9b2a7640c942c4a40b14ac7dc9828546052761a070f6404008f1ec3605836339c3da95a00b4fd81b2cabf88b51d2087d5b83e8c5b69bf96d8c72cbd278dad3bbb42b404b436f84ad688a22948adf60a81090f1e904291503c16e9f54b05fc76c881a5f95f0e732949e95d3f1bae2d3652a14fe0dda2d68879604657171856ef72637def2a96ac47d7b3fe86eb3198f5e0e626f06be86232305f2ae79ffcd2725e48208f9d8d63523f81915acc957563ab627cd6bc68c2a37d59fb0ed77a90aa9d085d6914a8ebada22a2c2d471b5163aeddd799d90fbb10ed6851ace2c4af504b7d572686700a59d6db46d5e42bb83f8e0c0ffe1dfa6582cc0b34c921ff6e85e83188d24906d5c08bb90069639e713051b3102b53e6f703e8210017878add5df68e6f2b108de279c5490e9eef5590185c4a1c744d4e00d244e1245a8805bd30407b1bc488db44870ccfd75a8af104df78efa2fb7ba31f048a263efdb3b63271fff4922bece9a71187108f65744a24f4947dc556b7440cb4fa45d296bb7f724588d1f245125b21ea063500029bd49650237f53899daf1312809552c81c5827341263cc807a29fe84746170cdfa1ff3838399a5645319bcaff674bb70efccdd88b3d3bb2f2d98111413585dc5d5bd5168f43b3f55e58972a5b2b9b3733febf02f931bd436648cb617c3794841aab961fe41277ab07812e1d3bc4ff6f4350a3e615bfba08c3b9480ef57904d3a16f7e916345202e3f93d11f7a7305170cb8c4eb9ac88ace8bbd1f377bdd5855d3162d6723d4435e84ce529b8f276a8927915ac759a0d04e5ca4a9d3da6291f0333b475df527e99fe38f7a4082662e8125936640c26dd1d17cf284ce6e2b17777a05aa0574f7793a6a062cc6f7263f7ab126b4528a17becfdec49ac0f7d8705aa1704af97fb861faa8a466161b2b5c08a5bacc79fe8500b913d65c8d3c52d1fd52d2ab2c9f52196e712455619c1cd3e0f391b274487944240e2ed8858dd0823c801094310024ae3fe4dd1cf5a2b6487b42cc5937bbafb193ee331d87e378258963d49b9da90899bbb4b88e79f78e866b0213f4719f67da7bcc2fce073c01e87c62ea3cdbcd589cfc41281f2f4c757c742d6d1e" - -var hashFunc = sha3.NewLegacyKeccak256 -var testKey Key - -func init() { - var err error - testKey, err = hexutil.Decode("0x8abf1502f557f15026716030fb6384792583daf39608a3cd02ff2f47e9bc6e49") - if err != nil { - panic(err.Error()) - } -} - -func TestEncryptDataLongerThanPadding(t *testing.T) { - enc := New(testKey, 4095, uint32(0), hashFunc) - - data := make([]byte, 4096) - - expectedError := "Data length longer than padding, data length 4096 padding 4095" - - _, err := enc.Encrypt(data) - if err == nil || err.Error() != expectedError { - t.Fatalf("Expected error \"%v\" got \"%v\"", expectedError, err) - } -} - -func TestEncryptDataZeroPadding(t *testing.T) { - enc := New(testKey, 0, uint32(0), hashFunc) - - data := make([]byte, 2048) - - encrypted, err := enc.Encrypt(data) - if err != nil { - t.Fatalf("Expected no error got %v", err) - } - if len(encrypted) != 2048 { - t.Fatalf("Encrypted data length expected \"%v\" got %v", 2048, len(encrypted)) - } -} - -func TestEncryptDataLengthEqualsPadding(t *testing.T) { - enc := New(testKey, 4096, uint32(0), hashFunc) - - data := make([]byte, 4096) - - encrypted, err := enc.Encrypt(data) - if err != nil { - t.Fatalf("Expected no error got %v", err) - } - encryptedHex := common.Bytes2Hex(encrypted) - expectedTransformed := common.Hex2Bytes(expectedTransformedHex) - - if !bytes.Equal(encrypted, expectedTransformed) { - t.Fatalf("Expected %v got %v", expectedTransformedHex, encryptedHex) - } -} - -func TestEncryptDataLengthSmallerThanPadding(t *testing.T) { - enc := New(testKey, 4096, uint32(0), hashFunc) - - data := make([]byte, 4080) - - encrypted, err := enc.Encrypt(data) - if err != nil { - t.Fatalf("Expected no error got %v", err) - } - if len(encrypted) != 4096 { - t.Fatalf("Encrypted data length expected %v got %v", 4096, len(encrypted)) - } -} - -func TestEncryptDataCounterNonZero(t *testing.T) { - // TODO -} - -func TestDecryptDataLengthNotEqualsPadding(t *testing.T) { - enc := New(testKey, 4096, uint32(0), hashFunc) - - data := make([]byte, 4097) - - expectedError := "Data length different than padding, data length 4097 padding 4096" - - _, err := enc.Decrypt(data) - if err == nil || err.Error() != expectedError { - t.Fatalf("Expected error \"%v\" got \"%v\"", expectedError, err) - } -} - -func TestEncryptDecryptIsIdentity(t *testing.T) { - testEncryptDecryptIsIdentity(t, 2048, 0, 2048, 32) - testEncryptDecryptIsIdentity(t, 4096, 0, 4096, 32) - testEncryptDecryptIsIdentity(t, 4096, 0, 1000, 32) - testEncryptDecryptIsIdentity(t, 32, 10, 32, 32) -} - -func testEncryptDecryptIsIdentity(t *testing.T, padding int, initCtr uint32, dataLength int, keyLength int) { - key := GenerateRandomKey(keyLength) - enc := New(key, padding, initCtr, hashFunc) - - data := testutil.RandomBytes(1, dataLength) - - encrypted, err := enc.Encrypt(data) - if err != nil { - t.Fatalf("Expected no error got %v", err) - } - - decrypted, err := enc.Decrypt(encrypted) - if err != nil { - t.Fatalf("Expected no error got %v", err) - } - if len(decrypted) != padding { - t.Fatalf("Expected decrypted data length %v got %v", padding, len(decrypted)) - } - - // we have to remove the extra bytes which were randomly added to fill until padding - if len(data) < padding { - decrypted = decrypted[:len(data)] - } - - if !bytes.Equal(data, decrypted) { - t.Fatalf("Expected decrypted %v got %v", common.Bytes2Hex(data), common.Bytes2Hex(decrypted)) - } -} diff --git a/swarm/storage/error.go b/swarm/storage/error.go deleted file mode 100644 index a9d0616fada6..000000000000 --- a/swarm/storage/error.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package storage - -import ( - "errors" -) - -const ( - ErrInit = iota - ErrNotFound - ErrUnauthorized - ErrInvalidValue - ErrDataOverflow - ErrNothingToReturn - ErrInvalidSignature - ErrNotSynced -) - -var ( - ErrChunkNotFound = errors.New("chunk not found") - ErrChunkInvalid = errors.New("invalid chunk") -) diff --git a/swarm/storage/feed/binaryserializer.go b/swarm/storage/feed/binaryserializer.go deleted file mode 100644 index d7598e768313..000000000000 --- a/swarm/storage/feed/binaryserializer.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package feed - -import "github.com/ubiq/go-ubiq/common/hexutil" - -type binarySerializer interface { - binaryPut(serializedData []byte) error - binaryLength() int - binaryGet(serializedData []byte) error -} - -// Values interface represents a string key-value store -// useful for building query strings -type Values interface { - Get(key string) string - Set(key, value string) -} - -type valueSerializer interface { - FromValues(values Values) error - AppendValues(values Values) -} - -// Hex serializes the structure and converts it to a hex string -func Hex(bin binarySerializer) string { - b := make([]byte, bin.binaryLength()) - bin.binaryPut(b) - return hexutil.Encode(b) -} diff --git a/swarm/storage/feed/binaryserializer_test.go b/swarm/storage/feed/binaryserializer_test.go deleted file mode 100644 index e635a451a61e..000000000000 --- a/swarm/storage/feed/binaryserializer_test.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package feed - -import ( - "encoding/json" - "reflect" - "testing" - - "github.com/ubiq/go-ubiq/common/hexutil" -) - -// KV mocks a key value store -type KV map[string]string - -func (kv KV) Get(key string) string { - return kv[key] -} -func (kv KV) Set(key, value string) { - kv[key] = value -} - -func compareByteSliceToExpectedHex(t *testing.T, variableName string, actualValue []byte, expectedHex string) { - if hexutil.Encode(actualValue) != expectedHex { - t.Fatalf("%s: Expected %s to be %s, got %s", t.Name(), variableName, expectedHex, hexutil.Encode(actualValue)) - } -} - -func testBinarySerializerRecovery(t *testing.T, bin binarySerializer, expectedHex string) { - name := reflect.TypeOf(bin).Elem().Name() - serialized := make([]byte, bin.binaryLength()) - if err := bin.binaryPut(serialized); err != nil { - t.Fatalf("%s.binaryPut error when trying to serialize structure: %s", name, err) - } - - compareByteSliceToExpectedHex(t, name, serialized, expectedHex) - - recovered := reflect.New(reflect.TypeOf(bin).Elem()).Interface().(binarySerializer) - if err := recovered.binaryGet(serialized); err != nil { - t.Fatalf("%s.binaryGet error when trying to deserialize structure: %s", name, err) - } - - if !reflect.DeepEqual(bin, recovered) { - t.Fatalf("Expected that the recovered %s equals the marshalled %s", name, name) - } - - serializedWrongLength := make([]byte, 1) - copy(serializedWrongLength[:], serialized) - if err := recovered.binaryGet(serializedWrongLength); err == nil { - t.Fatalf("Expected %s.binaryGet to fail since data is too small", name) - } -} - -func testBinarySerializerLengthCheck(t *testing.T, bin binarySerializer) { - name := reflect.TypeOf(bin).Elem().Name() - // make a slice that is too small to contain the metadata - serialized := make([]byte, bin.binaryLength()-1) - - if err := bin.binaryPut(serialized); err == nil { - t.Fatalf("Expected %s.binaryPut to fail, since target slice is too small", name) - } -} - -func testValueSerializer(t *testing.T, v valueSerializer, expected KV) { - name := reflect.TypeOf(v).Elem().Name() - kv := make(KV) - - v.AppendValues(kv) - if !reflect.DeepEqual(expected, kv) { - expj, _ := json.Marshal(expected) - gotj, _ := json.Marshal(kv) - t.Fatalf("Expected %s.AppendValues to return %s, got %s", name, string(expj), string(gotj)) - } - - recovered := reflect.New(reflect.TypeOf(v).Elem()).Interface().(valueSerializer) - err := recovered.FromValues(kv) - if err != nil { - t.Fatal(err) - } - - if !reflect.DeepEqual(recovered, v) { - t.Fatalf("Expected recovered %s to be the same", name) - } -} diff --git a/swarm/storage/feed/cacheentry.go b/swarm/storage/feed/cacheentry.go deleted file mode 100644 index 62eec4ea11c8..000000000000 --- a/swarm/storage/feed/cacheentry.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package feed - -import ( - "bytes" - "context" - "time" - - "github.com/ubiq/go-ubiq/swarm/storage" -) - -const ( - hasherCount = 8 - feedsHashAlgorithm = storage.SHA3Hash - defaultRetrieveTimeout = 100 * time.Millisecond -) - -// cacheEntry caches the last known update of a specific Swarm feed. -type cacheEntry struct { - Update - *bytes.Reader - lastKey storage.Address -} - -// implements storage.LazySectionReader -func (r *cacheEntry) Size(ctx context.Context, _ chan bool) (int64, error) { - return int64(len(r.Update.data)), nil -} - -//returns the feed's topic -func (r *cacheEntry) Topic() Topic { - return r.Feed.Topic -} diff --git a/swarm/storage/feed/doc.go b/swarm/storage/feed/doc.go deleted file mode 100644 index 1f07948f2119..000000000000 --- a/swarm/storage/feed/doc.go +++ /dev/null @@ -1,43 +0,0 @@ -/* -Package feeds defines Swarm Feeds. - -Swarm Feeds allows a user to build an update feed about a particular topic -without resorting to ENS on each update. -The update scheme is built on swarm chunks with chunk keys following -a predictable, versionable pattern. - -A Feed is tied to a unique identifier that is deterministically generated out of -the chosen topic. - -A Feed is defined as the series of updates of a specific user about a particular topic - -Actual data updates are also made in the form of swarm chunks. The keys -of the updates are the hash of a concatenation of properties as follows: - -updateAddr = H(Feed, Epoch ID) -where H is the SHA3 hash function -Feed is the combination of Topic and the user address -Epoch ID is a time slot. See the lookup package for more information. - -A user looking up a the latest update in a Feed only needs to know the Topic -and the other user's address. - -The Feed Update data is: -updatedata = Feed|Epoch|data - -The full update data that goes in the chunk payload is: -updatedata|sign(updatedata) - -Structure Summary: - -Request: Feed Update with signature - Update: headers + data - Header: Protocol version and reserved for future use placeholders - ID: Information about how to locate a specific update - Feed: Represents a user's series of publications about a specific Topic - Topic: Item that the updates are about - User: User who updates the Feed - Epoch: time slot where the update is stored - -*/ -package feed diff --git a/swarm/storage/feed/error.go b/swarm/storage/feed/error.go deleted file mode 100644 index 206ba3316fa1..000000000000 --- a/swarm/storage/feed/error.go +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package feed - -import ( - "fmt" -) - -const ( - ErrInit = iota - ErrNotFound - ErrIO - ErrUnauthorized - ErrInvalidValue - ErrDataOverflow - ErrNothingToReturn - ErrCorruptData - ErrInvalidSignature - ErrNotSynced - ErrPeriodDepth - ErrCnt -) - -// Error is a the typed error object used for Swarm feeds -type Error struct { - code int - err string -} - -// Error implements the error interface -func (e *Error) Error() string { - return e.err -} - -// Code returns the error code -// Error codes are enumerated in the error.go file within the feeds package -func (e *Error) Code() int { - return e.code -} - -// NewError creates a new Swarm feeds Error object with the specified code and custom error message -func NewError(code int, s string) error { - if code < 0 || code >= ErrCnt { - panic("no such error code!") - } - r := &Error{ - err: s, - } - switch code { - case ErrNotFound, ErrIO, ErrUnauthorized, ErrInvalidValue, ErrDataOverflow, ErrNothingToReturn, ErrInvalidSignature, ErrNotSynced, ErrPeriodDepth, ErrCorruptData: - r.code = code - } - return r -} - -// NewErrorf is a convenience version of NewError that incorporates printf-style formatting -func NewErrorf(code int, format string, args ...interface{}) error { - return NewError(code, fmt.Sprintf(format, args...)) -} diff --git a/swarm/storage/feed/feed.go b/swarm/storage/feed/feed.go deleted file mode 100644 index e633ebffd399..000000000000 --- a/swarm/storage/feed/feed.go +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package feed - -import ( - "hash" - "unsafe" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/common/hexutil" - "github.com/ubiq/go-ubiq/swarm/storage" -) - -// Feed represents a particular user's stream of updates on a topic -type Feed struct { - Topic Topic `json:"topic"` - User common.Address `json:"user"` -} - -// Feed layout: -// TopicLength bytes -// userAddr common.AddressLength bytes -const feedLength = TopicLength + common.AddressLength - -// mapKey calculates a unique id for this feed. Used by the cache map in `Handler` -func (f *Feed) mapKey() uint64 { - serializedData := make([]byte, feedLength) - f.binaryPut(serializedData) - hasher := hashPool.Get().(hash.Hash) - defer hashPool.Put(hasher) - hasher.Reset() - hasher.Write(serializedData) - hash := hasher.Sum(nil) - return *(*uint64)(unsafe.Pointer(&hash[0])) -} - -// binaryPut serializes this feed instance into the provided slice -func (f *Feed) binaryPut(serializedData []byte) error { - if len(serializedData) != feedLength { - return NewErrorf(ErrInvalidValue, "Incorrect slice size to serialize feed. Expected %d, got %d", feedLength, len(serializedData)) - } - var cursor int - copy(serializedData[cursor:cursor+TopicLength], f.Topic[:TopicLength]) - cursor += TopicLength - - copy(serializedData[cursor:cursor+common.AddressLength], f.User[:]) - cursor += common.AddressLength - - return nil -} - -// binaryLength returns the expected size of this structure when serialized -func (f *Feed) binaryLength() int { - return feedLength -} - -// binaryGet restores the current instance from the information contained in the passed slice -func (f *Feed) binaryGet(serializedData []byte) error { - if len(serializedData) != feedLength { - return NewErrorf(ErrInvalidValue, "Incorrect slice size to read feed. Expected %d, got %d", feedLength, len(serializedData)) - } - - var cursor int - copy(f.Topic[:], serializedData[cursor:cursor+TopicLength]) - cursor += TopicLength - - copy(f.User[:], serializedData[cursor:cursor+common.AddressLength]) - cursor += common.AddressLength - - return nil -} - -// Hex serializes the feed to a hex string -func (f *Feed) Hex() string { - serializedData := make([]byte, feedLength) - f.binaryPut(serializedData) - return hexutil.Encode(serializedData) -} - -// FromValues deserializes this instance from a string key-value store -// useful to parse query strings -func (f *Feed) FromValues(values Values) (err error) { - topic := values.Get("topic") - if topic != "" { - if err := f.Topic.FromHex(values.Get("topic")); err != nil { - return err - } - } else { // see if the user set name and relatedcontent - name := values.Get("name") - relatedContent, _ := hexutil.Decode(values.Get("relatedcontent")) - if len(relatedContent) > 0 { - if len(relatedContent) < storage.AddressLength { - return NewErrorf(ErrInvalidValue, "relatedcontent field must be a hex-encoded byte array exactly %d bytes long", storage.AddressLength) - } - relatedContent = relatedContent[:storage.AddressLength] - } - f.Topic, err = NewTopic(name, relatedContent) - if err != nil { - return err - } - } - f.User = common.HexToAddress(values.Get("user")) - return nil -} - -// AppendValues serializes this structure into the provided string key-value store -// useful to build query strings -func (f *Feed) AppendValues(values Values) { - values.Set("topic", f.Topic.Hex()) - values.Set("user", f.User.Hex()) -} diff --git a/swarm/storage/feed/feed_test.go b/swarm/storage/feed/feed_test.go deleted file mode 100644 index 6a575594f4c4..000000000000 --- a/swarm/storage/feed/feed_test.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . -package feed - -import ( - "testing" -) - -func getTestFeed() *Feed { - topic, _ := NewTopic("world news report, every hour", nil) - return &Feed{ - Topic: topic, - User: newCharlieSigner().Address(), - } -} - -func TestFeedSerializerDeserializer(t *testing.T) { - testBinarySerializerRecovery(t, getTestFeed(), "0x776f726c64206e657773207265706f72742c20657665727920686f7572000000876a8936a7cd0b79ef0735ad0896c1afe278781c") -} - -func TestFeedSerializerLengthCheck(t *testing.T) { - testBinarySerializerLengthCheck(t, getTestFeed()) -} diff --git a/swarm/storage/feed/handler.go b/swarm/storage/feed/handler.go deleted file mode 100644 index d9f693bd3fdf..000000000000 --- a/swarm/storage/feed/handler.go +++ /dev/null @@ -1,291 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Handler is the API for feeds -// It enables creating, updating, syncing and retrieving feed updates and their data -package feed - -import ( - "bytes" - "context" - "fmt" - "sync" - - "github.com/ubiq/go-ubiq/swarm/storage/feed/lookup" - - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/storage" -) - -type Handler struct { - chunkStore *storage.NetStore - HashSize int - cache map[uint64]*cacheEntry - cacheLock sync.RWMutex -} - -// HandlerParams pass parameters to the Handler constructor NewHandler -// Signer and TimestampProvider are mandatory parameters -type HandlerParams struct { -} - -// hashPool contains a pool of ready hashers -var hashPool sync.Pool - -// init initializes the package and hashPool -func init() { - hashPool = sync.Pool{ - New: func() interface{} { - return storage.MakeHashFunc(feedsHashAlgorithm)() - }, - } -} - -// NewHandler creates a new Swarm feeds API -func NewHandler(params *HandlerParams) *Handler { - fh := &Handler{ - cache: make(map[uint64]*cacheEntry), - } - - for i := 0; i < hasherCount; i++ { - hashfunc := storage.MakeHashFunc(feedsHashAlgorithm)() - if fh.HashSize == 0 { - fh.HashSize = hashfunc.Size() - } - hashPool.Put(hashfunc) - } - - return fh -} - -// SetStore sets the store backend for the Swarm feeds API -func (h *Handler) SetStore(store *storage.NetStore) { - h.chunkStore = store -} - -// Validate is a chunk validation method -// If it looks like a feed update, the chunk address is checked against the userAddr of the update's signature -// It implements the storage.ChunkValidator interface -func (h *Handler) Validate(chunk storage.Chunk) bool { - if len(chunk.Data()) < minimumSignedUpdateLength { - return false - } - - // check if it is a properly formatted update chunk with - // valid signature and proof of ownership of the feed it is trying - // to update - - // First, deserialize the chunk - var r Request - if err := r.fromChunk(chunk); err != nil { - log.Debug("Invalid feed update chunk", "addr", chunk.Address(), "err", err) - return false - } - - // Verify signatures and that the signer actually owns the feed - // If it fails, it means either the signature is not valid, data is corrupted - // or someone is trying to update someone else's feed. - if err := r.Verify(); err != nil { - log.Debug("Invalid feed update signature", "err", err) - return false - } - - return true -} - -// GetContent retrieves the data payload of the last synced update of the feed -func (h *Handler) GetContent(feed *Feed) (storage.Address, []byte, error) { - if feed == nil { - return nil, nil, NewError(ErrInvalidValue, "feed is nil") - } - feedUpdate := h.get(feed) - if feedUpdate == nil { - return nil, nil, NewError(ErrNotFound, "feed update not cached") - } - return feedUpdate.lastKey, feedUpdate.data, nil -} - -// NewRequest prepares a Request structure with all the necessary information to -// just add the desired data and sign it. -// The resulting structure can then be signed and passed to Handler.Update to be verified and sent -func (h *Handler) NewRequest(ctx context.Context, feed *Feed) (request *Request, err error) { - if feed == nil { - return nil, NewError(ErrInvalidValue, "feed cannot be nil") - } - - now := TimestampProvider.Now().Time - request = new(Request) - request.Header.Version = ProtocolVersion - - query := NewQueryLatest(feed, lookup.NoClue) - - feedUpdate, err := h.Lookup(ctx, query) - if err != nil { - if err.(*Error).code != ErrNotFound { - return nil, err - } - // not finding updates means that there is a network error - // or that the feed really does not have updates - } - - request.Feed = *feed - - // if we already have an update, then find next epoch - if feedUpdate != nil { - request.Epoch = lookup.GetNextEpoch(feedUpdate.Epoch, now) - } else { - request.Epoch = lookup.GetFirstEpoch(now) - } - - return request, nil -} - -// Lookup retrieves a specific or latest feed update -// Lookup works differently depending on the configuration of `query` -// See the `query` documentation and helper functions: -// `NewQueryLatest` and `NewQuery` -func (h *Handler) Lookup(ctx context.Context, query *Query) (*cacheEntry, error) { - - timeLimit := query.TimeLimit - if timeLimit == 0 { // if time limit is set to zero, the user wants to get the latest update - timeLimit = TimestampProvider.Now().Time - } - - if query.Hint == lookup.NoClue { // try to use our cache - entry := h.get(&query.Feed) - if entry != nil && entry.Epoch.Time <= timeLimit { // avoid bad hints - query.Hint = entry.Epoch - } - } - - // we can't look for anything without a store - if h.chunkStore == nil { - return nil, NewError(ErrInit, "Call Handler.SetStore() before performing lookups") - } - - var id ID - id.Feed = query.Feed - var readCount int - - // Invoke the lookup engine. - // The callback will be called every time the lookup algorithm needs to guess - requestPtr, err := lookup.Lookup(timeLimit, query.Hint, func(epoch lookup.Epoch, now uint64) (interface{}, error) { - readCount++ - id.Epoch = epoch - ctx, cancel := context.WithTimeout(ctx, defaultRetrieveTimeout) - defer cancel() - - chunk, err := h.chunkStore.Get(ctx, id.Addr()) - if err != nil { // TODO: check for catastrophic errors other than chunk not found - return nil, nil - } - - var request Request - if err := request.fromChunk(chunk); err != nil { - return nil, nil - } - if request.Time <= timeLimit { - return &request, nil - } - return nil, nil - }) - if err != nil { - return nil, err - } - - log.Info(fmt.Sprintf("Feed lookup finished in %d lookups", readCount)) - - request, _ := requestPtr.(*Request) - if request == nil { - return nil, NewError(ErrNotFound, "no feed updates found") - } - return h.updateCache(request) - -} - -// update feed updates cache with specified content -func (h *Handler) updateCache(request *Request) (*cacheEntry, error) { - - updateAddr := request.Addr() - log.Trace("feed cache update", "topic", request.Topic.Hex(), "updateaddr", updateAddr, "epoch time", request.Epoch.Time, "epoch level", request.Epoch.Level) - - feedUpdate := h.get(&request.Feed) - if feedUpdate == nil { - feedUpdate = &cacheEntry{} - h.set(&request.Feed, feedUpdate) - } - - // update our rsrcs entry map - feedUpdate.lastKey = updateAddr - feedUpdate.Update = request.Update - feedUpdate.Reader = bytes.NewReader(feedUpdate.data) - return feedUpdate, nil -} - -// Update publishes a feed update -// Note that a feed update cannot span chunks, and thus has a MAX NET LENGTH 4096, INCLUDING update header data and signature. -// This results in a max payload of `maxUpdateDataLength` (check update.go for more details) -// An error will be returned if the total length of the chunk payload will exceed this limit. -// Update can only check if the caller is trying to overwrite the very last known version, otherwise it just puts the update -// on the network. -func (h *Handler) Update(ctx context.Context, r *Request) (updateAddr storage.Address, err error) { - - // we can't update anything without a store - if h.chunkStore == nil { - return nil, NewError(ErrInit, "Call Handler.SetStore() before updating") - } - - feedUpdate := h.get(&r.Feed) - if feedUpdate != nil && feedUpdate.Epoch.Equals(r.Epoch) { // This is the only cheap check we can do for sure - return nil, NewError(ErrInvalidValue, "A former update in this epoch is already known to exist") - } - - chunk, err := r.toChunk() // Serialize the update into a chunk. Fails if data is too big - if err != nil { - return nil, err - } - - // send the chunk - h.chunkStore.Put(ctx, chunk) - log.Trace("feed update", "updateAddr", r.idAddr, "epoch time", r.Epoch.Time, "epoch level", r.Epoch.Level, "data", chunk.Data()) - // update our feed updates map cache entry if the new update is older than the one we have, if we have it. - if feedUpdate != nil && r.Epoch.After(feedUpdate.Epoch) { - feedUpdate.Epoch = r.Epoch - feedUpdate.data = make([]byte, len(r.data)) - feedUpdate.lastKey = r.idAddr - copy(feedUpdate.data, r.data) - feedUpdate.Reader = bytes.NewReader(feedUpdate.data) - } - - return r.idAddr, nil -} - -// Retrieves the feed update cache value for the given nameHash -func (h *Handler) get(feed *Feed) *cacheEntry { - mapKey := feed.mapKey() - h.cacheLock.RLock() - defer h.cacheLock.RUnlock() - feedUpdate := h.cache[mapKey] - return feedUpdate -} - -// Sets the feed update cache value for the given feed -func (h *Handler) set(feed *Feed, feedUpdate *cacheEntry) { - mapKey := feed.mapKey() - h.cacheLock.Lock() - defer h.cacheLock.Unlock() - h.cache[mapKey] = feedUpdate -} diff --git a/swarm/storage/feed/handler_test.go b/swarm/storage/feed/handler_test.go deleted file mode 100644 index ea3773b32e70..000000000000 --- a/swarm/storage/feed/handler_test.go +++ /dev/null @@ -1,506 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package feed - -import ( - "bytes" - "context" - "flag" - "fmt" - "io/ioutil" - "os" - "testing" - "time" - - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/swarm/chunk" - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/ubiq/go-ubiq/swarm/storage/feed/lookup" -) - -var ( - loglevel = flag.Int("loglevel", 3, "loglevel") - startTime = Timestamp{ - Time: uint64(4200), - } - cleanF func() - subtopicName = "føø.bar" -) - -func init() { - flag.Parse() - log.Root().SetHandler(log.CallerFileHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(os.Stderr, log.TerminalFormat(true))))) -} - -// simulated timeProvider -type fakeTimeProvider struct { - currentTime uint64 -} - -func (f *fakeTimeProvider) Tick() { - f.currentTime++ -} - -func (f *fakeTimeProvider) Set(time uint64) { - f.currentTime = time -} - -func (f *fakeTimeProvider) FastForward(offset uint64) { - f.currentTime += offset -} - -func (f *fakeTimeProvider) Now() Timestamp { - return Timestamp{ - Time: f.currentTime, - } -} - -// make updates and retrieve them based on periods and versions -func TestFeedsHandler(t *testing.T) { - - // make fake timeProvider - clock := &fakeTimeProvider{ - currentTime: startTime.Time, // clock starts at t=4200 - } - - // signer containing private key - signer := newAliceSigner() - - feedsHandler, datadir, teardownTest, err := setupTest(clock, signer) - if err != nil { - t.Fatal(err) - } - defer teardownTest() - - // create a new feed - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - topic, _ := NewTopic("Mess with Swarm feeds code and see what ghost catches you", nil) - fd := Feed{ - Topic: topic, - User: signer.Address(), - } - - // data for updates: - updates := []string{ - "blinky", // t=4200 - "pinky", // t=4242 - "inky", // t=4284 - "clyde", // t=4285 - } - - request := NewFirstRequest(fd.Topic) // this timestamps the update at t = 4200 (start time) - chunkAddress := make(map[string]storage.Address) - data := []byte(updates[0]) - request.SetData(data) - if err := request.Sign(signer); err != nil { - t.Fatal(err) - } - chunkAddress[updates[0]], err = feedsHandler.Update(ctx, request) - if err != nil { - t.Fatal(err) - } - - // move the clock ahead 21 seconds - clock.FastForward(21) // t=4221 - - request, err = feedsHandler.NewRequest(ctx, &request.Feed) // this timestamps the update at t = 4221 - if err != nil { - t.Fatal(err) - } - if request.Epoch.Base() != 0 || request.Epoch.Level != lookup.HighestLevel-1 { - t.Fatalf("Suggested epoch BaseTime should be 0 and Epoch level should be %d", lookup.HighestLevel-1) - } - - request.Epoch.Level = lookup.HighestLevel // force level 25 instead of 24 to make it fail - data = []byte(updates[1]) - request.SetData(data) - if err := request.Sign(signer); err != nil { - t.Fatal(err) - } - chunkAddress[updates[1]], err = feedsHandler.Update(ctx, request) - if err == nil { - t.Fatal("Expected update to fail since an update in this epoch already exists") - } - - // move the clock ahead 21 seconds - clock.FastForward(21) // t=4242 - request, err = feedsHandler.NewRequest(ctx, &request.Feed) - if err != nil { - t.Fatal(err) - } - request.SetData(data) - if err := request.Sign(signer); err != nil { - t.Fatal(err) - } - chunkAddress[updates[1]], err = feedsHandler.Update(ctx, request) - if err != nil { - t.Fatal(err) - } - - // move the clock ahead 42 seconds - clock.FastForward(42) // t=4284 - request, err = feedsHandler.NewRequest(ctx, &request.Feed) - if err != nil { - t.Fatal(err) - } - data = []byte(updates[2]) - request.SetData(data) - if err := request.Sign(signer); err != nil { - t.Fatal(err) - } - chunkAddress[updates[2]], err = feedsHandler.Update(ctx, request) - if err != nil { - t.Fatal(err) - } - - // move the clock ahead 1 second - clock.FastForward(1) // t=4285 - request, err = feedsHandler.NewRequest(ctx, &request.Feed) - if err != nil { - t.Fatal(err) - } - if request.Epoch.Base() != 0 || request.Epoch.Level != 22 { - t.Fatalf("Expected epoch base time to be %d, got %d. Expected epoch level to be %d, got %d", 0, request.Epoch.Base(), 22, request.Epoch.Level) - } - data = []byte(updates[3]) - request.SetData(data) - - if err := request.Sign(signer); err != nil { - t.Fatal(err) - } - chunkAddress[updates[3]], err = feedsHandler.Update(ctx, request) - if err != nil { - t.Fatal(err) - } - - time.Sleep(time.Second) - feedsHandler.Close() - - // check we can retrieve the updates after close - clock.FastForward(2000) // t=6285 - - feedParams := &HandlerParams{} - - feedsHandler2, err := NewTestHandler(datadir, feedParams) - if err != nil { - t.Fatal(err) - } - - update2, err := feedsHandler2.Lookup(ctx, NewQueryLatest(&request.Feed, lookup.NoClue)) - if err != nil { - t.Fatal(err) - } - - // last update should be "clyde" - if !bytes.Equal(update2.data, []byte(updates[len(updates)-1])) { - t.Fatalf("feed update data was %v, expected %v", string(update2.data), updates[len(updates)-1]) - } - if update2.Level != 22 { - t.Fatalf("feed update epoch level was %d, expected 22", update2.Level) - } - if update2.Base() != 0 { - t.Fatalf("feed update epoch base time was %d, expected 0", update2.Base()) - } - log.Debug("Latest lookup", "epoch base time", update2.Base(), "epoch level", update2.Level, "data", update2.data) - - // specific point in time - update, err := feedsHandler2.Lookup(ctx, NewQuery(&request.Feed, 4284, lookup.NoClue)) - if err != nil { - t.Fatal(err) - } - // check data - if !bytes.Equal(update.data, []byte(updates[2])) { - t.Fatalf("feed update data (historical) was %v, expected %v", string(update2.data), updates[2]) - } - log.Debug("Historical lookup", "epoch base time", update2.Base(), "epoch level", update2.Level, "data", update2.data) - - // beyond the first should yield an error - update, err = feedsHandler2.Lookup(ctx, NewQuery(&request.Feed, startTime.Time-1, lookup.NoClue)) - if err == nil { - t.Fatalf("expected previous to fail, returned epoch %s data %v", update.Epoch.String(), update.data) - } - -} - -const Day = 60 * 60 * 24 -const Year = Day * 365 -const Month = Day * 30 - -func generateData(x uint64) []byte { - return []byte(fmt.Sprintf("%d", x)) -} - -func TestSparseUpdates(t *testing.T) { - - // make fake timeProvider - timeProvider := &fakeTimeProvider{ - currentTime: startTime.Time, - } - - // signer containing private key - signer := newAliceSigner() - - rh, datadir, teardownTest, err := setupTest(timeProvider, signer) - if err != nil { - t.Fatal(err) - } - defer teardownTest() - defer os.RemoveAll(datadir) - - // create a new feed - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - topic, _ := NewTopic("Very slow updates", nil) - fd := Feed{ - Topic: topic, - User: signer.Address(), - } - - // publish one update every 5 years since Unix 0 until today - today := uint64(1533799046) - var epoch lookup.Epoch - var lastUpdateTime uint64 - for T := uint64(0); T < today; T += 5 * Year { - request := NewFirstRequest(fd.Topic) - request.Epoch = lookup.GetNextEpoch(epoch, T) - request.data = generateData(T) // this generates some data that depends on T, so we can check later - request.Sign(signer) - if err != nil { - t.Fatal(err) - } - - if _, err := rh.Update(ctx, request); err != nil { - t.Fatal(err) - } - epoch = request.Epoch - lastUpdateTime = T - } - - query := NewQuery(&fd, today, lookup.NoClue) - - _, err = rh.Lookup(ctx, query) - if err != nil { - t.Fatal(err) - } - - _, content, err := rh.GetContent(&fd) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(generateData(lastUpdateTime), content) { - t.Fatalf("Expected to recover last written value %d, got %s", lastUpdateTime, string(content)) - } - - // lookup the closest update to 35*Year + 6* Month (~ June 2005): - // it should find the update we put on 35*Year, since we were updating every 5 years. - - query.TimeLimit = 35*Year + 6*Month - - _, err = rh.Lookup(ctx, query) - if err != nil { - t.Fatal(err) - } - - _, content, err = rh.GetContent(&fd) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(generateData(35*Year), content) { - t.Fatalf("Expected to recover %d, got %s", 35*Year, string(content)) - } -} - -func TestValidator(t *testing.T) { - - // make fake timeProvider - timeProvider := &fakeTimeProvider{ - currentTime: startTime.Time, - } - - // signer containing private key. Alice will be the good girl - signer := newAliceSigner() - - // set up sim timeProvider - rh, _, teardownTest, err := setupTest(timeProvider, signer) - if err != nil { - t.Fatal(err) - } - defer teardownTest() - - // create new feed - topic, _ := NewTopic(subtopicName, nil) - fd := Feed{ - Topic: topic, - User: signer.Address(), - } - mr := NewFirstRequest(fd.Topic) - - // chunk with address - data := []byte("foo") - mr.SetData(data) - if err := mr.Sign(signer); err != nil { - t.Fatalf("sign fail: %v", err) - } - - chunk, err := mr.toChunk() - if err != nil { - t.Fatal(err) - } - if !rh.Validate(chunk) { - t.Fatal("Chunk validator fail on update chunk") - } - - address := chunk.Address() - // mess with the address - address[0] = 11 - address[15] = 99 - - if rh.Validate(storage.NewChunk(address, chunk.Data())) { - t.Fatal("Expected Validate to fail with false chunk address") - } -} - -// tests that the content address validator correctly checks the data -// tests that feed update chunks are passed through content address validator -// there is some redundancy in this test as it also tests content addressed chunks, -// which should be evaluated as invalid chunks by this validator -func TestValidatorInStore(t *testing.T) { - - // make fake timeProvider - TimestampProvider = &fakeTimeProvider{ - currentTime: startTime.Time, - } - - // signer containing private key - signer := newAliceSigner() - - // set up localstore - datadir, err := ioutil.TempDir("", "storage-testfeedsvalidator") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(datadir) - - handlerParams := storage.NewDefaultLocalStoreParams() - handlerParams.Init(datadir) - store, err := storage.NewLocalStore(handlerParams, nil) - if err != nil { - t.Fatal(err) - } - - // set up Swarm feeds handler and add is as a validator to the localstore - fhParams := &HandlerParams{} - fh := NewHandler(fhParams) - store.Validators = append(store.Validators, fh) - - // create content addressed chunks, one good, one faulty - chunks := storage.GenerateRandomChunks(chunk.DefaultSize, 2) - goodChunk := chunks[0] - badChunk := storage.NewChunk(chunks[1].Address(), goodChunk.Data()) - - topic, _ := NewTopic("xyzzy", nil) - fd := Feed{ - Topic: topic, - User: signer.Address(), - } - - // create a feed update chunk with correct publickey - id := ID{ - Epoch: lookup.Epoch{Time: 42, - Level: 1, - }, - Feed: fd, - } - - updateAddr := id.Addr() - data := []byte("bar") - - r := new(Request) - r.idAddr = updateAddr - r.Update.ID = id - r.data = data - - r.Sign(signer) - - uglyChunk, err := r.toChunk() - if err != nil { - t.Fatal(err) - } - - // put the chunks in the store and check their error status - err = store.Put(context.Background(), goodChunk) - if err == nil { - t.Fatal("expected error on good content address chunk with feed update validator only, but got nil") - } - err = store.Put(context.Background(), badChunk) - if err == nil { - t.Fatal("expected error on bad content address chunk with feed update validator only, but got nil") - } - err = store.Put(context.Background(), uglyChunk) - if err != nil { - t.Fatalf("expected no error on feed update chunk with feed update validator only, but got: %s", err) - } -} - -// create rpc and feeds Handler -func setupTest(timeProvider timestampProvider, signer Signer) (fh *TestHandler, datadir string, teardown func(), err error) { - - var fsClean func() - var rpcClean func() - cleanF = func() { - if fsClean != nil { - fsClean() - } - if rpcClean != nil { - rpcClean() - } - } - - // temp datadir - datadir, err = ioutil.TempDir("", "fh") - if err != nil { - return nil, "", nil, err - } - fsClean = func() { - os.RemoveAll(datadir) - } - - TimestampProvider = timeProvider - fhParams := &HandlerParams{} - fh, err = NewTestHandler(datadir, fhParams) - return fh, datadir, cleanF, err -} - -func newAliceSigner() *GenericSigner { - privKey, _ := crypto.HexToECDSA("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") - return NewGenericSigner(privKey) -} - -func newBobSigner() *GenericSigner { - privKey, _ := crypto.HexToECDSA("accedeaccedeaccedeaccedeaccedeaccedeaccedeaccedeaccedeaccedecaca") - return NewGenericSigner(privKey) -} - -func newCharlieSigner() *GenericSigner { - privKey, _ := crypto.HexToECDSA("facadefacadefacadefacadefacadefacadefacadefacadefacadefacadefaca") - return NewGenericSigner(privKey) -} diff --git a/swarm/storage/feed/id.go b/swarm/storage/feed/id.go deleted file mode 100644 index 01386bad22ca..000000000000 --- a/swarm/storage/feed/id.go +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package feed - -import ( - "fmt" - "hash" - "strconv" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/swarm/storage/feed/lookup" - - "github.com/ubiq/go-ubiq/swarm/storage" -) - -// ID uniquely identifies an update on the network. -type ID struct { - Feed `json:"feed"` - lookup.Epoch `json:"epoch"` -} - -// ID layout: -// Feed feedLength bytes -// Epoch EpochLength -const idLength = feedLength + lookup.EpochLength - -// Addr calculates the feed update chunk address corresponding to this ID -func (u *ID) Addr() (updateAddr storage.Address) { - serializedData := make([]byte, idLength) - var cursor int - u.Feed.binaryPut(serializedData[cursor : cursor+feedLength]) - cursor += feedLength - - eid := u.Epoch.ID() - copy(serializedData[cursor:cursor+lookup.EpochLength], eid[:]) - - hasher := hashPool.Get().(hash.Hash) - defer hashPool.Put(hasher) - hasher.Reset() - hasher.Write(serializedData) - return hasher.Sum(nil) -} - -// binaryPut serializes this instance into the provided slice -func (u *ID) binaryPut(serializedData []byte) error { - if len(serializedData) != idLength { - return NewErrorf(ErrInvalidValue, "Incorrect slice size to serialize ID. Expected %d, got %d", idLength, len(serializedData)) - } - var cursor int - if err := u.Feed.binaryPut(serializedData[cursor : cursor+feedLength]); err != nil { - return err - } - cursor += feedLength - - epochBytes, err := u.Epoch.MarshalBinary() - if err != nil { - return err - } - copy(serializedData[cursor:cursor+lookup.EpochLength], epochBytes[:]) - cursor += lookup.EpochLength - - return nil -} - -// binaryLength returns the expected size of this structure when serialized -func (u *ID) binaryLength() int { - return idLength -} - -// binaryGet restores the current instance from the information contained in the passed slice -func (u *ID) binaryGet(serializedData []byte) error { - if len(serializedData) != idLength { - return NewErrorf(ErrInvalidValue, "Incorrect slice size to read ID. Expected %d, got %d", idLength, len(serializedData)) - } - - var cursor int - if err := u.Feed.binaryGet(serializedData[cursor : cursor+feedLength]); err != nil { - return err - } - cursor += feedLength - - if err := u.Epoch.UnmarshalBinary(serializedData[cursor : cursor+lookup.EpochLength]); err != nil { - return err - } - cursor += lookup.EpochLength - - return nil -} - -// FromValues deserializes this instance from a string key-value store -// useful to parse query strings -func (u *ID) FromValues(values Values) error { - level, _ := strconv.ParseUint(values.Get("level"), 10, 32) - u.Epoch.Level = uint8(level) - u.Epoch.Time, _ = strconv.ParseUint(values.Get("time"), 10, 64) - - if u.Feed.User == (common.Address{}) { - return u.Feed.FromValues(values) - } - return nil -} - -// AppendValues serializes this structure into the provided string key-value store -// useful to build query strings -func (u *ID) AppendValues(values Values) { - values.Set("level", fmt.Sprintf("%d", u.Epoch.Level)) - values.Set("time", fmt.Sprintf("%d", u.Epoch.Time)) - u.Feed.AppendValues(values) -} diff --git a/swarm/storage/feed/id_test.go b/swarm/storage/feed/id_test.go deleted file mode 100644 index ad4ad98c7c68..000000000000 --- a/swarm/storage/feed/id_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package feed - -import ( - "testing" - - "github.com/ubiq/go-ubiq/swarm/storage/feed/lookup" -) - -func getTestID() *ID { - return &ID{ - Feed: *getTestFeed(), - Epoch: lookup.GetFirstEpoch(1000), - } -} - -func TestIDAddr(t *testing.T) { - id := getTestID() - updateAddr := id.Addr() - compareByteSliceToExpectedHex(t, "updateAddr", updateAddr, "0x8b24583ec293e085f4c78aaee66d1bc5abfb8b4233304d14a349afa57af2a783") -} - -func TestIDSerializer(t *testing.T) { - testBinarySerializerRecovery(t, getTestID(), "0x776f726c64206e657773207265706f72742c20657665727920686f7572000000876a8936a7cd0b79ef0735ad0896c1afe278781ce803000000000019") -} - -func TestIDLengthCheck(t *testing.T) { - testBinarySerializerLengthCheck(t, getTestID()) -} diff --git a/swarm/storage/feed/lookup/epoch.go b/swarm/storage/feed/lookup/epoch.go deleted file mode 100644 index bafe9547798e..000000000000 --- a/swarm/storage/feed/lookup/epoch.go +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package lookup - -import ( - "encoding/binary" - "errors" - "fmt" -) - -// Epoch represents a time slot at a particular frequency level -type Epoch struct { - Time uint64 `json:"time"` // Time stores the time at which the update or lookup takes place - Level uint8 `json:"level"` // Level indicates the frequency level as the exponent of a power of 2 -} - -// EpochID is a unique identifier for an Epoch, based on its level and base time. -type EpochID [8]byte - -// EpochLength stores the serialized binary length of an Epoch -const EpochLength = 8 - -// MaxTime contains the highest possible time value an Epoch can handle -const MaxTime uint64 = (1 << 56) - 1 - -// Base returns the base time of the Epoch -func (e *Epoch) Base() uint64 { - return getBaseTime(e.Time, e.Level) -} - -// ID Returns the unique identifier of this epoch -func (e *Epoch) ID() EpochID { - base := e.Base() - var id EpochID - binary.LittleEndian.PutUint64(id[:], base) - id[7] = e.Level - return id -} - -// MarshalBinary implements the encoding.BinaryMarshaller interface -func (e *Epoch) MarshalBinary() (data []byte, err error) { - b := make([]byte, 8) - binary.LittleEndian.PutUint64(b[:], e.Time) - b[7] = e.Level - return b, nil -} - -// UnmarshalBinary implements the encoding.BinaryUnmarshaller interface -func (e *Epoch) UnmarshalBinary(data []byte) error { - if len(data) != EpochLength { - return errors.New("Invalid data unmarshalling Epoch") - } - b := make([]byte, 8) - copy(b, data) - e.Level = b[7] - b[7] = 0 - e.Time = binary.LittleEndian.Uint64(b) - return nil -} - -// After returns true if this epoch occurs later or exactly at the other epoch. -func (e *Epoch) After(epoch Epoch) bool { - if e.Time == epoch.Time { - return e.Level < epoch.Level - } - return e.Time >= epoch.Time -} - -// Equals compares two epochs and returns true if they refer to the same time period. -func (e *Epoch) Equals(epoch Epoch) bool { - return e.Level == epoch.Level && e.Base() == epoch.Base() -} - -// String implements the Stringer interface. -func (e *Epoch) String() string { - return fmt.Sprintf("Epoch{Time:%d, Level:%d}", e.Time, e.Level) -} diff --git a/swarm/storage/feed/lookup/epoch_test.go b/swarm/storage/feed/lookup/epoch_test.go deleted file mode 100644 index be60f905866b..000000000000 --- a/swarm/storage/feed/lookup/epoch_test.go +++ /dev/null @@ -1,57 +0,0 @@ -package lookup_test - -import ( - "testing" - - "github.com/ubiq/go-ubiq/swarm/storage/feed/lookup" -) - -func TestMarshallers(t *testing.T) { - - for i := uint64(1); i < lookup.MaxTime; i *= 3 { - e := lookup.Epoch{ - Time: i, - Level: uint8(i % 20), - } - b, err := e.MarshalBinary() - if err != nil { - t.Fatal(err) - } - var e2 lookup.Epoch - if err := e2.UnmarshalBinary(b); err != nil { - t.Fatal(err) - } - if e != e2 { - t.Fatal("Expected unmarshalled epoch to be equal to marshalled onet.Fatal(err)") - } - } - -} - -func TestAfter(t *testing.T) { - a := lookup.Epoch{ - Time: 5, - Level: 3, - } - b := lookup.Epoch{ - Time: 6, - Level: 3, - } - c := lookup.Epoch{ - Time: 6, - Level: 4, - } - - if !b.After(a) { - t.Fatal("Expected 'after' to be true, got false") - } - - if b.After(b) { - t.Fatal("Expected 'after' to be false when both epochs are identical, got true") - } - - if !b.After(c) { - t.Fatal("Expected 'after' to be true when both epochs have the same time but the level is lower in the first one, but got false") - } - -} diff --git a/swarm/storage/feed/lookup/lookup.go b/swarm/storage/feed/lookup/lookup.go deleted file mode 100644 index 2f862d81c470..000000000000 --- a/swarm/storage/feed/lookup/lookup.go +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -/* -Package lookup defines feed lookup algorithms and provides tools to place updates -so they can be found -*/ -package lookup - -const maxuint64 = ^uint64(0) - -// LowestLevel establishes the frequency resolution of the lookup algorithm as a power of 2. -const LowestLevel uint8 = 0 // default is 0 (1 second) - -// HighestLevel sets the lowest frequency the algorithm will operate at, as a power of 2. -// 25 -> 2^25 equals to roughly one year. -const HighestLevel = 25 // default is 25 (~1 year) - -// DefaultLevel sets what level will be chosen to search when there is no hint -const DefaultLevel = HighestLevel - -//Algorithm is the function signature of a lookup algorithm -type Algorithm func(now uint64, hint Epoch, read ReadFunc) (value interface{}, err error) - -// Lookup finds the update with the highest timestamp that is smaller or equal than 'now' -// It takes a hint which should be the epoch where the last known update was -// If you don't know in what epoch the last update happened, simply submit lookup.NoClue -// read() will be called on each lookup attempt -// Returns an error only if read() returns an error -// Returns nil if an update was not found -var Lookup Algorithm = FluzCapacitorAlgorithm - -// ReadFunc is a handler called by Lookup each time it attempts to find a value -// It should return if a value is not found -// It should return if a value is found, but its timestamp is higher than "now" -// It should only return an error in case the handler wants to stop the -// lookup process entirely. -type ReadFunc func(epoch Epoch, now uint64) (interface{}, error) - -// NoClue is a hint that can be provided when the Lookup caller does not have -// a clue about where the last update may be -var NoClue = Epoch{} - -// getBaseTime returns the epoch base time of the given -// time and level -func getBaseTime(t uint64, level uint8) uint64 { - return t & (maxuint64 << level) -} - -// Hint creates a hint based only on the last known update time -func Hint(last uint64) Epoch { - return Epoch{ - Time: last, - Level: DefaultLevel, - } -} - -// GetNextLevel returns the frequency level a next update should be placed at, provided where -// the last update was and what time it is now. -// This is the first nonzero bit of the XOR of 'last' and 'now', counting from the highest significant bit -// but limited to not return a level that is smaller than the last-1 -func GetNextLevel(last Epoch, now uint64) uint8 { - // First XOR the last epoch base time with the current clock. - // This will set all the common most significant bits to zero. - mix := (last.Base() ^ now) - - // Then, make sure we stop the below loop before one level below the current, by setting - // that level's bit to 1. - // If the next level is lower than the current one, it must be exactly level-1 and not lower. - mix |= (1 << (last.Level - 1)) - - // if the last update was more than 2^highestLevel seconds ago, choose the highest level - if mix > (maxuint64 >> (64 - HighestLevel - 1)) { - return HighestLevel - } - - // set up a mask to scan for nonzero bits, starting at the highest level - mask := uint64(1 << (HighestLevel)) - - for i := uint8(HighestLevel); i > LowestLevel; i-- { - if mix&mask != 0 { // if we find a nonzero bit, this is the level the next update should be at. - return i - } - mask = mask >> 1 // move our bit one position to the right - } - return 0 -} - -// GetNextEpoch returns the epoch where the next update should be located -// according to where the previous update was -// and what time it is now. -func GetNextEpoch(last Epoch, now uint64) Epoch { - if last == NoClue { - return GetFirstEpoch(now) - } - level := GetNextLevel(last, now) - return Epoch{ - Level: level, - Time: now, - } -} - -// GetFirstEpoch returns the epoch where the first update should be located -// based on what time it is now. -func GetFirstEpoch(now uint64) Epoch { - return Epoch{Level: HighestLevel, Time: now} -} - -var worstHint = Epoch{Time: 0, Level: 63} - -// FluzCapacitorAlgorithm works by narrowing the epoch search area if an update is found -// going back and forth in time -// First, it will attempt to find an update where it should be now if the hint was -// really the last update. If that lookup fails, then the last update must be either the hint itself -// or the epochs right below. If however, that lookup succeeds, then the update must be -// that one or within the epochs right below. -// see the guide for a more graphical representation -func FluzCapacitorAlgorithm(now uint64, hint Epoch, read ReadFunc) (value interface{}, err error) { - var lastFound interface{} - var epoch Epoch - if hint == NoClue { - hint = worstHint - } - - t := now - - for { - epoch = GetNextEpoch(hint, t) - value, err = read(epoch, now) - if err != nil { - return nil, err - } - if value != nil { - lastFound = value - if epoch.Level == LowestLevel || epoch.Equals(hint) { - return value, nil - } - hint = epoch - continue - } - if epoch.Base() == hint.Base() { - if lastFound != nil { - return lastFound, nil - } - // we have reached the hint itself - if hint == worstHint { - return nil, nil - } - // check it out - value, err = read(hint, now) - if err != nil { - return nil, err - } - if value != nil { - return value, nil - } - // bad hint. - epoch = hint - hint = worstHint - } - base := epoch.Base() - if base == 0 { - return nil, nil - } - t = base - 1 - } -} diff --git a/swarm/storage/feed/lookup/lookup_test.go b/swarm/storage/feed/lookup/lookup_test.go deleted file mode 100644 index c7cd18da55c5..000000000000 --- a/swarm/storage/feed/lookup/lookup_test.go +++ /dev/null @@ -1,414 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package lookup_test - -import ( - "fmt" - "math/rand" - "testing" - - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/storage/feed/lookup" -) - -type Data struct { - Payload uint64 - Time uint64 -} - -type Store map[lookup.EpochID]*Data - -func write(store Store, epoch lookup.Epoch, value *Data) { - log.Debug("Write: %d-%d, value='%d'\n", epoch.Base(), epoch.Level, value.Payload) - store[epoch.ID()] = value -} - -func update(store Store, last lookup.Epoch, now uint64, value *Data) lookup.Epoch { - epoch := lookup.GetNextEpoch(last, now) - - write(store, epoch, value) - - return epoch -} - -const Day = 60 * 60 * 24 -const Year = Day * 365 -const Month = Day * 30 - -func makeReadFunc(store Store, counter *int) lookup.ReadFunc { - return func(epoch lookup.Epoch, now uint64) (interface{}, error) { - *counter++ - data := store[epoch.ID()] - var valueStr string - if data != nil { - valueStr = fmt.Sprintf("%d", data.Payload) - } - log.Debug("Read: %d-%d, value='%s'\n", epoch.Base(), epoch.Level, valueStr) - if data != nil && data.Time <= now { - return data, nil - } - return nil, nil - } -} - -func TestLookup(t *testing.T) { - - store := make(Store) - readCount := 0 - readFunc := makeReadFunc(store, &readCount) - - // write an update every month for 12 months 3 years ago and then silence for two years - now := uint64(1533799046) - var epoch lookup.Epoch - - var lastData *Data - for i := uint64(0); i < 12; i++ { - t := uint64(now - Year*3 + i*Month) - data := Data{ - Payload: t, //our "payload" will be the timestamp itself. - Time: t, - } - epoch = update(store, epoch, t, &data) - lastData = &data - } - - // try to get the last value - - value, err := lookup.Lookup(now, lookup.NoClue, readFunc) - if err != nil { - t.Fatal(err) - } - - readCountWithoutHint := readCount - - if value != lastData { - t.Fatalf("Expected lookup to return the last written value: %v. Got %v", lastData, value) - } - - // reset the read count for the next test - readCount = 0 - // Provide a hint to get a faster lookup. In particular, we give the exact location of the last update - value, err = lookup.Lookup(now, epoch, readFunc) - if err != nil { - t.Fatal(err) - } - - if value != lastData { - t.Fatalf("Expected lookup to return the last written value: %v. Got %v", lastData, value) - } - - if readCount > readCountWithoutHint { - t.Fatalf("Expected lookup to complete with fewer or same reads than %d since we provided a hint. Did %d reads.", readCountWithoutHint, readCount) - } - - // try to get an intermediate value - // if we look for a value in now - Year*3 + 6*Month, we should get that value - // Since the "payload" is the timestamp itself, we can check this. - - expectedTime := now - Year*3 + 6*Month - - value, err = lookup.Lookup(expectedTime, lookup.NoClue, readFunc) - if err != nil { - t.Fatal(err) - } - - data, ok := value.(*Data) - - if !ok { - t.Fatal("Expected value to contain data") - } - - if data.Time != expectedTime { - t.Fatalf("Expected value timestamp to be %d, got %d", data.Time, expectedTime) - } - -} - -func TestOneUpdateAt0(t *testing.T) { - - store := make(Store) - readCount := 0 - - readFunc := makeReadFunc(store, &readCount) - now := uint64(1533903729) - - var epoch lookup.Epoch - data := Data{ - Payload: 79, - Time: 0, - } - update(store, epoch, 0, &data) - - value, err := lookup.Lookup(now, lookup.NoClue, readFunc) - if err != nil { - t.Fatal(err) - } - if value != &data { - t.Fatalf("Expected lookup to return the last written value: %v. Got %v", data, value) - } -} - -// Tests the update is found even when a bad hint is given -func TestBadHint(t *testing.T) { - - store := make(Store) - readCount := 0 - - readFunc := makeReadFunc(store, &readCount) - now := uint64(1533903729) - - var epoch lookup.Epoch - data := Data{ - Payload: 79, - Time: 0, - } - - // place an update for t=1200 - update(store, epoch, 1200, &data) - - // come up with some evil hint - badHint := lookup.Epoch{ - Level: 18, - Time: 1200000000, - } - - value, err := lookup.Lookup(now, badHint, readFunc) - if err != nil { - t.Fatal(err) - } - if value != &data { - t.Fatalf("Expected lookup to return the last written value: %v. Got %v", data, value) - } -} - -func TestLookupFail(t *testing.T) { - - store := make(Store) - readCount := 0 - - readFunc := makeReadFunc(store, &readCount) - now := uint64(1533903729) - - // don't write anything and try to look up. - // we're testing we don't get stuck in a loop - - value, err := lookup.Lookup(now, lookup.NoClue, readFunc) - if err != nil { - t.Fatal(err) - } - if value != nil { - t.Fatal("Expected value to be nil, since the update should've failed") - } - - expectedReads := now/(1< readCountWithoutHint { - t.Fatalf("Expected lookup to complete with fewer or equal reads than %d since we provided a hint. Did %d reads.", readCountWithoutHint, readCount) - } - - for i := uint64(0); i <= 994; i++ { - T := uint64(now - 1000 + i) // update every second for the last 1000 seconds - value, err := lookup.Lookup(T, lookup.NoClue, readFunc) - if err != nil { - t.Fatal(err) - } - data, _ := value.(*Data) - if data == nil { - t.Fatalf("Expected lookup to return %d, got nil", T) - } - if data.Payload != T { - t.Fatalf("Expected lookup to return %d, got %d", T, data.Time) - } - } -} - -func TestSparseUpdates(t *testing.T) { - - store := make(Store) - readCount := 0 - readFunc := makeReadFunc(store, &readCount) - - // write an update every 5 years 3 times starting in Jan 1st 1970 and then silence - - now := uint64(1533799046) - var epoch lookup.Epoch - - var lastData *Data - for i := uint64(0); i < 5; i++ { - T := uint64(Year * 5 * i) // write an update every 5 years 3 times starting in Jan 1st 1970 and then silence - data := Data{ - Payload: T, //our "payload" will be the timestamp itself. - Time: T, - } - epoch = update(store, epoch, T, &data) - lastData = &data - } - - // try to get the last value - - value, err := lookup.Lookup(now, lookup.NoClue, readFunc) - if err != nil { - t.Fatal(err) - } - - readCountWithoutHint := readCount - - if value != lastData { - t.Fatalf("Expected lookup to return the last written value: %v. Got %v", lastData, value) - } - - // reset the read count for the next test - readCount = 0 - // Provide a hint to get a faster lookup. In particular, we give the exact location of the last update - value, err = lookup.Lookup(now, epoch, readFunc) - if err != nil { - t.Fatal(err) - } - - if value != lastData { - t.Fatalf("Expected lookup to return the last written value: %v. Got %v", lastData, value) - } - - if readCount > readCountWithoutHint { - t.Fatalf("Expected lookup to complete with fewer reads than %d since we provided a hint. Did %d reads.", readCountWithoutHint, readCount) - } - -} - -// testG will hold precooked test results -// fields are abbreviated to reduce the size of the literal below -type testG struct { - e lookup.Epoch // last - n uint64 // next level - x uint8 // expected result -} - -// test cases -var testGetNextLevelCases []testG = []testG{{e: lookup.Epoch{Time: 989875233, Level: 12}, n: 989875233, x: 11}, {e: lookup.Epoch{Time: 995807650, Level: 18}, n: 995598156, x: 19}, {e: lookup.Epoch{Time: 969167082, Level: 0}, n: 968990357, x: 18}, {e: lookup.Epoch{Time: 993087628, Level: 14}, n: 992987044, x: 20}, {e: lookup.Epoch{Time: 963364631, Level: 20}, n: 963364630, x: 19}, {e: lookup.Epoch{Time: 963497510, Level: 16}, n: 963370732, x: 18}, {e: lookup.Epoch{Time: 955421349, Level: 22}, n: 955421348, x: 21}, {e: lookup.Epoch{Time: 968220379, Level: 15}, n: 968220378, x: 14}, {e: lookup.Epoch{Time: 939129014, Level: 6}, n: 939128771, x: 11}, {e: lookup.Epoch{Time: 907847903, Level: 6}, n: 907791833, x: 18}, {e: lookup.Epoch{Time: 910835564, Level: 15}, n: 910835564, x: 14}, {e: lookup.Epoch{Time: 913578333, Level: 22}, n: 881808431, x: 25}, {e: lookup.Epoch{Time: 895818460, Level: 3}, n: 895818132, x: 9}, {e: lookup.Epoch{Time: 903843025, Level: 24}, n: 895609561, x: 23}, {e: lookup.Epoch{Time: 877889433, Level: 13}, n: 877877093, x: 15}, {e: lookup.Epoch{Time: 901450396, Level: 10}, n: 901450058, x: 9}, {e: lookup.Epoch{Time: 925179910, Level: 3}, n: 925168393, x: 16}, {e: lookup.Epoch{Time: 913485477, Level: 21}, n: 913485476, x: 20}, {e: lookup.Epoch{Time: 924462991, Level: 18}, n: 924462990, x: 17}, {e: lookup.Epoch{Time: 941175128, Level: 13}, n: 941175127, x: 12}, {e: lookup.Epoch{Time: 920126583, Level: 3}, n: 920100782, x: 19}, {e: lookup.Epoch{Time: 932403200, Level: 9}, n: 932279891, x: 17}, {e: lookup.Epoch{Time: 948284931, Level: 2}, n: 948284921, x: 9}, {e: lookup.Epoch{Time: 953540997, Level: 7}, n: 950547986, x: 22}, {e: lookup.Epoch{Time: 926639837, Level: 18}, n: 918608882, x: 24}, {e: lookup.Epoch{Time: 954637598, Level: 1}, n: 954578761, x: 17}, {e: lookup.Epoch{Time: 943482981, Level: 10}, n: 942924151, x: 19}, {e: lookup.Epoch{Time: 963580771, Level: 7}, n: 963580771, x: 6}, {e: lookup.Epoch{Time: 993744930, Level: 7}, n: 993690858, x: 16}, {e: lookup.Epoch{Time: 1018890213, Level: 12}, n: 1018890212, x: 11}, {e: lookup.Epoch{Time: 1030309411, Level: 2}, n: 1030309227, x: 9}, {e: lookup.Epoch{Time: 1063204997, Level: 20}, n: 1063204996, x: 19}, {e: lookup.Epoch{Time: 1094340832, Level: 6}, n: 1094340633, x: 7}, {e: lookup.Epoch{Time: 1077880597, Level: 10}, n: 1075914292, x: 20}, {e: lookup.Epoch{Time: 1051114957, Level: 18}, n: 1051114957, x: 17}, {e: lookup.Epoch{Time: 1045649701, Level: 22}, n: 1045649700, x: 21}, {e: lookup.Epoch{Time: 1066198885, Level: 14}, n: 1066198884, x: 13}, {e: lookup.Epoch{Time: 1053231952, Level: 1}, n: 1053210845, x: 16}, {e: lookup.Epoch{Time: 1068763404, Level: 14}, n: 1068675428, x: 18}, {e: lookup.Epoch{Time: 1039042173, Level: 15}, n: 1038973110, x: 17}, {e: lookup.Epoch{Time: 1050747636, Level: 6}, n: 1050747364, x: 9}, {e: lookup.Epoch{Time: 1030034434, Level: 23}, n: 1030034433, x: 22}, {e: lookup.Epoch{Time: 1003783425, Level: 18}, n: 1003783424, x: 17}, {e: lookup.Epoch{Time: 988163976, Level: 15}, n: 988084064, x: 17}, {e: lookup.Epoch{Time: 1007222377, Level: 15}, n: 1007222377, x: 14}, {e: lookup.Epoch{Time: 1001211375, Level: 13}, n: 1001208178, x: 14}, {e: lookup.Epoch{Time: 997623199, Level: 8}, n: 997623198, x: 7}, {e: lookup.Epoch{Time: 1026283830, Level: 10}, n: 1006681704, x: 24}, {e: lookup.Epoch{Time: 1019421907, Level: 20}, n: 1019421906, x: 19}, {e: lookup.Epoch{Time: 1043154306, Level: 16}, n: 1043108343, x: 16}, {e: lookup.Epoch{Time: 1075643767, Level: 17}, n: 1075325898, x: 18}, {e: lookup.Epoch{Time: 1043726309, Level: 20}, n: 1043726308, x: 19}, {e: lookup.Epoch{Time: 1056415324, Level: 17}, n: 1056415324, x: 16}, {e: lookup.Epoch{Time: 1088650219, Level: 13}, n: 1088650218, x: 12}, {e: lookup.Epoch{Time: 1088551662, Level: 7}, n: 1088543355, x: 13}, {e: lookup.Epoch{Time: 1069667265, Level: 6}, n: 1069667075, x: 7}, {e: lookup.Epoch{Time: 1079145970, Level: 18}, n: 1079145969, x: 17}, {e: lookup.Epoch{Time: 1083338876, Level: 7}, n: 1083338875, x: 6}, {e: lookup.Epoch{Time: 1051581086, Level: 4}, n: 1051568869, x: 14}, {e: lookup.Epoch{Time: 1028430882, Level: 4}, n: 1028430864, x: 5}, {e: lookup.Epoch{Time: 1057356462, Level: 1}, n: 1057356417, x: 5}, {e: lookup.Epoch{Time: 1033104266, Level: 0}, n: 1033097479, x: 13}, {e: lookup.Epoch{Time: 1031391367, Level: 11}, n: 1031387304, x: 14}, {e: lookup.Epoch{Time: 1049781164, Level: 15}, n: 1049781163, x: 14}, {e: lookup.Epoch{Time: 1027271628, Level: 12}, n: 1027271627, x: 11}, {e: lookup.Epoch{Time: 1057270560, Level: 23}, n: 1057270560, x: 22}, {e: lookup.Epoch{Time: 1047501317, Level: 15}, n: 1047501317, x: 14}, {e: lookup.Epoch{Time: 1058349035, Level: 11}, n: 1045175573, x: 24}, {e: lookup.Epoch{Time: 1057396147, Level: 20}, n: 1057396147, x: 19}, {e: lookup.Epoch{Time: 1048906375, Level: 18}, n: 1039616919, x: 25}, {e: lookup.Epoch{Time: 1074294831, Level: 20}, n: 1074294831, x: 19}, {e: lookup.Epoch{Time: 1088946052, Level: 1}, n: 1088917364, x: 14}, {e: lookup.Epoch{Time: 1112337595, Level: 17}, n: 1111008110, x: 22}, {e: lookup.Epoch{Time: 1099990284, Level: 5}, n: 1099968370, x: 15}, {e: lookup.Epoch{Time: 1087036441, Level: 16}, n: 1053967855, x: 25}, {e: lookup.Epoch{Time: 1069225185, Level: 8}, n: 1069224660, x: 10}, {e: lookup.Epoch{Time: 1057505479, Level: 9}, n: 1057505170, x: 14}, {e: lookup.Epoch{Time: 1072381377, Level: 12}, n: 1065950959, x: 22}, {e: lookup.Epoch{Time: 1093887139, Level: 8}, n: 1093863305, x: 14}, {e: lookup.Epoch{Time: 1082366510, Level: 24}, n: 1082366510, x: 23}, {e: lookup.Epoch{Time: 1103231132, Level: 14}, n: 1102292201, x: 22}, {e: lookup.Epoch{Time: 1094502355, Level: 3}, n: 1094324652, x: 18}, {e: lookup.Epoch{Time: 1068488344, Level: 12}, n: 1067577330, x: 19}, {e: lookup.Epoch{Time: 1050278233, Level: 12}, n: 1050278232, x: 11}, {e: lookup.Epoch{Time: 1047660768, Level: 5}, n: 1047652137, x: 17}, {e: lookup.Epoch{Time: 1060116167, Level: 11}, n: 1060114091, x: 12}, {e: lookup.Epoch{Time: 1068149392, Level: 21}, n: 1052074801, x: 24}, {e: lookup.Epoch{Time: 1081934120, Level: 6}, n: 1081933847, x: 8}, {e: lookup.Epoch{Time: 1107943693, Level: 16}, n: 1107096139, x: 25}, {e: lookup.Epoch{Time: 1131571649, Level: 9}, n: 1131570428, x: 11}, {e: lookup.Epoch{Time: 1123139367, Level: 0}, n: 1122912198, x: 20}, {e: lookup.Epoch{Time: 1121144423, Level: 6}, n: 1120568289, x: 20}, {e: lookup.Epoch{Time: 1089932411, Level: 17}, n: 1089932410, x: 16}, {e: lookup.Epoch{Time: 1104899012, Level: 22}, n: 1098978789, x: 22}, {e: lookup.Epoch{Time: 1094588059, Level: 21}, n: 1094588059, x: 20}, {e: lookup.Epoch{Time: 1114987438, Level: 24}, n: 1114987437, x: 23}, {e: lookup.Epoch{Time: 1084186305, Level: 7}, n: 1084186241, x: 6}, {e: lookup.Epoch{Time: 1058827111, Level: 8}, n: 1058826504, x: 9}, {e: lookup.Epoch{Time: 1090679810, Level: 12}, n: 1090616539, x: 17}, {e: lookup.Epoch{Time: 1084299475, Level: 23}, n: 1084299475, x: 22}} - -func TestGetNextLevel(t *testing.T) { - - // First, test well-known cases - last := lookup.Epoch{ - Time: 1533799046, - Level: 5, - } - - level := lookup.GetNextLevel(last, last.Time) - expected := uint8(4) - if level != expected { - t.Fatalf("Expected GetNextLevel to return %d for same-time updates at a nonzero level, got %d", expected, level) - } - - level = lookup.GetNextLevel(last, last.Time+(1< lookup.HighestLevel { - v = 0 - } - now = last.Time + uint64(rand.Intn(1<. - -package feed - -import ( - "fmt" - "strconv" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/swarm/storage/feed/lookup" -) - -// Query is used to specify constraints when performing an update lookup -// TimeLimit indicates an upper bound for the search. Set to 0 for "now" -type Query struct { - Feed - Hint lookup.Epoch - TimeLimit uint64 -} - -// FromValues deserializes this instance from a string key-value store -// useful to parse query strings -func (q *Query) FromValues(values Values) error { - time, _ := strconv.ParseUint(values.Get("time"), 10, 64) - q.TimeLimit = time - - level, _ := strconv.ParseUint(values.Get("hint.level"), 10, 32) - q.Hint.Level = uint8(level) - q.Hint.Time, _ = strconv.ParseUint(values.Get("hint.time"), 10, 64) - if q.Feed.User == (common.Address{}) { - return q.Feed.FromValues(values) - } - return nil -} - -// AppendValues serializes this structure into the provided string key-value store -// useful to build query strings -func (q *Query) AppendValues(values Values) { - if q.TimeLimit != 0 { - values.Set("time", fmt.Sprintf("%d", q.TimeLimit)) - } - if q.Hint.Level != 0 { - values.Set("hint.level", fmt.Sprintf("%d", q.Hint.Level)) - } - if q.Hint.Time != 0 { - values.Set("hint.time", fmt.Sprintf("%d", q.Hint.Time)) - } - q.Feed.AppendValues(values) -} - -// NewQuery constructs an Query structure to find updates on or before `time` -// if time == 0, the latest update will be looked up -func NewQuery(feed *Feed, time uint64, hint lookup.Epoch) *Query { - return &Query{ - TimeLimit: time, - Feed: *feed, - Hint: hint, - } -} - -// NewQueryLatest generates lookup parameters that look for the latest update to a feed -func NewQueryLatest(feed *Feed, hint lookup.Epoch) *Query { - return NewQuery(feed, 0, hint) -} diff --git a/swarm/storage/feed/query_test.go b/swarm/storage/feed/query_test.go deleted file mode 100644 index 9fa5e29800d5..000000000000 --- a/swarm/storage/feed/query_test.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package feed - -import ( - "testing" -) - -func getTestQuery() *Query { - id := getTestID() - return &Query{ - TimeLimit: 5000, - Feed: id.Feed, - Hint: id.Epoch, - } -} - -func TestQueryValues(t *testing.T) { - var expected = KV{"hint.level": "25", "hint.time": "1000", "time": "5000", "topic": "0x776f726c64206e657773207265706f72742c20657665727920686f7572000000", "user": "0x876A8936A7Cd0b79Ef0735AD0896c1AFe278781c"} - - query := getTestQuery() - testValueSerializer(t, query, expected) - -} diff --git a/swarm/storage/feed/request.go b/swarm/storage/feed/request.go deleted file mode 100644 index ec8209acddba..000000000000 --- a/swarm/storage/feed/request.go +++ /dev/null @@ -1,286 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package feed - -import ( - "bytes" - "encoding/json" - "hash" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/common/hexutil" - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/ubiq/go-ubiq/swarm/storage/feed/lookup" -) - -// Request represents a request to sign or signed feed update message -type Request struct { - Update // actual content that will be put on the chunk, less signature - Signature *Signature - idAddr storage.Address // cached chunk address for the update (not serialized, for internal use) - binaryData []byte // cached serialized data (does not get serialized again!, for efficiency/internal use) -} - -// updateRequestJSON represents a JSON-serialized UpdateRequest -type updateRequestJSON struct { - ID - ProtocolVersion uint8 `json:"protocolVersion"` - Data string `json:"data,omitempty"` - Signature string `json:"signature,omitempty"` -} - -// Request layout -// Update bytes -// SignatureLength bytes -const minimumSignedUpdateLength = minimumUpdateDataLength + signatureLength - -// NewFirstRequest returns a ready to sign request to publish a first feed update -func NewFirstRequest(topic Topic) *Request { - - request := new(Request) - - // get the current time - now := TimestampProvider.Now().Time - request.Epoch = lookup.GetFirstEpoch(now) - request.Feed.Topic = topic - request.Header.Version = ProtocolVersion - - return request -} - -// SetData stores the payload data the feed update will be updated with -func (r *Request) SetData(data []byte) { - r.data = data - r.Signature = nil -} - -// IsUpdate returns true if this request models a signed update or otherwise it is a signature request -func (r *Request) IsUpdate() bool { - return r.Signature != nil -} - -// Verify checks that signatures are valid -func (r *Request) Verify() (err error) { - if len(r.data) == 0 { - return NewError(ErrInvalidValue, "Update does not contain data") - } - if r.Signature == nil { - return NewError(ErrInvalidSignature, "Missing signature field") - } - - digest, err := r.GetDigest() - if err != nil { - return err - } - - // get the address of the signer (which also checks that it's a valid signature) - r.Feed.User, err = getUserAddr(digest, *r.Signature) - if err != nil { - return err - } - - // check that the lookup information contained in the chunk matches the updateAddr (chunk search key) - // that was used to retrieve this chunk - // if this validation fails, someone forged a chunk. - if !bytes.Equal(r.idAddr, r.Addr()) { - return NewError(ErrInvalidSignature, "Signature address does not match with update user address") - } - - return nil -} - -// Sign executes the signature to validate the update message -func (r *Request) Sign(signer Signer) error { - r.Feed.User = signer.Address() - r.binaryData = nil //invalidate serialized data - digest, err := r.GetDigest() // computes digest and serializes into .binaryData - if err != nil { - return err - } - - signature, err := signer.Sign(digest) - if err != nil { - return err - } - - // Although the Signer interface returns the public address of the signer, - // recover it from the signature to see if they match - userAddr, err := getUserAddr(digest, signature) - if err != nil { - return NewError(ErrInvalidSignature, "Error verifying signature") - } - - if userAddr != signer.Address() { // sanity check to make sure the Signer is declaring the same address used to sign! - return NewError(ErrInvalidSignature, "Signer address does not match update user address") - } - - r.Signature = &signature - r.idAddr = r.Addr() - return nil -} - -// GetDigest creates the feed update digest used in signatures -// the serialized payload is cached in .binaryData -func (r *Request) GetDigest() (result common.Hash, err error) { - hasher := hashPool.Get().(hash.Hash) - defer hashPool.Put(hasher) - hasher.Reset() - dataLength := r.Update.binaryLength() - if r.binaryData == nil { - r.binaryData = make([]byte, dataLength+signatureLength) - if err := r.Update.binaryPut(r.binaryData[:dataLength]); err != nil { - return result, err - } - } - hasher.Write(r.binaryData[:dataLength]) //everything except the signature. - - return common.BytesToHash(hasher.Sum(nil)), nil -} - -// create an update chunk. -func (r *Request) toChunk() (storage.Chunk, error) { - - // Check that the update is signed and serialized - // For efficiency, data is serialized during signature and cached in - // the binaryData field when computing the signature digest in .getDigest() - if r.Signature == nil || r.binaryData == nil { - return nil, NewError(ErrInvalidSignature, "toChunk called without a valid signature or payload data. Call .Sign() first.") - } - - updateLength := r.Update.binaryLength() - - // signature is the last item in the chunk data - copy(r.binaryData[updateLength:], r.Signature[:]) - - chunk := storage.NewChunk(r.idAddr, r.binaryData) - return chunk, nil -} - -// fromChunk populates this structure from chunk data. It does not verify the signature is valid. -func (r *Request) fromChunk(chunk storage.Chunk) error { - // for update chunk layout see Request definition - - chunkdata := chunk.Data() - - //deserialize the feed update portion - if err := r.Update.binaryGet(chunkdata[:len(chunkdata)-signatureLength]); err != nil { - return err - } - - // Extract the signature - var signature *Signature - cursor := r.Update.binaryLength() - sigdata := chunkdata[cursor : cursor+signatureLength] - if len(sigdata) > 0 { - signature = &Signature{} - copy(signature[:], sigdata) - } - - r.Signature = signature - r.idAddr = chunk.Address() - r.binaryData = chunkdata - - return nil - -} - -// FromValues deserializes this instance from a string key-value store -// useful to parse query strings -func (r *Request) FromValues(values Values, data []byte) error { - signatureBytes, err := hexutil.Decode(values.Get("signature")) - if err != nil { - r.Signature = nil - } else { - if len(signatureBytes) != signatureLength { - return NewError(ErrInvalidSignature, "Incorrect signature length") - } - r.Signature = new(Signature) - copy(r.Signature[:], signatureBytes) - } - err = r.Update.FromValues(values, data) - if err != nil { - return err - } - r.idAddr = r.Addr() - return err -} - -// AppendValues serializes this structure into the provided string key-value store -// useful to build query strings -func (r *Request) AppendValues(values Values) []byte { - if r.Signature != nil { - values.Set("signature", hexutil.Encode(r.Signature[:])) - } - return r.Update.AppendValues(values) -} - -// fromJSON takes an update request JSON and populates an UpdateRequest -func (r *Request) fromJSON(j *updateRequestJSON) error { - - r.ID = j.ID - r.Header.Version = j.ProtocolVersion - - var err error - if j.Data != "" { - r.data, err = hexutil.Decode(j.Data) - if err != nil { - return NewError(ErrInvalidValue, "Cannot decode data") - } - } - - if j.Signature != "" { - sigBytes, err := hexutil.Decode(j.Signature) - if err != nil || len(sigBytes) != signatureLength { - return NewError(ErrInvalidSignature, "Cannot decode signature") - } - r.Signature = new(Signature) - r.idAddr = r.Addr() - copy(r.Signature[:], sigBytes) - } - return nil -} - -// UnmarshalJSON takes a JSON structure stored in a byte array and populates the Request object -// Implements json.Unmarshaler interface -func (r *Request) UnmarshalJSON(rawData []byte) error { - var requestJSON updateRequestJSON - if err := json.Unmarshal(rawData, &requestJSON); err != nil { - return err - } - return r.fromJSON(&requestJSON) -} - -// MarshalJSON takes an update request and encodes it as a JSON structure into a byte array -// Implements json.Marshaler interface -func (r *Request) MarshalJSON() (rawData []byte, err error) { - var signatureString, dataString string - if r.Signature != nil { - signatureString = hexutil.Encode(r.Signature[:]) - } - if r.data != nil { - dataString = hexutil.Encode(r.data) - } - - requestJSON := &updateRequestJSON{ - ID: r.ID, - ProtocolVersion: r.Header.Version, - Data: dataString, - Signature: signatureString, - } - - return json.Marshal(requestJSON) -} diff --git a/swarm/storage/feed/request_test.go b/swarm/storage/feed/request_test.go deleted file mode 100644 index 943623b0b54c..000000000000 --- a/swarm/storage/feed/request_test.go +++ /dev/null @@ -1,312 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package feed - -import ( - "bytes" - "encoding/binary" - "encoding/json" - "fmt" - "reflect" - "testing" - - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/ubiq/go-ubiq/swarm/storage/feed/lookup" -) - -func areEqualJSON(s1, s2 string) (bool, error) { - //credit for the trick: turtlemonvh https://gist.github.com/turtlemonvh/e4f7404e28387fadb8ad275a99596f67 - var o1 interface{} - var o2 interface{} - - err := json.Unmarshal([]byte(s1), &o1) - if err != nil { - return false, fmt.Errorf("Error mashalling string 1 :: %s", err.Error()) - } - err = json.Unmarshal([]byte(s2), &o2) - if err != nil { - return false, fmt.Errorf("Error mashalling string 2 :: %s", err.Error()) - } - - return reflect.DeepEqual(o1, o2), nil -} - -// TestEncodingDecodingUpdateRequests ensures that requests are serialized properly -// while also checking cryptographically that only the owner of a feed can update it. -func TestEncodingDecodingUpdateRequests(t *testing.T) { - - charlie := newCharlieSigner() //Charlie - bob := newBobSigner() //Bob - - // Create a feed to our good guy Charlie's name - topic, _ := NewTopic("a good topic name", nil) - firstRequest := NewFirstRequest(topic) - firstRequest.User = charlie.Address() - - // We now encode the create message to simulate we send it over the wire - messageRawData, err := firstRequest.MarshalJSON() - if err != nil { - t.Fatalf("Error encoding first feed update request: %s", err) - } - - // ... the message arrives and is decoded... - var recoveredFirstRequest Request - if err := recoveredFirstRequest.UnmarshalJSON(messageRawData); err != nil { - t.Fatalf("Error decoding first feed update request: %s", err) - } - - // ... but verification should fail because it is not signed! - if err := recoveredFirstRequest.Verify(); err == nil { - t.Fatal("Expected Verify to fail since the message is not signed") - } - - // We now assume that the feed ypdate was created and propagated. - - const expectedSignature = "0x7235b27a68372ddebcf78eba48543fa460864b0b0e99cb533fcd3664820e603312d29426dd00fb39628f5299480a69bf6e462838d78de49ce0704c754c9deb2601" - const expectedJSON = `{"feed":{"topic":"0x6120676f6f6420746f706963206e616d65000000000000000000000000000000","user":"0x876a8936a7cd0b79ef0735ad0896c1afe278781c"},"epoch":{"time":1000,"level":1},"protocolVersion":0,"data":"0x5468697320686f75722773207570646174653a20537761726d2039392e3020686173206265656e2072656c656173656421"}` - - //Put together an unsigned update request that we will serialize to send it to the signer. - data := []byte("This hour's update: Swarm 99.0 has been released!") - request := &Request{ - Update: Update{ - ID: ID{ - Epoch: lookup.Epoch{ - Time: 1000, - Level: 1, - }, - Feed: firstRequest.Update.Feed, - }, - data: data, - }, - } - - messageRawData, err = request.MarshalJSON() - if err != nil { - t.Fatalf("Error encoding update request: %s", err) - } - - equalJSON, err := areEqualJSON(string(messageRawData), expectedJSON) - if err != nil { - t.Fatalf("Error decoding update request JSON: %s", err) - } - if !equalJSON { - t.Fatalf("Received a different JSON message. Expected %s, got %s", expectedJSON, string(messageRawData)) - } - - // now the encoded message messageRawData is sent over the wire and arrives to the signer - - //Attempt to extract an UpdateRequest out of the encoded message - var recoveredRequest Request - if err := recoveredRequest.UnmarshalJSON(messageRawData); err != nil { - t.Fatalf("Error decoding update request: %s", err) - } - - //sign the request and see if it matches our predefined signature above. - if err := recoveredRequest.Sign(charlie); err != nil { - t.Fatalf("Error signing request: %s", err) - } - - compareByteSliceToExpectedHex(t, "signature", recoveredRequest.Signature[:], expectedSignature) - - // mess with the signature and see what happens. To alter the signature, we briefly decode it as JSON - // to alter the signature field. - var j updateRequestJSON - if err := json.Unmarshal([]byte(expectedJSON), &j); err != nil { - t.Fatal("Error unmarshalling test json, check expectedJSON constant") - } - j.Signature = "Certainly not a signature" - corruptMessage, _ := json.Marshal(j) // encode the message with the bad signature - var corruptRequest Request - if err = corruptRequest.UnmarshalJSON(corruptMessage); err == nil { - t.Fatal("Expected DecodeUpdateRequest to fail when trying to interpret a corrupt message with an invalid signature") - } - - // Now imagine Bob wants to create an update of his own about the same feed, - // signing a message with his private key - if err := request.Sign(bob); err != nil { - t.Fatalf("Error signing: %s", err) - } - - // Now Bob encodes the message to send it over the wire... - messageRawData, err = request.MarshalJSON() - if err != nil { - t.Fatalf("Error encoding message:%s", err) - } - - // ... the message arrives to our Swarm node and it is decoded. - recoveredRequest = Request{} - if err := recoveredRequest.UnmarshalJSON(messageRawData); err != nil { - t.Fatalf("Error decoding message:%s", err) - } - - // Before checking what happened with Bob's update, let's see what would happen if we mess - // with the signature big time to see if Verify catches it - savedSignature := *recoveredRequest.Signature // save the signature for later - binary.LittleEndian.PutUint64(recoveredRequest.Signature[5:], 556845463424) // write some random data to break the signature - if err = recoveredRequest.Verify(); err == nil { - t.Fatal("Expected Verify to fail on corrupt signature") - } - - // restore the Bob's signature from corruption - *recoveredRequest.Signature = savedSignature - - // Now the signature is not corrupt - if err = recoveredRequest.Verify(); err != nil { - t.Fatal(err) - } - - // Reuse object and sign with our friend Charlie's private key - if err := recoveredRequest.Sign(charlie); err != nil { - t.Fatalf("Error signing with the correct private key: %s", err) - } - - // And now, Verify should work since this update now belongs to Charlie - if err = recoveredRequest.Verify(); err != nil { - t.Fatalf("Error verifying that Charlie, can sign a reused request object:%s", err) - } - - // mess with the lookup key to make sure Verify fails: - recoveredRequest.Time = 77999 // this will alter the lookup key - if err = recoveredRequest.Verify(); err == nil { - t.Fatalf("Expected Verify to fail since the lookup key has been altered") - } -} - -func getTestRequest() *Request { - return &Request{ - Update: *getTestFeedUpdate(), - } -} - -func TestUpdateChunkSerializationErrorChecking(t *testing.T) { - - // Test that parseUpdate fails if the chunk is too small - var r Request - if err := r.fromChunk(storage.NewChunk(storage.ZeroAddr, make([]byte, minimumUpdateDataLength-1+signatureLength))); err == nil { - t.Fatalf("Expected request.fromChunk to fail when chunkData contains less than %d bytes", minimumUpdateDataLength) - } - - r = *getTestRequest() - - _, err := r.toChunk() - if err == nil { - t.Fatal("Expected request.toChunk to fail when there is no data") - } - r.data = []byte("Al bien hacer jamás le falta premio") // put some arbitrary length data - _, err = r.toChunk() - if err == nil { - t.Fatal("expected request.toChunk to fail when there is no signature") - } - - charlie := newCharlieSigner() - if err := r.Sign(charlie); err != nil { - t.Fatalf("error signing:%s", err) - } - - chunk, err := r.toChunk() - if err != nil { - t.Fatalf("error creating update chunk:%s", err) - } - - compareByteSliceToExpectedHex(t, "chunk", chunk.Data(), "0x0000000000000000776f726c64206e657773207265706f72742c20657665727920686f7572000000876a8936a7cd0b79ef0735ad0896c1afe278781ce803000000000019416c206269656e206861636572206a616dc3a173206c652066616c7461207072656d696f5a0ffe0bc27f207cd5b00944c8b9cee93e08b89b5ada777f123ac535189333f174a6a4ca2f43a92c4a477a49d774813c36ce8288552c58e6205b0ac35d0507eb00") - - var recovered Request - recovered.fromChunk(chunk) - if !reflect.DeepEqual(recovered, r) { - t.Fatal("Expected recovered feed update request to equal the original one") - } -} - -// check that signature address matches update signer address -func TestReverse(t *testing.T) { - - epoch := lookup.Epoch{ - Time: 7888, - Level: 6, - } - - // make fake timeProvider - timeProvider := &fakeTimeProvider{ - currentTime: startTime.Time, - } - - // signer containing private key - signer := newAliceSigner() - - // set up rpc and create feeds handler - _, _, teardownTest, err := setupTest(timeProvider, signer) - if err != nil { - t.Fatal(err) - } - defer teardownTest() - - topic, _ := NewTopic("Cervantes quotes", nil) - fd := Feed{ - Topic: topic, - User: signer.Address(), - } - - data := []byte("Donde una puerta se cierra, otra se abre") - - request := new(Request) - request.Feed = fd - request.Epoch = epoch - request.data = data - - // generate a chunk key for this request - key := request.Addr() - - if err = request.Sign(signer); err != nil { - t.Fatal(err) - } - - chunk, err := request.toChunk() - if err != nil { - t.Fatal(err) - } - - // check that we can recover the owner account from the update chunk's signature - var checkUpdate Request - if err := checkUpdate.fromChunk(chunk); err != nil { - t.Fatal(err) - } - checkdigest, err := checkUpdate.GetDigest() - if err != nil { - t.Fatal(err) - } - recoveredAddr, err := getUserAddr(checkdigest, *checkUpdate.Signature) - if err != nil { - t.Fatalf("Retrieve address from signature fail: %v", err) - } - originalAddr := crypto.PubkeyToAddress(signer.PrivKey.PublicKey) - - // check that the metadata retrieved from the chunk matches what we gave it - if recoveredAddr != originalAddr { - t.Fatalf("addresses dont match: %x != %x", originalAddr, recoveredAddr) - } - - if !bytes.Equal(key[:], chunk.Address()[:]) { - t.Fatalf("Expected chunk key '%x', was '%x'", key, chunk.Address()) - } - if epoch != checkUpdate.Epoch { - t.Fatalf("Expected epoch to be '%s', was '%s'", epoch.String(), checkUpdate.Epoch.String()) - } - if !bytes.Equal(data, checkUpdate.data) { - t.Fatalf("Expected data '%x', was '%x'", data, checkUpdate.data) - } -} diff --git a/swarm/storage/feed/sign.go b/swarm/storage/feed/sign.go deleted file mode 100644 index bd2d64339e7c..000000000000 --- a/swarm/storage/feed/sign.go +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package feed - -import ( - "crypto/ecdsa" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/crypto" -) - -const signatureLength = 65 - -// Signature is an alias for a static byte array with the size of a signature -type Signature [signatureLength]byte - -// Signer signs feed update payloads -type Signer interface { - Sign(common.Hash) (Signature, error) - Address() common.Address -} - -// GenericSigner implements the Signer interface -// It is the vanilla signer that probably should be used in most cases -type GenericSigner struct { - PrivKey *ecdsa.PrivateKey - address common.Address -} - -// NewGenericSigner builds a signer that will sign everything with the provided private key -func NewGenericSigner(privKey *ecdsa.PrivateKey) *GenericSigner { - return &GenericSigner{ - PrivKey: privKey, - address: crypto.PubkeyToAddress(privKey.PublicKey), - } -} - -// Sign signs the supplied data -// It wraps the ethereum crypto.Sign() method -func (s *GenericSigner) Sign(data common.Hash) (signature Signature, err error) { - signaturebytes, err := crypto.Sign(data.Bytes(), s.PrivKey) - if err != nil { - return - } - copy(signature[:], signaturebytes) - return -} - -// Address returns the public key of the signer's private key -func (s *GenericSigner) Address() common.Address { - return s.address -} - -// getUserAddr extracts the address of the feed update signer -func getUserAddr(digest common.Hash, signature Signature) (common.Address, error) { - pub, err := crypto.SigToPub(digest.Bytes(), signature[:]) - if err != nil { - return common.Address{}, err - } - return crypto.PubkeyToAddress(*pub), nil -} diff --git a/swarm/storage/feed/testutil.go b/swarm/storage/feed/testutil.go deleted file mode 100644 index 01151547509c..000000000000 --- a/swarm/storage/feed/testutil.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package feed - -import ( - "context" - "fmt" - "path/filepath" - "sync" - - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/swarm/storage" -) - -const ( - testDbDirName = "feeds" -) - -type TestHandler struct { - *Handler -} - -func (t *TestHandler) Close() { - t.chunkStore.Close() -} - -type mockNetFetcher struct{} - -func (m *mockNetFetcher) Request(hopCount uint8) { -} -func (m *mockNetFetcher) Offer(source *enode.ID) { -} - -func newFakeNetFetcher(context.Context, storage.Address, *sync.Map) storage.NetFetcher { - return &mockNetFetcher{} -} - -// NewTestHandler creates Handler object to be used for testing purposes. -func NewTestHandler(datadir string, params *HandlerParams) (*TestHandler, error) { - path := filepath.Join(datadir, testDbDirName) - fh := NewHandler(params) - localstoreparams := storage.NewDefaultLocalStoreParams() - localstoreparams.Init(path) - localStore, err := storage.NewLocalStore(localstoreparams, nil) - if err != nil { - return nil, fmt.Errorf("localstore create fail, path %s: %v", path, err) - } - localStore.Validators = append(localStore.Validators, storage.NewContentAddressValidator(storage.MakeHashFunc(feedsHashAlgorithm))) - localStore.Validators = append(localStore.Validators, fh) - netStore, err := storage.NewNetStore(localStore, nil) - if err != nil { - return nil, err - } - netStore.NewNetFetcherFunc = newFakeNetFetcher - fh.SetStore(netStore) - return &TestHandler{fh}, nil -} diff --git a/swarm/storage/feed/timestampprovider.go b/swarm/storage/feed/timestampprovider.go deleted file mode 100644 index fb60cea9c3d6..000000000000 --- a/swarm/storage/feed/timestampprovider.go +++ /dev/null @@ -1,62 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package feed - -import ( - "encoding/json" - "time" -) - -// TimestampProvider sets the time source of the feeds package -var TimestampProvider timestampProvider = NewDefaultTimestampProvider() - -// Timestamp encodes a point in time as a Unix epoch -type Timestamp struct { - Time uint64 `json:"time"` // Unix epoch timestamp, in seconds -} - -// timestampProvider interface describes a source of timestamp information -type timestampProvider interface { - Now() Timestamp // returns the current timestamp information -} - -// UnmarshalJSON implements the json.Unmarshaller interface -func (t *Timestamp) UnmarshalJSON(data []byte) error { - return json.Unmarshal(data, &t.Time) -} - -// MarshalJSON implements the json.Marshaller interface -func (t *Timestamp) MarshalJSON() ([]byte, error) { - return json.Marshal(t.Time) -} - -// DefaultTimestampProvider is a TimestampProvider that uses system time -// as time source -type DefaultTimestampProvider struct { -} - -// NewDefaultTimestampProvider creates a system clock based timestamp provider -func NewDefaultTimestampProvider() *DefaultTimestampProvider { - return &DefaultTimestampProvider{} -} - -// Now returns the current time according to this provider -func (dtp *DefaultTimestampProvider) Now() Timestamp { - return Timestamp{ - Time: uint64(time.Now().Unix()), - } -} diff --git a/swarm/storage/feed/topic.go b/swarm/storage/feed/topic.go deleted file mode 100644 index 2e65603efcf7..000000000000 --- a/swarm/storage/feed/topic.go +++ /dev/null @@ -1,105 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package feed - -import ( - "bytes" - "encoding/json" - "fmt" - - "github.com/ubiq/go-ubiq/common/bitutil" - "github.com/ubiq/go-ubiq/common/hexutil" - "github.com/ubiq/go-ubiq/swarm/storage" -) - -// TopicLength establishes the max length of a topic string -const TopicLength = storage.AddressLength - -// Topic represents what a feed is about -type Topic [TopicLength]byte - -// ErrTopicTooLong is returned when creating a topic with a name/related content too long -var ErrTopicTooLong = fmt.Errorf("Topic is too long. Max length is %d", TopicLength) - -// NewTopic creates a new topic from a provided name and "related content" byte array, -// merging the two together. -// If relatedContent or name are longer than TopicLength, they will be truncated and an error returned -// name can be an empty string -// relatedContent can be nil -func NewTopic(name string, relatedContent []byte) (topic Topic, err error) { - if relatedContent != nil { - contentLength := len(relatedContent) - if contentLength > TopicLength { - contentLength = TopicLength - err = ErrTopicTooLong - } - copy(topic[:], relatedContent[:contentLength]) - } - nameBytes := []byte(name) - nameLength := len(nameBytes) - if nameLength > TopicLength { - nameLength = TopicLength - err = ErrTopicTooLong - } - bitutil.XORBytes(topic[:], topic[:], nameBytes[:nameLength]) - return topic, err -} - -// Hex will return the topic encoded as an hex string -func (t *Topic) Hex() string { - return hexutil.Encode(t[:]) -} - -// FromHex will parse a hex string into this Topic instance -func (t *Topic) FromHex(hex string) error { - bytes, err := hexutil.Decode(hex) - if err != nil || len(bytes) != len(t) { - return NewErrorf(ErrInvalidValue, "Cannot decode topic") - } - copy(t[:], bytes) - return nil -} - -// Name will try to extract the topic name out of the Topic -func (t *Topic) Name(relatedContent []byte) string { - nameBytes := *t - if relatedContent != nil { - contentLength := len(relatedContent) - if contentLength > TopicLength { - contentLength = TopicLength - } - bitutil.XORBytes(nameBytes[:], t[:], relatedContent[:contentLength]) - } - z := bytes.IndexByte(nameBytes[:], 0) - if z < 0 { - z = TopicLength - } - return string(nameBytes[:z]) - -} - -// UnmarshalJSON implements the json.Unmarshaller interface -func (t *Topic) UnmarshalJSON(data []byte) error { - var hex string - json.Unmarshal(data, &hex) - return t.FromHex(hex) -} - -// MarshalJSON implements the json.Marshaller interface -func (t *Topic) MarshalJSON() ([]byte, error) { - return json.Marshal(t.Hex()) -} diff --git a/swarm/storage/feed/topic_test.go b/swarm/storage/feed/topic_test.go deleted file mode 100644 index 317f96bed936..000000000000 --- a/swarm/storage/feed/topic_test.go +++ /dev/null @@ -1,50 +0,0 @@ -package feed - -import ( - "testing" - - "github.com/ubiq/go-ubiq/common/hexutil" -) - -func TestTopic(t *testing.T) { - related, _ := hexutil.Decode("0xabcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789") - topicName := "test-topic" - topic, _ := NewTopic(topicName, related) - hex := topic.Hex() - expectedHex := "0xdfa89c750e3108f9c2aeef0123456789abcdef0123456789abcdef0123456789" - if hex != expectedHex { - t.Fatalf("Expected %s, got %s", expectedHex, hex) - } - - var topic2 Topic - topic2.FromHex(hex) - if topic2 != topic { - t.Fatal("Expected recovered topic to be equal to original one") - } - - if topic2.Name(related) != topicName { - t.Fatal("Retrieved name does not match") - } - - bytes, err := topic2.MarshalJSON() - if err != nil { - t.Fatal(err) - } - expectedJSON := `"0xdfa89c750e3108f9c2aeef0123456789abcdef0123456789abcdef0123456789"` - equal, err := areEqualJSON(expectedJSON, string(bytes)) - if err != nil { - t.Fatal(err) - } - if !equal { - t.Fatalf("Expected JSON to be %s, got %s", expectedJSON, string(bytes)) - } - - err = topic2.UnmarshalJSON(bytes) - if err != nil { - t.Fatal(err) - } - if topic2 != topic { - t.Fatal("Expected recovered topic to be equal to original one") - } - -} diff --git a/swarm/storage/feed/update.go b/swarm/storage/feed/update.go deleted file mode 100644 index e5ec8f6ccae0..000000000000 --- a/swarm/storage/feed/update.go +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package feed - -import ( - "fmt" - "strconv" - - "github.com/ubiq/go-ubiq/swarm/chunk" -) - -// ProtocolVersion defines the current version of the protocol that will be included in each update message -const ProtocolVersion uint8 = 0 - -const headerLength = 8 - -// Header defines a update message header including a protocol version byte -type Header struct { - Version uint8 // Protocol version - Padding [headerLength - 1]uint8 // reserved for future use -} - -// Update encapsulates the information sent as part of a feed update -type Update struct { - Header Header // - ID // Feed Update identifying information - data []byte // actual data payload -} - -const minimumUpdateDataLength = idLength + headerLength + 1 - -//MaxUpdateDataLength indicates the maximum payload size for a feed update -const MaxUpdateDataLength = chunk.DefaultSize - signatureLength - idLength - headerLength - -// binaryPut serializes the feed update information into the given slice -func (r *Update) binaryPut(serializedData []byte) error { - datalength := len(r.data) - if datalength == 0 { - return NewError(ErrInvalidValue, "a feed update must contain data") - } - - if datalength > MaxUpdateDataLength { - return NewErrorf(ErrInvalidValue, "feed update data is too big (length=%d). Max length=%d", datalength, MaxUpdateDataLength) - } - - if len(serializedData) != r.binaryLength() { - return NewErrorf(ErrInvalidValue, "slice passed to putBinary must be of exact size. Expected %d bytes", r.binaryLength()) - } - - var cursor int - // serialize Header - serializedData[cursor] = r.Header.Version - copy(serializedData[cursor+1:headerLength], r.Header.Padding[:headerLength-1]) - cursor += headerLength - - // serialize ID - if err := r.ID.binaryPut(serializedData[cursor : cursor+idLength]); err != nil { - return err - } - cursor += idLength - - // add the data - copy(serializedData[cursor:], r.data) - cursor += datalength - - return nil -} - -// binaryLength returns the expected number of bytes this structure will take to encode -func (r *Update) binaryLength() int { - return idLength + headerLength + len(r.data) -} - -// binaryGet populates this instance from the information contained in the passed byte slice -func (r *Update) binaryGet(serializedData []byte) error { - if len(serializedData) < minimumUpdateDataLength { - return NewErrorf(ErrNothingToReturn, "chunk less than %d bytes cannot be a feed update chunk", minimumUpdateDataLength) - } - dataLength := len(serializedData) - idLength - headerLength - // at this point we can be satisfied that we have the correct data length to read - - var cursor int - - // deserialize Header - r.Header.Version = serializedData[cursor] // extract the protocol version - copy(r.Header.Padding[:headerLength-1], serializedData[cursor+1:headerLength]) // extract the padding - cursor += headerLength - - if err := r.ID.binaryGet(serializedData[cursor : cursor+idLength]); err != nil { - return err - } - cursor += idLength - - data := serializedData[cursor : cursor+dataLength] - cursor += dataLength - - // now that all checks have passed, copy data into structure - r.data = make([]byte, dataLength) - copy(r.data, data) - - return nil - -} - -// FromValues deserializes this instance from a string key-value store -// useful to parse query strings -func (r *Update) FromValues(values Values, data []byte) error { - r.data = data - version, _ := strconv.ParseUint(values.Get("protocolVersion"), 10, 32) - r.Header.Version = uint8(version) - return r.ID.FromValues(values) -} - -// AppendValues serializes this structure into the provided string key-value store -// useful to build query strings -func (r *Update) AppendValues(values Values) []byte { - r.ID.AppendValues(values) - values.Set("protocolVersion", fmt.Sprintf("%d", r.Header.Version)) - return r.data -} diff --git a/swarm/storage/feed/update_test.go b/swarm/storage/feed/update_test.go deleted file mode 100644 index 24c09b361741..000000000000 --- a/swarm/storage/feed/update_test.go +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package feed - -import ( - "testing" -) - -func getTestFeedUpdate() *Update { - return &Update{ - ID: *getTestID(), - data: []byte("El que lee mucho y anda mucho, ve mucho y sabe mucho"), - } -} - -func TestUpdateSerializer(t *testing.T) { - testBinarySerializerRecovery(t, getTestFeedUpdate(), "0x0000000000000000776f726c64206e657773207265706f72742c20657665727920686f7572000000876a8936a7cd0b79ef0735ad0896c1afe278781ce803000000000019456c20717565206c6565206d7563686f207920616e6461206d7563686f2c207665206d7563686f20792073616265206d7563686f") -} - -func TestUpdateLengthCheck(t *testing.T) { - testBinarySerializerLengthCheck(t, getTestFeedUpdate()) - // Test fail if update is too big - update := getTestFeedUpdate() - update.data = make([]byte, MaxUpdateDataLength+100) - serialized := make([]byte, update.binaryLength()) - if err := update.binaryPut(serialized); err == nil { - t.Fatal("Expected update.binaryPut to fail since update is too big") - } - - // test fail if data is empty or nil - update.data = nil - serialized = make([]byte, update.binaryLength()) - if err := update.binaryPut(serialized); err == nil { - t.Fatal("Expected update.binaryPut to fail since data is empty") - } -} diff --git a/swarm/storage/filestore.go b/swarm/storage/filestore.go deleted file mode 100644 index 0bad944ee91e..000000000000 --- a/swarm/storage/filestore.go +++ /dev/null @@ -1,146 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package storage - -import ( - "context" - "io" - "sort" - "sync" -) - -/* -FileStore provides the client API entrypoints Store and Retrieve to store and retrieve -It can store anything that has a byte slice representation, so files or serialised objects etc. - -Storage: FileStore calls the Chunker to segment the input datastream of any size to a merkle hashed tree of chunks. The key of the root block is returned to the client. - -Retrieval: given the key of the root block, the FileStore retrieves the block chunks and reconstructs the original data and passes it back as a lazy reader. A lazy reader is a reader with on-demand delayed processing, i.e. the chunks needed to reconstruct a large file are only fetched and processed if that particular part of the document is actually read. - -As the chunker produces chunks, FileStore dispatches them to its own chunk store -implementation for storage or retrieval. -*/ - -const ( - defaultLDBCapacity = 5000000 // capacity for LevelDB, by default 5*10^6*4096 bytes == 20GB - defaultCacheCapacity = 10000 // capacity for in-memory chunks' cache - defaultChunkRequestsCacheCapacity = 5000000 // capacity for container holding outgoing requests for chunks. should be set to LevelDB capacity -) - -type FileStore struct { - ChunkStore - hashFunc SwarmHasher -} - -type FileStoreParams struct { - Hash string -} - -func NewFileStoreParams() *FileStoreParams { - return &FileStoreParams{ - Hash: DefaultHash, - } -} - -// for testing locally -func NewLocalFileStore(datadir string, basekey []byte) (*FileStore, error) { - params := NewDefaultLocalStoreParams() - params.Init(datadir) - localStore, err := NewLocalStore(params, nil) - if err != nil { - return nil, err - } - localStore.Validators = append(localStore.Validators, NewContentAddressValidator(MakeHashFunc(DefaultHash))) - return NewFileStore(localStore, NewFileStoreParams()), nil -} - -func NewFileStore(store ChunkStore, params *FileStoreParams) *FileStore { - hashFunc := MakeHashFunc(params.Hash) - return &FileStore{ - ChunkStore: store, - hashFunc: hashFunc, - } -} - -// Retrieve is a public API. Main entry point for document retrieval directly. Used by the -// FS-aware API and httpaccess -// Chunk retrieval blocks on netStore requests with a timeout so reader will -// report error if retrieval of chunks within requested range time out. -// It returns a reader with the chunk data and whether the content was encrypted -func (f *FileStore) Retrieve(ctx context.Context, addr Address) (reader *LazyChunkReader, isEncrypted bool) { - isEncrypted = len(addr) > f.hashFunc().Size() - getter := NewHasherStore(f.ChunkStore, f.hashFunc, isEncrypted) - reader = TreeJoin(ctx, addr, getter, 0) - return -} - -// Store is a public API. Main entry point for document storage directly. Used by the -// FS-aware API and httpaccess -func (f *FileStore) Store(ctx context.Context, data io.Reader, size int64, toEncrypt bool) (addr Address, wait func(context.Context) error, err error) { - putter := NewHasherStore(f.ChunkStore, f.hashFunc, toEncrypt) - return PyramidSplit(ctx, data, putter, putter) -} - -func (f *FileStore) HashSize() int { - return f.hashFunc().Size() -} - -// GetAllReferences is a public API. This endpoint returns all chunk hashes (only) for a given file -func (f *FileStore) GetAllReferences(ctx context.Context, data io.Reader, toEncrypt bool) (addrs AddressCollection, err error) { - // create a special kind of putter, which only will store the references - putter := &hashExplorer{ - hasherStore: NewHasherStore(f.ChunkStore, f.hashFunc, toEncrypt), - } - // do the actual splitting anyway, no way around it - _, wait, err := PyramidSplit(ctx, data, putter, putter) - if err != nil { - return nil, err - } - // wait for splitting to be complete and all chunks processed - err = wait(ctx) - if err != nil { - return nil, err - } - // collect all references - addrs = NewAddressCollection(0) - for _, ref := range putter.references { - addrs = append(addrs, Address(ref)) - } - sort.Sort(addrs) - return addrs, nil -} - -// hashExplorer is a special kind of putter which will only store chunk references -type hashExplorer struct { - *hasherStore - references []Reference - lock sync.Mutex -} - -// HashExplorer's Put will add just the chunk hashes to its `References` -func (he *hashExplorer) Put(ctx context.Context, chunkData ChunkData) (Reference, error) { - // Need to do the actual Put, which returns the references - ref, err := he.hasherStore.Put(ctx, chunkData) - if err != nil { - return nil, err - } - // internally store the reference - he.lock.Lock() - he.references = append(he.references, ref) - he.lock.Unlock() - return ref, nil -} diff --git a/swarm/storage/filestore_test.go b/swarm/storage/filestore_test.go deleted file mode 100644 index 9175d1ce9123..000000000000 --- a/swarm/storage/filestore_test.go +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package storage - -import ( - "bytes" - "context" - "io" - "io/ioutil" - "os" - "testing" - - "github.com/ubiq/go-ubiq/swarm/testutil" -) - -const testDataSize = 0x0001000 - -func TestFileStorerandom(t *testing.T) { - testFileStoreRandom(false, t) - testFileStoreRandom(true, t) -} - -func testFileStoreRandom(toEncrypt bool, t *testing.T) { - tdb, cleanup, err := newTestDbStore(false, false) - defer cleanup() - if err != nil { - t.Fatalf("init dbStore failed: %v", err) - } - db := tdb.LDBStore - db.setCapacity(50000) - memStore := NewMemStore(NewDefaultStoreParams(), db) - localStore := &LocalStore{ - memStore: memStore, - DbStore: db, - } - - fileStore := NewFileStore(localStore, NewFileStoreParams()) - defer os.RemoveAll("/tmp/bzz") - - slice := testutil.RandomBytes(1, testDataSize) - ctx := context.TODO() - key, wait, err := fileStore.Store(ctx, bytes.NewReader(slice), testDataSize, toEncrypt) - if err != nil { - t.Fatalf("Store error: %v", err) - } - err = wait(ctx) - if err != nil { - t.Fatalf("Store waitt error: %v", err.Error()) - } - resultReader, isEncrypted := fileStore.Retrieve(context.TODO(), key) - if isEncrypted != toEncrypt { - t.Fatalf("isEncrypted expected %v got %v", toEncrypt, isEncrypted) - } - resultSlice := make([]byte, testDataSize) - n, err := resultReader.ReadAt(resultSlice, 0) - if err != io.EOF { - t.Fatalf("Retrieve error: %v", err) - } - if n != testDataSize { - t.Fatalf("Slice size error got %d, expected %d.", n, testDataSize) - } - if !bytes.Equal(slice, resultSlice) { - t.Fatalf("Comparison error.") - } - ioutil.WriteFile("/tmp/slice.bzz.16M", slice, 0666) - ioutil.WriteFile("/tmp/result.bzz.16M", resultSlice, 0666) - localStore.memStore = NewMemStore(NewDefaultStoreParams(), db) - resultReader, isEncrypted = fileStore.Retrieve(context.TODO(), key) - if isEncrypted != toEncrypt { - t.Fatalf("isEncrypted expected %v got %v", toEncrypt, isEncrypted) - } - for i := range resultSlice { - resultSlice[i] = 0 - } - n, err = resultReader.ReadAt(resultSlice, 0) - if err != io.EOF { - t.Fatalf("Retrieve error after removing memStore: %v", err) - } - if n != len(slice) { - t.Fatalf("Slice size error after removing memStore got %d, expected %d.", n, len(slice)) - } - if !bytes.Equal(slice, resultSlice) { - t.Fatalf("Comparison error after removing memStore.") - } -} - -func TestFileStoreCapacity(t *testing.T) { - testFileStoreCapacity(false, t) - testFileStoreCapacity(true, t) -} - -func testFileStoreCapacity(toEncrypt bool, t *testing.T) { - tdb, cleanup, err := newTestDbStore(false, false) - defer cleanup() - if err != nil { - t.Fatalf("init dbStore failed: %v", err) - } - db := tdb.LDBStore - memStore := NewMemStore(NewDefaultStoreParams(), db) - localStore := &LocalStore{ - memStore: memStore, - DbStore: db, - } - fileStore := NewFileStore(localStore, NewFileStoreParams()) - slice := testutil.RandomBytes(1, testDataSize) - ctx := context.TODO() - key, wait, err := fileStore.Store(ctx, bytes.NewReader(slice), testDataSize, toEncrypt) - if err != nil { - t.Errorf("Store error: %v", err) - } - err = wait(ctx) - if err != nil { - t.Fatalf("Store error: %v", err) - } - resultReader, isEncrypted := fileStore.Retrieve(context.TODO(), key) - if isEncrypted != toEncrypt { - t.Fatalf("isEncrypted expected %v got %v", toEncrypt, isEncrypted) - } - resultSlice := make([]byte, len(slice)) - n, err := resultReader.ReadAt(resultSlice, 0) - if err != io.EOF { - t.Fatalf("Retrieve error: %v", err) - } - if n != len(slice) { - t.Fatalf("Slice size error got %d, expected %d.", n, len(slice)) - } - if !bytes.Equal(slice, resultSlice) { - t.Fatalf("Comparison error.") - } - // Clear memStore - memStore.setCapacity(0) - // check whether it is, indeed, empty - fileStore.ChunkStore = memStore - resultReader, isEncrypted = fileStore.Retrieve(context.TODO(), key) - if isEncrypted != toEncrypt { - t.Fatalf("isEncrypted expected %v got %v", toEncrypt, isEncrypted) - } - if _, err = resultReader.ReadAt(resultSlice, 0); err == nil { - t.Fatalf("Was able to read %d bytes from an empty memStore.", len(slice)) - } - // check how it works with localStore - fileStore.ChunkStore = localStore - // localStore.dbStore.setCapacity(0) - resultReader, isEncrypted = fileStore.Retrieve(context.TODO(), key) - if isEncrypted != toEncrypt { - t.Fatalf("isEncrypted expected %v got %v", toEncrypt, isEncrypted) - } - for i := range resultSlice { - resultSlice[i] = 0 - } - n, err = resultReader.ReadAt(resultSlice, 0) - if err != io.EOF { - t.Fatalf("Retrieve error after clearing memStore: %v", err) - } - if n != len(slice) { - t.Fatalf("Slice size error after clearing memStore got %d, expected %d.", n, len(slice)) - } - if !bytes.Equal(slice, resultSlice) { - t.Fatalf("Comparison error after clearing memStore.") - } -} - -// TestGetAllReferences only tests that GetAllReferences returns an expected -// number of references for a given file -func TestGetAllReferences(t *testing.T) { - tdb, cleanup, err := newTestDbStore(false, false) - defer cleanup() - if err != nil { - t.Fatalf("init dbStore failed: %v", err) - } - db := tdb.LDBStore - memStore := NewMemStore(NewDefaultStoreParams(), db) - localStore := &LocalStore{ - memStore: memStore, - DbStore: db, - } - fileStore := NewFileStore(localStore, NewFileStoreParams()) - - // testRuns[i] and expectedLen[i] are dataSize and expected length respectively - testRuns := []int{1024, 8192, 16000, 30000, 1000000} - expectedLens := []int{1, 3, 5, 9, 248} - for i, r := range testRuns { - slice := testutil.RandomBytes(1, r) - - addrs, err := fileStore.GetAllReferences(context.Background(), bytes.NewReader(slice), false) - if err != nil { - t.Fatal(err) - } - if len(addrs) != expectedLens[i] { - t.Fatalf("Expected reference array length to be %d, but is %d", expectedLens[i], len(addrs)) - } - } -} diff --git a/swarm/storage/hasherstore.go b/swarm/storage/hasherstore.go deleted file mode 100644 index c347041e5316..000000000000 --- a/swarm/storage/hasherstore.go +++ /dev/null @@ -1,263 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package storage - -import ( - "context" - "fmt" - "sync/atomic" - - ch "github.com/ubiq/go-ubiq/swarm/chunk" - "github.com/ubiq/go-ubiq/swarm/storage/encryption" - "golang.org/x/crypto/sha3" -) - -type hasherStore struct { - store ChunkStore - toEncrypt bool - hashFunc SwarmHasher - hashSize int // content hash size - refSize int64 // reference size (content hash + possibly encryption key) - errC chan error // global error channel - doneC chan struct{} // closed by Close() call to indicate that count is the final number of chunks - quitC chan struct{} // closed to quit unterminated routines - // nrChunks is used with atomic functions - // it is required to be at the end of the struct to ensure 64bit alignment for arm architecture - // see: https://golang.org/pkg/sync/atomic/#pkg-note-BUG - nrChunks uint64 // number of chunks to store -} - -// NewHasherStore creates a hasherStore object, which implements Putter and Getter interfaces. -// With the HasherStore you can put and get chunk data (which is just []byte) into a ChunkStore -// and the hasherStore will take core of encryption/decryption of data if necessary -func NewHasherStore(store ChunkStore, hashFunc SwarmHasher, toEncrypt bool) *hasherStore { - hashSize := hashFunc().Size() - refSize := int64(hashSize) - if toEncrypt { - refSize += encryption.KeyLength - } - - h := &hasherStore{ - store: store, - toEncrypt: toEncrypt, - hashFunc: hashFunc, - hashSize: hashSize, - refSize: refSize, - errC: make(chan error), - doneC: make(chan struct{}), - quitC: make(chan struct{}), - } - - return h -} - -// Put stores the chunkData into the ChunkStore of the hasherStore and returns the reference. -// If hasherStore has a chunkEncryption object, the data will be encrypted. -// Asynchronous function, the data will not necessarily be stored when it returns. -func (h *hasherStore) Put(ctx context.Context, chunkData ChunkData) (Reference, error) { - c := chunkData - var encryptionKey encryption.Key - if h.toEncrypt { - var err error - c, encryptionKey, err = h.encryptChunkData(chunkData) - if err != nil { - return nil, err - } - } - chunk := h.createChunk(c) - h.storeChunk(ctx, chunk) - - return Reference(append(chunk.Address(), encryptionKey...)), nil -} - -// Get returns data of the chunk with the given reference (retrieved from the ChunkStore of hasherStore). -// If the data is encrypted and the reference contains an encryption key, it will be decrypted before -// return. -func (h *hasherStore) Get(ctx context.Context, ref Reference) (ChunkData, error) { - addr, encryptionKey, err := parseReference(ref, h.hashSize) - if err != nil { - return nil, err - } - - chunk, err := h.store.Get(ctx, addr) - if err != nil { - return nil, err - } - - chunkData := ChunkData(chunk.Data()) - toDecrypt := (encryptionKey != nil) - if toDecrypt { - var err error - chunkData, err = h.decryptChunkData(chunkData, encryptionKey) - if err != nil { - return nil, err - } - } - return chunkData, nil -} - -// Close indicates that no more chunks will be put with the hasherStore, so the Wait -// function can return when all the previously put chunks has been stored. -func (h *hasherStore) Close() { - close(h.doneC) -} - -// Wait returns when -// 1) the Close() function has been called and -// 2) all the chunks which has been Put has been stored -func (h *hasherStore) Wait(ctx context.Context) error { - defer close(h.quitC) - var nrStoredChunks uint64 // number of stored chunks - var done bool - doneC := h.doneC - for { - select { - // if context is done earlier, just return with the error - case <-ctx.Done(): - return ctx.Err() - // doneC is closed if all chunks have been submitted, from then we just wait until all of them are also stored - case <-doneC: - done = true - doneC = nil - // a chunk has been stored, if err is nil, then successfully, so increase the stored chunk counter - case err := <-h.errC: - if err != nil { - return err - } - nrStoredChunks++ - } - // if all the chunks have been submitted and all of them are stored, then we can return - if done { - if nrStoredChunks >= atomic.LoadUint64(&h.nrChunks) { - return nil - } - } - } -} - -func (h *hasherStore) createHash(chunkData ChunkData) Address { - hasher := h.hashFunc() - hasher.ResetWithLength(chunkData[:8]) // 8 bytes of length - hasher.Write(chunkData[8:]) // minus 8 []byte length - return hasher.Sum(nil) -} - -func (h *hasherStore) createChunk(chunkData ChunkData) *chunk { - hash := h.createHash(chunkData) - chunk := NewChunk(hash, chunkData) - return chunk -} - -func (h *hasherStore) encryptChunkData(chunkData ChunkData) (ChunkData, encryption.Key, error) { - if len(chunkData) < 8 { - return nil, nil, fmt.Errorf("Invalid ChunkData, min length 8 got %v", len(chunkData)) - } - - key, encryptedSpan, encryptedData, err := h.encrypt(chunkData) - if err != nil { - return nil, nil, err - } - c := make(ChunkData, len(encryptedSpan)+len(encryptedData)) - copy(c[:8], encryptedSpan) - copy(c[8:], encryptedData) - return c, key, nil -} - -func (h *hasherStore) decryptChunkData(chunkData ChunkData, encryptionKey encryption.Key) (ChunkData, error) { - if len(chunkData) < 8 { - return nil, fmt.Errorf("Invalid ChunkData, min length 8 got %v", len(chunkData)) - } - - decryptedSpan, decryptedData, err := h.decrypt(chunkData, encryptionKey) - if err != nil { - return nil, err - } - - // removing extra bytes which were just added for padding - length := ChunkData(decryptedSpan).Size() - for length > ch.DefaultSize { - length = length + (ch.DefaultSize - 1) - length = length / ch.DefaultSize - length *= uint64(h.refSize) - } - - c := make(ChunkData, length+8) - copy(c[:8], decryptedSpan) - copy(c[8:], decryptedData[:length]) - - return c, nil -} - -func (h *hasherStore) RefSize() int64 { - return h.refSize -} - -func (h *hasherStore) encrypt(chunkData ChunkData) (encryption.Key, []byte, []byte, error) { - key := encryption.GenerateRandomKey(encryption.KeyLength) - encryptedSpan, err := h.newSpanEncryption(key).Encrypt(chunkData[:8]) - if err != nil { - return nil, nil, nil, err - } - encryptedData, err := h.newDataEncryption(key).Encrypt(chunkData[8:]) - if err != nil { - return nil, nil, nil, err - } - return key, encryptedSpan, encryptedData, nil -} - -func (h *hasherStore) decrypt(chunkData ChunkData, key encryption.Key) ([]byte, []byte, error) { - encryptedSpan, err := h.newSpanEncryption(key).Encrypt(chunkData[:8]) - if err != nil { - return nil, nil, err - } - encryptedData, err := h.newDataEncryption(key).Encrypt(chunkData[8:]) - if err != nil { - return nil, nil, err - } - return encryptedSpan, encryptedData, nil -} - -func (h *hasherStore) newSpanEncryption(key encryption.Key) encryption.Encryption { - return encryption.New(key, 0, uint32(ch.DefaultSize/h.refSize), sha3.NewLegacyKeccak256) -} - -func (h *hasherStore) newDataEncryption(key encryption.Key) encryption.Encryption { - return encryption.New(key, int(ch.DefaultSize), 0, sha3.NewLegacyKeccak256) -} - -func (h *hasherStore) storeChunk(ctx context.Context, chunk *chunk) { - atomic.AddUint64(&h.nrChunks, 1) - go func() { - select { - case h.errC <- h.store.Put(ctx, chunk): - case <-h.quitC: - } - }() -} - -func parseReference(ref Reference, hashSize int) (Address, encryption.Key, error) { - encryptedRefLength := hashSize + encryption.KeyLength - switch len(ref) { - case AddressLength: - return Address(ref), nil, nil - case encryptedRefLength: - encKeyIdx := len(ref) - encryption.KeyLength - return Address(ref[:encKeyIdx]), encryption.Key(ref[encKeyIdx:]), nil - default: - return nil, nil, fmt.Errorf("Invalid reference length, expected %v or %v got %v", hashSize, encryptedRefLength, len(ref)) - } -} diff --git a/swarm/storage/hasherstore_test.go b/swarm/storage/hasherstore_test.go deleted file mode 100644 index 51b8126e9c4d..000000000000 --- a/swarm/storage/hasherstore_test.go +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package storage - -import ( - "bytes" - "context" - "testing" - - "github.com/ubiq/go-ubiq/swarm/storage/encryption" - - "github.com/ubiq/go-ubiq/common" -) - -func TestHasherStore(t *testing.T) { - var tests = []struct { - chunkLength int - toEncrypt bool - }{ - {10, false}, - {100, false}, - {1000, false}, - {4096, false}, - {10, true}, - {100, true}, - {1000, true}, - {4096, true}, - } - - for _, tt := range tests { - chunkStore := NewMapChunkStore() - hasherStore := NewHasherStore(chunkStore, MakeHashFunc(DefaultHash), tt.toEncrypt) - - // Put two random chunks into the hasherStore - chunkData1 := GenerateRandomChunk(int64(tt.chunkLength)).Data() - ctx, cancel := context.WithTimeout(context.Background(), getTimeout) - defer cancel() - key1, err := hasherStore.Put(ctx, chunkData1) - if err != nil { - t.Fatalf("Expected no error got \"%v\"", err) - } - - chunkData2 := GenerateRandomChunk(int64(tt.chunkLength)).Data() - key2, err := hasherStore.Put(ctx, chunkData2) - if err != nil { - t.Fatalf("Expected no error got \"%v\"", err) - } - - hasherStore.Close() - - // Wait until chunks are really stored - err = hasherStore.Wait(ctx) - if err != nil { - t.Fatalf("Expected no error got \"%v\"", err) - } - - // Get the first chunk - retrievedChunkData1, err := hasherStore.Get(ctx, key1) - if err != nil { - t.Fatalf("Expected no error, got \"%v\"", err) - } - - // Retrieved data should be same as the original - if !bytes.Equal(chunkData1, retrievedChunkData1) { - t.Fatalf("Expected retrieved chunk data %v, got %v", common.Bytes2Hex(chunkData1), common.Bytes2Hex(retrievedChunkData1)) - } - - // Get the second chunk - retrievedChunkData2, err := hasherStore.Get(ctx, key2) - if err != nil { - t.Fatalf("Expected no error, got \"%v\"", err) - } - - // Retrieved data should be same as the original - if !bytes.Equal(chunkData2, retrievedChunkData2) { - t.Fatalf("Expected retrieved chunk data %v, got %v", common.Bytes2Hex(chunkData2), common.Bytes2Hex(retrievedChunkData2)) - } - - hash1, encryptionKey1, err := parseReference(key1, hasherStore.hashSize) - if err != nil { - t.Fatalf("Expected no error, got \"%v\"", err) - } - - if tt.toEncrypt { - if encryptionKey1 == nil { - t.Fatal("Expected non-nil encryption key, got nil") - } else if len(encryptionKey1) != encryption.KeyLength { - t.Fatalf("Expected encryption key length %v, got %v", encryption.KeyLength, len(encryptionKey1)) - } - } - if !tt.toEncrypt && encryptionKey1 != nil { - t.Fatalf("Expected nil encryption key, got key with length %v", len(encryptionKey1)) - } - - // Check if chunk data in store is encrypted or not - chunkInStore, err := chunkStore.Get(ctx, hash1) - if err != nil { - t.Fatalf("Expected no error got \"%v\"", err) - } - - chunkDataInStore := chunkInStore.Data() - - if tt.toEncrypt && bytes.Equal(chunkData1, chunkDataInStore) { - t.Fatalf("Chunk expected to be encrypted but it is stored without encryption") - } - if !tt.toEncrypt && !bytes.Equal(chunkData1, chunkDataInStore) { - t.Fatalf("Chunk expected to be not encrypted but stored content is different. Expected %v got %v", common.Bytes2Hex(chunkData1), common.Bytes2Hex(chunkDataInStore)) - } - } -} diff --git a/swarm/storage/ldbstore.go b/swarm/storage/ldbstore.go deleted file mode 100644 index 2b6940b02f0d..000000000000 --- a/swarm/storage/ldbstore.go +++ /dev/null @@ -1,1077 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// disk storage layer for the package bzz -// DbStore implements the ChunkStore interface and is used by the FileStore as -// persistent storage of chunks -// it implements purging based on access count allowing for external control of -// max capacity - -package storage - -import ( - "archive/tar" - "bytes" - "context" - "encoding/binary" - "encoding/hex" - "errors" - "fmt" - "io" - "io/ioutil" - "sync" - - "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/rlp" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/storage/mock" - "github.com/syndtr/goleveldb/leveldb" -) - -const ( - defaultGCRatio = 10 - defaultMaxGCRound = 10000 - defaultMaxGCBatch = 5000 - - wEntryCnt = 1 << 0 - wIndexCnt = 1 << 1 - wAccessCnt = 1 << 2 -) - -var ( - dbEntryCount = metrics.NewRegisteredCounter("ldbstore.entryCnt", nil) -) - -var ( - keyIndex = byte(0) - keyAccessCnt = []byte{2} - keyEntryCnt = []byte{3} - keyDataIdx = []byte{4} - keyData = byte(6) - keyDistanceCnt = byte(7) - keySchema = []byte{8} - keyGCIdx = byte(9) // access to chunk data index, used by garbage collection in ascending order from first entry -) - -var ( - ErrDBClosed = errors.New("LDBStore closed") -) - -type LDBStoreParams struct { - *StoreParams - Path string - Po func(Address) uint8 -} - -// NewLDBStoreParams constructs LDBStoreParams with the specified values. -func NewLDBStoreParams(storeparams *StoreParams, path string) *LDBStoreParams { - return &LDBStoreParams{ - StoreParams: storeparams, - Path: path, - Po: func(k Address) (ret uint8) { return uint8(Proximity(storeparams.BaseKey, k[:])) }, - } -} - -type garbage struct { - maxRound int // maximum number of chunks to delete in one garbage collection round - maxBatch int // maximum number of chunks to delete in one db request batch - ratio int // 1/x ratio to calculate the number of chunks to gc on a low capacity db - count int // number of chunks deleted in running round - target int // number of chunks to delete in running round - batch *dbBatch // the delete batch - runC chan struct{} // struct in chan means gc is NOT running -} - -type LDBStore struct { - db *LDBDatabase - - // this should be stored in db, accessed transactionally - entryCnt uint64 // number of items in the LevelDB - accessCnt uint64 // ever-accumulating number increased every time we read/access an entry - dataIdx uint64 // similar to entryCnt, but we only increment it - capacity uint64 - bucketCnt []uint64 - - hashfunc SwarmHasher - po func(Address) uint8 - - batchesC chan struct{} - closed bool - batch *dbBatch - lock sync.RWMutex - quit chan struct{} - gc *garbage - - // Functions encodeDataFunc is used to bypass - // the default functionality of DbStore with - // mock.NodeStore for testing purposes. - encodeDataFunc func(chunk Chunk) []byte - // If getDataFunc is defined, it will be used for - // retrieving the chunk data instead from the local - // LevelDB database. - getDataFunc func(key Address) (data []byte, err error) -} - -type dbBatch struct { - *leveldb.Batch - err error - c chan struct{} -} - -func newBatch() *dbBatch { - return &dbBatch{Batch: new(leveldb.Batch), c: make(chan struct{})} -} - -// TODO: Instead of passing the distance function, just pass the address from which distances are calculated -// to avoid the appearance of a pluggable distance metric and opportunities of bugs associated with providing -// a function different from the one that is actually used. -func NewLDBStore(params *LDBStoreParams) (s *LDBStore, err error) { - s = new(LDBStore) - s.hashfunc = params.Hash - s.quit = make(chan struct{}) - - s.batchesC = make(chan struct{}, 1) - go s.writeBatches() - s.batch = newBatch() - // associate encodeData with default functionality - s.encodeDataFunc = encodeData - - s.db, err = NewLDBDatabase(params.Path) - if err != nil { - return nil, err - } - - s.po = params.Po - s.setCapacity(params.DbCapacity) - - s.bucketCnt = make([]uint64, 0x100) - for i := 0; i < 0x100; i++ { - k := make([]byte, 2) - k[0] = keyDistanceCnt - k[1] = uint8(i) - cnt, _ := s.db.Get(k) - s.bucketCnt[i] = BytesToU64(cnt) - } - data, _ := s.db.Get(keyEntryCnt) - s.entryCnt = BytesToU64(data) - data, _ = s.db.Get(keyAccessCnt) - s.accessCnt = BytesToU64(data) - data, _ = s.db.Get(keyDataIdx) - s.dataIdx = BytesToU64(data) - - // set up garbage collection - s.gc = &garbage{ - maxBatch: defaultMaxGCBatch, - maxRound: defaultMaxGCRound, - ratio: defaultGCRatio, - } - - s.gc.runC = make(chan struct{}, 1) - s.gc.runC <- struct{}{} - - return s, nil -} - -// MarkAccessed increments the access counter as a best effort for a chunk, so -// the chunk won't get garbage collected. -func (s *LDBStore) MarkAccessed(addr Address) { - s.lock.Lock() - defer s.lock.Unlock() - - if s.closed { - return - } - - proximity := s.po(addr) - s.tryAccessIdx(addr, proximity) -} - -// initialize and set values for processing of gc round -func (s *LDBStore) startGC(c int) { - - s.gc.count = 0 - // calculate the target number of deletions - if c >= s.gc.maxRound { - s.gc.target = s.gc.maxRound - } else { - s.gc.target = c / s.gc.ratio - } - s.gc.batch = newBatch() - log.Debug("startgc", "requested", c, "target", s.gc.target) -} - -// NewMockDbStore creates a new instance of DbStore with -// mockStore set to a provided value. If mockStore argument is nil, -// this function behaves exactly as NewDbStore. -func NewMockDbStore(params *LDBStoreParams, mockStore *mock.NodeStore) (s *LDBStore, err error) { - s, err = NewLDBStore(params) - if err != nil { - return nil, err - } - - // replace put and get with mock store functionality - if mockStore != nil { - s.encodeDataFunc = newMockEncodeDataFunc(mockStore) - s.getDataFunc = newMockGetDataFunc(mockStore) - } - return -} - -type dpaDBIndex struct { - Idx uint64 - Access uint64 -} - -func BytesToU64(data []byte) uint64 { - if len(data) < 8 { - return 0 - } - return binary.BigEndian.Uint64(data) -} - -func U64ToBytes(val uint64) []byte { - data := make([]byte, 8) - binary.BigEndian.PutUint64(data, val) - return data -} - -func getIndexKey(hash Address) []byte { - hashSize := len(hash) - key := make([]byte, hashSize+1) - key[0] = keyIndex - copy(key[1:], hash[:]) - return key -} - -func getDataKey(idx uint64, po uint8) []byte { - key := make([]byte, 10) - key[0] = keyData - key[1] = po - binary.BigEndian.PutUint64(key[2:], idx) - - return key -} - -func getGCIdxKey(index *dpaDBIndex) []byte { - key := make([]byte, 9) - key[0] = keyGCIdx - binary.BigEndian.PutUint64(key[1:], index.Access) - return key -} - -func getGCIdxValue(index *dpaDBIndex, po uint8, addr Address) []byte { - val := make([]byte, 41) // po = 1, index.Index = 8, Address = 32 - val[0] = po - binary.BigEndian.PutUint64(val[1:], index.Idx) - copy(val[9:], addr) - return val -} - -func parseIdxKey(key []byte) (byte, []byte) { - return key[0], key[1:] -} - -func parseGCIdxEntry(accessCnt []byte, val []byte) (index *dpaDBIndex, po uint8, addr Address) { - index = &dpaDBIndex{ - Idx: binary.BigEndian.Uint64(val[1:]), - Access: binary.BigEndian.Uint64(accessCnt), - } - po = val[0] - addr = val[9:] - return -} - -func encodeIndex(index *dpaDBIndex) []byte { - data, _ := rlp.EncodeToBytes(index) - return data -} - -func encodeData(chunk Chunk) []byte { - // Always create a new underlying array for the returned byte slice. - // The chunk.Address array may be used in the returned slice which - // may be changed later in the code or by the LevelDB, resulting - // that the Address is changed as well. - return append(append([]byte{}, chunk.Address()[:]...), chunk.Data()...) -} - -func decodeIndex(data []byte, index *dpaDBIndex) error { - dec := rlp.NewStream(bytes.NewReader(data), 0) - return dec.Decode(index) -} - -func decodeData(addr Address, data []byte) (*chunk, error) { - return NewChunk(addr, data[32:]), nil -} - -func (s *LDBStore) collectGarbage() error { - // prevent duplicate gc from starting when one is already running - select { - case <-s.gc.runC: - default: - return nil - } - - s.lock.Lock() - entryCnt := s.entryCnt - s.lock.Unlock() - - metrics.GetOrRegisterCounter("ldbstore.collectgarbage", nil).Inc(1) - - // calculate the amount of chunks to collect and reset counter - s.startGC(int(entryCnt)) - log.Debug("collectGarbage", "target", s.gc.target, "entryCnt", entryCnt) - - for s.gc.count < s.gc.target { - it := s.db.NewIterator() - ok := it.Seek([]byte{keyGCIdx}) - var singleIterationCount int - - // every batch needs a lock so we avoid entries changing accessidx in the meantime - s.lock.Lock() - for ; ok && (singleIterationCount < s.gc.maxBatch); ok = it.Next() { - - // quit if no more access index keys - itkey := it.Key() - if (itkey == nil) || (itkey[0] != keyGCIdx) { - break - } - - // get chunk data entry from access index - val := it.Value() - index, po, hash := parseGCIdxEntry(itkey[1:], val) - keyIdx := make([]byte, 33) - keyIdx[0] = keyIndex - copy(keyIdx[1:], hash) - - // add delete operation to batch - s.delete(s.gc.batch.Batch, index, keyIdx, po) - singleIterationCount++ - s.gc.count++ - log.Trace("garbage collect enqueued chunk for deletion", "key", hash) - - // break if target is not on max garbage batch boundary - if s.gc.count >= s.gc.target { - break - } - } - - s.writeBatch(s.gc.batch, wEntryCnt) - log.Trace("garbage collect batch done", "batch", singleIterationCount, "total", s.gc.count) - s.lock.Unlock() - it.Release() - } - - metrics.GetOrRegisterCounter("ldbstore.collectgarbage.delete", nil).Inc(int64(s.gc.count)) - log.Debug("garbage collect done", "c", s.gc.count) - s.gc.runC <- struct{}{} - - return nil -} - -// Export writes all chunks from the store to a tar archive, returning the -// number of chunks written. -func (s *LDBStore) Export(out io.Writer) (int64, error) { - tw := tar.NewWriter(out) - defer tw.Close() - - it := s.db.NewIterator() - defer it.Release() - var count int64 - for ok := it.Seek([]byte{keyIndex}); ok; ok = it.Next() { - key := it.Key() - if (key == nil) || (key[0] != keyIndex) { - break - } - - var index dpaDBIndex - - hash := key[1:] - decodeIndex(it.Value(), &index) - po := s.po(hash) - datakey := getDataKey(index.Idx, po) - log.Trace("store.export", "dkey", fmt.Sprintf("%x", datakey), "dataidx", index.Idx, "po", po) - data, err := s.db.Get(datakey) - if err != nil { - log.Warn(fmt.Sprintf("Chunk %x found but could not be accessed: %v", key, err)) - continue - } - - hdr := &tar.Header{ - Name: hex.EncodeToString(hash), - Mode: 0644, - Size: int64(len(data)), - } - if err := tw.WriteHeader(hdr); err != nil { - return count, err - } - if _, err := tw.Write(data); err != nil { - return count, err - } - count++ - } - - return count, nil -} - -// of chunks read. -func (s *LDBStore) Import(in io.Reader) (int64, error) { - tr := tar.NewReader(in) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - countC := make(chan int64) - errC := make(chan error) - var count int64 - go func() { - for { - hdr, err := tr.Next() - if err == io.EOF { - break - } else if err != nil { - select { - case errC <- err: - case <-ctx.Done(): - } - } - - if len(hdr.Name) != 64 { - log.Warn("ignoring non-chunk file", "name", hdr.Name) - continue - } - - keybytes, err := hex.DecodeString(hdr.Name) - if err != nil { - log.Warn("ignoring invalid chunk file", "name", hdr.Name, "err", err) - continue - } - - data, err := ioutil.ReadAll(tr) - if err != nil { - select { - case errC <- err: - case <-ctx.Done(): - } - } - key := Address(keybytes) - chunk := NewChunk(key, data[32:]) - - go func() { - select { - case errC <- s.Put(ctx, chunk): - case <-ctx.Done(): - } - }() - - count++ - } - countC <- count - }() - - // wait for all chunks to be stored - i := int64(0) - var total int64 - for { - select { - case err := <-errC: - if err != nil { - return count, err - } - i++ - case total = <-countC: - case <-ctx.Done(): - return i, ctx.Err() - } - if total > 0 && i == total { - return total, nil - } - } -} - -// Cleanup iterates over the database and deletes chunks if they pass the `f` condition -func (s *LDBStore) Cleanup(f func(*chunk) bool) { - var errorsFound, removed, total int - - it := s.db.NewIterator() - defer it.Release() - for ok := it.Seek([]byte{keyIndex}); ok; ok = it.Next() { - key := it.Key() - if (key == nil) || (key[0] != keyIndex) { - break - } - total++ - var index dpaDBIndex - err := decodeIndex(it.Value(), &index) - if err != nil { - log.Warn("Cannot decode") - errorsFound++ - continue - } - hash := key[1:] - po := s.po(hash) - datakey := getDataKey(index.Idx, po) - data, err := s.db.Get(datakey) - if err != nil { - found := false - - // highest possible proximity is 255 - for po = 1; po <= 255; po++ { - datakey = getDataKey(index.Idx, po) - data, err = s.db.Get(datakey) - if err == nil { - found = true - break - } - } - - if !found { - log.Warn(fmt.Sprintf("Chunk %x found but count not be accessed with any po", key)) - errorsFound++ - continue - } - } - - ck := data[:32] - c, err := decodeData(ck, data) - if err != nil { - log.Error("decodeData error", "err", err) - continue - } - - cs := int64(binary.LittleEndian.Uint64(c.sdata[:8])) - log.Trace("chunk", "key", fmt.Sprintf("%x", key), "ck", fmt.Sprintf("%x", ck), "dkey", fmt.Sprintf("%x", datakey), "dataidx", index.Idx, "po", po, "len data", len(data), "len sdata", len(c.sdata), "size", cs) - - // if chunk is to be removed - if f(c) { - log.Warn("chunk for cleanup", "key", fmt.Sprintf("%x", key), "ck", fmt.Sprintf("%x", ck), "dkey", fmt.Sprintf("%x", datakey), "dataidx", index.Idx, "po", po, "len data", len(data), "len sdata", len(c.sdata), "size", cs) - s.deleteNow(&index, getIndexKey(key[1:]), po) - removed++ - errorsFound++ - } - } - - log.Warn(fmt.Sprintf("Found %v errors out of %v entries. Removed %v chunks.", errorsFound, total, removed)) -} - -// CleanGCIndex rebuilds the garbage collector index from scratch, while -// removing inconsistent elements, e.g., indices with missing data chunks. -// WARN: it's a pretty heavy, long running function. -func (s *LDBStore) CleanGCIndex() error { - s.lock.Lock() - defer s.lock.Unlock() - - batch := leveldb.Batch{} - - var okEntryCount uint64 - var totalEntryCount uint64 - - // throw out all gc indices, we will rebuild from cleaned index - it := s.db.NewIterator() - it.Seek([]byte{keyGCIdx}) - var gcDeletes int - for it.Valid() { - rowType, _ := parseIdxKey(it.Key()) - if rowType != keyGCIdx { - break - } - batch.Delete(it.Key()) - gcDeletes++ - it.Next() - } - log.Debug("gc", "deletes", gcDeletes) - if err := s.db.Write(&batch); err != nil { - return err - } - batch.Reset() - - it.Release() - - // corrected po index pointer values - var poPtrs [256]uint64 - - // set to true if chunk count not on 4096 iteration boundary - var doneIterating bool - - // last key index in previous iteration - lastIdxKey := []byte{keyIndex} - - // counter for debug output - var cleanBatchCount int - - // go through all key index entries - for !doneIterating { - cleanBatchCount++ - var idxs []dpaDBIndex - var chunkHashes [][]byte - var pos []uint8 - it := s.db.NewIterator() - - it.Seek(lastIdxKey) - - // 4096 is just a nice number, don't look for any hidden meaning here... - var i int - for i = 0; i < 4096; i++ { - - // this really shouldn't happen unless database is empty - // but let's keep it to be safe - if !it.Valid() { - doneIterating = true - break - } - - // if it's not keyindex anymore we're done iterating - rowType, chunkHash := parseIdxKey(it.Key()) - if rowType != keyIndex { - doneIterating = true - break - } - - // decode the retrieved index - var idx dpaDBIndex - err := decodeIndex(it.Value(), &idx) - if err != nil { - return fmt.Errorf("corrupt index: %v", err) - } - po := s.po(chunkHash) - lastIdxKey = it.Key() - - // if we don't find the data key, remove the entry - // if we find it, add to the array of new gc indices to create - dataKey := getDataKey(idx.Idx, po) - _, err = s.db.Get(dataKey) - if err != nil { - log.Warn("deleting inconsistent index (missing data)", "key", chunkHash) - batch.Delete(it.Key()) - } else { - idxs = append(idxs, idx) - chunkHashes = append(chunkHashes, chunkHash) - pos = append(pos, po) - okEntryCount++ - if idx.Idx > poPtrs[po] { - poPtrs[po] = idx.Idx - } - } - totalEntryCount++ - it.Next() - } - it.Release() - - // flush the key index corrections - err := s.db.Write(&batch) - if err != nil { - return err - } - batch.Reset() - - // add correct gc indices - for i, okIdx := range idxs { - gcIdxKey := getGCIdxKey(&okIdx) - gcIdxData := getGCIdxValue(&okIdx, pos[i], chunkHashes[i]) - batch.Put(gcIdxKey, gcIdxData) - log.Trace("clean ok", "key", chunkHashes[i], "gcKey", gcIdxKey, "gcData", gcIdxData) - } - - // flush them - err = s.db.Write(&batch) - if err != nil { - return err - } - batch.Reset() - - log.Debug("clean gc index pass", "batch", cleanBatchCount, "checked", i, "kept", len(idxs)) - } - - log.Debug("gc cleanup entries", "ok", okEntryCount, "total", totalEntryCount, "batchlen", batch.Len()) - - // lastly add updated entry count - var entryCount [8]byte - binary.BigEndian.PutUint64(entryCount[:], okEntryCount) - batch.Put(keyEntryCnt, entryCount[:]) - - // and add the new po index pointers - var poKey [2]byte - poKey[0] = keyDistanceCnt - for i, poPtr := range poPtrs { - poKey[1] = uint8(i) - if poPtr == 0 { - batch.Delete(poKey[:]) - } else { - var idxCount [8]byte - binary.BigEndian.PutUint64(idxCount[:], poPtr) - batch.Put(poKey[:], idxCount[:]) - } - } - - // if you made it this far your harddisk has survived. Congratulations - return s.db.Write(&batch) -} - -// Delete is removes a chunk and updates indices. -// Is thread safe -func (s *LDBStore) Delete(addr Address) error { - s.lock.Lock() - defer s.lock.Unlock() - - ikey := getIndexKey(addr) - - idata, err := s.db.Get(ikey) - if err != nil { - return err - } - - var idx dpaDBIndex - decodeIndex(idata, &idx) - proximity := s.po(addr) - return s.deleteNow(&idx, ikey, proximity) -} - -// executes one delete operation immediately -// see *LDBStore.delete -func (s *LDBStore) deleteNow(idx *dpaDBIndex, idxKey []byte, po uint8) error { - batch := new(leveldb.Batch) - s.delete(batch, idx, idxKey, po) - return s.db.Write(batch) -} - -// adds a delete chunk operation to the provided batch -// if called directly, decrements entrycount regardless if the chunk exists upon deletion. Risk of wrap to max uint64 -func (s *LDBStore) delete(batch *leveldb.Batch, idx *dpaDBIndex, idxKey []byte, po uint8) { - metrics.GetOrRegisterCounter("ldbstore.delete", nil).Inc(1) - - gcIdxKey := getGCIdxKey(idx) - batch.Delete(gcIdxKey) - dataKey := getDataKey(idx.Idx, po) - batch.Delete(dataKey) - batch.Delete(idxKey) - s.entryCnt-- - dbEntryCount.Dec(1) - cntKey := make([]byte, 2) - cntKey[0] = keyDistanceCnt - cntKey[1] = po - batch.Put(keyEntryCnt, U64ToBytes(s.entryCnt)) - batch.Put(cntKey, U64ToBytes(s.bucketCnt[po])) -} - -func (s *LDBStore) BinIndex(po uint8) uint64 { - s.lock.RLock() - defer s.lock.RUnlock() - return s.bucketCnt[po] -} - -// Put adds a chunk to the database, adding indices and incrementing global counters. -// If it already exists, it merely increments the access count of the existing entry. -// Is thread safe -func (s *LDBStore) Put(ctx context.Context, chunk Chunk) error { - metrics.GetOrRegisterCounter("ldbstore.put", nil).Inc(1) - log.Trace("ldbstore.put", "key", chunk.Address()) - - ikey := getIndexKey(chunk.Address()) - var index dpaDBIndex - - po := s.po(chunk.Address()) - - s.lock.Lock() - - if s.closed { - s.lock.Unlock() - return ErrDBClosed - } - batch := s.batch - - log.Trace("ldbstore.put: s.db.Get", "key", chunk.Address(), "ikey", fmt.Sprintf("%x", ikey)) - _, err := s.db.Get(ikey) - if err != nil { - s.doPut(chunk, &index, po) - } - idata := encodeIndex(&index) - s.batch.Put(ikey, idata) - - // add the access-chunkindex index for garbage collection - gcIdxKey := getGCIdxKey(&index) - gcIdxData := getGCIdxValue(&index, po, chunk.Address()) - s.batch.Put(gcIdxKey, gcIdxData) - s.lock.Unlock() - - select { - case s.batchesC <- struct{}{}: - default: - } - - select { - case <-batch.c: - return batch.err - case <-ctx.Done(): - return ctx.Err() - } -} - -// force putting into db, does not check or update necessary indices -func (s *LDBStore) doPut(chunk Chunk, index *dpaDBIndex, po uint8) { - data := s.encodeDataFunc(chunk) - dkey := getDataKey(s.dataIdx, po) - s.batch.Put(dkey, data) - index.Idx = s.dataIdx - s.bucketCnt[po] = s.dataIdx - s.entryCnt++ - dbEntryCount.Inc(1) - s.dataIdx++ - index.Access = s.accessCnt - s.accessCnt++ - cntKey := make([]byte, 2) - cntKey[0] = keyDistanceCnt - cntKey[1] = po - s.batch.Put(cntKey, U64ToBytes(s.bucketCnt[po])) -} - -func (s *LDBStore) writeBatches() { - for { - select { - case <-s.quit: - log.Debug("DbStore: quit batch write loop") - return - case <-s.batchesC: - err := s.writeCurrentBatch() - if err != nil { - log.Debug("DbStore: quit batch write loop", "err", err.Error()) - return - } - } - } - -} - -func (s *LDBStore) writeCurrentBatch() error { - s.lock.Lock() - defer s.lock.Unlock() - b := s.batch - l := b.Len() - if l == 0 { - return nil - } - s.batch = newBatch() - b.err = s.writeBatch(b, wEntryCnt|wAccessCnt|wIndexCnt) - close(b.c) - if s.entryCnt >= s.capacity { - go s.collectGarbage() - } - return nil -} - -// must be called non concurrently -func (s *LDBStore) writeBatch(b *dbBatch, wFlag uint8) error { - if wFlag&wEntryCnt > 0 { - b.Put(keyEntryCnt, U64ToBytes(s.entryCnt)) - } - if wFlag&wIndexCnt > 0 { - b.Put(keyDataIdx, U64ToBytes(s.dataIdx)) - } - if wFlag&wAccessCnt > 0 { - b.Put(keyAccessCnt, U64ToBytes(s.accessCnt)) - } - l := b.Len() - if err := s.db.Write(b.Batch); err != nil { - return fmt.Errorf("unable to write batch: %v", err) - } - log.Trace(fmt.Sprintf("batch write (%d entries)", l)) - return nil -} - -// newMockEncodeDataFunc returns a function that stores the chunk data -// to a mock store to bypass the default functionality encodeData. -// The constructed function always returns the nil data, as DbStore does -// not need to store the data, but still need to create the index. -func newMockEncodeDataFunc(mockStore *mock.NodeStore) func(chunk Chunk) []byte { - return func(chunk Chunk) []byte { - if err := mockStore.Put(chunk.Address(), encodeData(chunk)); err != nil { - log.Error(fmt.Sprintf("%T: Chunk %v put: %v", mockStore, chunk.Address().Log(), err)) - } - return chunk.Address()[:] - } -} - -// tryAccessIdx tries to find index entry. If found then increments the access -// count for garbage collection and returns the index entry and true for found, -// otherwise returns nil and false. -func (s *LDBStore) tryAccessIdx(addr Address, po uint8) (*dpaDBIndex, bool) { - ikey := getIndexKey(addr) - idata, err := s.db.Get(ikey) - if err != nil { - return nil, false - } - - index := new(dpaDBIndex) - decodeIndex(idata, index) - oldGCIdxKey := getGCIdxKey(index) - s.batch.Put(keyAccessCnt, U64ToBytes(s.accessCnt)) - index.Access = s.accessCnt - idata = encodeIndex(index) - s.accessCnt++ - s.batch.Put(ikey, idata) - newGCIdxKey := getGCIdxKey(index) - newGCIdxData := getGCIdxValue(index, po, ikey[1:]) - s.batch.Delete(oldGCIdxKey) - s.batch.Put(newGCIdxKey, newGCIdxData) - select { - case s.batchesC <- struct{}{}: - default: - } - return index, true -} - -// GetSchema is returning the current named schema of the datastore as read from LevelDB -func (s *LDBStore) GetSchema() (string, error) { - s.lock.Lock() - defer s.lock.Unlock() - - data, err := s.db.Get(keySchema) - if err != nil { - if err == leveldb.ErrNotFound { - return DbSchemaNone, nil - } - return "", err - } - - return string(data), nil -} - -// PutSchema is saving a named schema to the LevelDB datastore -func (s *LDBStore) PutSchema(schema string) error { - s.lock.Lock() - defer s.lock.Unlock() - - return s.db.Put(keySchema, []byte(schema)) -} - -// Get retrieves the chunk matching the provided key from the database. -// If the chunk entry does not exist, it returns an error -// Updates access count and is thread safe -func (s *LDBStore) Get(_ context.Context, addr Address) (chunk Chunk, err error) { - metrics.GetOrRegisterCounter("ldbstore.get", nil).Inc(1) - log.Trace("ldbstore.get", "key", addr) - - s.lock.Lock() - defer s.lock.Unlock() - return s.get(addr) -} - -// Has queries the underlying DB if a chunk with the given address is stored -// Returns true if the chunk is found, false if not -func (s *LDBStore) Has(_ context.Context, addr Address) bool { - s.lock.RLock() - defer s.lock.RUnlock() - - ikey := getIndexKey(addr) - _, err := s.db.Get(ikey) - - return err == nil -} - -// TODO: To conform with other private methods of this object indices should not be updated -func (s *LDBStore) get(addr Address) (chunk *chunk, err error) { - if s.closed { - return nil, ErrDBClosed - } - proximity := s.po(addr) - index, found := s.tryAccessIdx(addr, proximity) - if found { - var data []byte - if s.getDataFunc != nil { - // if getDataFunc is defined, use it to retrieve the chunk data - log.Trace("ldbstore.get retrieve with getDataFunc", "key", addr) - data, err = s.getDataFunc(addr) - if err != nil { - return - } - } else { - // default DbStore functionality to retrieve chunk data - datakey := getDataKey(index.Idx, proximity) - data, err = s.db.Get(datakey) - log.Trace("ldbstore.get retrieve", "key", addr, "indexkey", index.Idx, "datakey", fmt.Sprintf("%x", datakey), "proximity", proximity) - if err != nil { - log.Trace("ldbstore.get chunk found but could not be accessed", "key", addr, "err", err) - s.deleteNow(index, getIndexKey(addr), s.po(addr)) - return - } - } - - return decodeData(addr, data) - } else { - err = ErrChunkNotFound - } - - return -} - -// newMockGetFunc returns a function that reads chunk data from -// the mock database, which is used as the value for DbStore.getFunc -// to bypass the default functionality of DbStore with a mock store. -func newMockGetDataFunc(mockStore *mock.NodeStore) func(addr Address) (data []byte, err error) { - return func(addr Address) (data []byte, err error) { - data, err = mockStore.Get(addr) - if err == mock.ErrNotFound { - // preserve ErrChunkNotFound error - err = ErrChunkNotFound - } - return data, err - } -} - -func (s *LDBStore) setCapacity(c uint64) { - s.lock.Lock() - defer s.lock.Unlock() - - s.capacity = c - - for s.entryCnt > c { - s.collectGarbage() - } -} - -func (s *LDBStore) Close() { - close(s.quit) - s.lock.Lock() - s.closed = true - s.lock.Unlock() - // force writing out current batch - s.writeCurrentBatch() - s.db.Close() -} - -// SyncIterator(start, stop, po, f) calls f on each hash of a bin po from start to stop -func (s *LDBStore) SyncIterator(since uint64, until uint64, po uint8, f func(Address, uint64) bool) error { - metrics.GetOrRegisterCounter("ldbstore.synciterator", nil).Inc(1) - - sincekey := getDataKey(since, po) - untilkey := getDataKey(until, po) - it := s.db.NewIterator() - defer it.Release() - - for ok := it.Seek(sincekey); ok; ok = it.Next() { - metrics.GetOrRegisterCounter("ldbstore.synciterator.seek", nil).Inc(1) - - dbkey := it.Key() - if dbkey[0] != keyData || dbkey[1] != po || bytes.Compare(untilkey, dbkey) < 0 { - break - } - key := make([]byte, 32) - val := it.Value() - copy(key, val[:32]) - if !f(Address(key), binary.BigEndian.Uint64(dbkey[2:])) { - break - } - } - return it.Error() -} diff --git a/swarm/storage/ldbstore_test.go b/swarm/storage/ldbstore_test.go deleted file mode 100644 index d1d780f2cd4e..000000000000 --- a/swarm/storage/ldbstore_test.go +++ /dev/null @@ -1,778 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package storage - -import ( - "bytes" - "context" - "encoding/binary" - "fmt" - "io/ioutil" - "os" - "strconv" - "strings" - "testing" - "time" - - "github.com/ubiq/go-ubiq/common" - ch "github.com/ubiq/go-ubiq/swarm/chunk" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/storage/mock/mem" - ldberrors "github.com/syndtr/goleveldb/leveldb/errors" -) - -type testDbStore struct { - *LDBStore - dir string -} - -func newTestDbStore(mock bool, trusted bool) (*testDbStore, func(), error) { - dir, err := ioutil.TempDir("", "bzz-storage-test") - if err != nil { - return nil, func() {}, err - } - - var db *LDBStore - storeparams := NewDefaultStoreParams() - params := NewLDBStoreParams(storeparams, dir) - params.Po = testPoFunc - - if mock { - globalStore := mem.NewGlobalStore() - addr := common.HexToAddress("0x5aaeb6053f3e94c9b9a09f33669435e7ef1beaed") - mockStore := globalStore.NewNodeStore(addr) - - db, err = NewMockDbStore(params, mockStore) - } else { - db, err = NewLDBStore(params) - } - - cleanup := func() { - if db != nil { - db.Close() - } - err = os.RemoveAll(dir) - if err != nil { - panic(fmt.Sprintf("db cleanup failed: %v", err)) - } - } - - return &testDbStore{db, dir}, cleanup, err -} - -func testPoFunc(k Address) (ret uint8) { - basekey := make([]byte, 32) - return uint8(Proximity(basekey, k[:])) -} - -func testDbStoreRandom(n int, mock bool, t *testing.T) { - db, cleanup, err := newTestDbStore(mock, true) - defer cleanup() - if err != nil { - t.Fatalf("init dbStore failed: %v", err) - } - testStoreRandom(db, n, t) -} - -func testDbStoreCorrect(n int, mock bool, t *testing.T) { - db, cleanup, err := newTestDbStore(mock, false) - defer cleanup() - if err != nil { - t.Fatalf("init dbStore failed: %v", err) - } - testStoreCorrect(db, n, t) -} - -func TestMarkAccessed(t *testing.T) { - db, cleanup, err := newTestDbStore(false, true) - defer cleanup() - if err != nil { - t.Fatalf("init dbStore failed: %v", err) - } - - h := GenerateRandomChunk(ch.DefaultSize) - - db.Put(context.Background(), h) - - var index dpaDBIndex - addr := h.Address() - idxk := getIndexKey(addr) - - idata, err := db.db.Get(idxk) - if err != nil { - t.Fatal(err) - } - decodeIndex(idata, &index) - - if index.Access != 0 { - t.Fatalf("Expected the access index to be %d, but it is %d", 0, index.Access) - } - - db.MarkAccessed(addr) - db.writeCurrentBatch() - - idata, err = db.db.Get(idxk) - if err != nil { - t.Fatal(err) - } - decodeIndex(idata, &index) - - if index.Access != 1 { - t.Fatalf("Expected the access index to be %d, but it is %d", 1, index.Access) - } - -} - -func TestDbStoreRandom_1(t *testing.T) { - testDbStoreRandom(1, false, t) -} - -func TestDbStoreCorrect_1(t *testing.T) { - testDbStoreCorrect(1, false, t) -} - -func TestDbStoreRandom_1k(t *testing.T) { - testDbStoreRandom(1000, false, t) -} - -func TestDbStoreCorrect_1k(t *testing.T) { - testDbStoreCorrect(1000, false, t) -} - -func TestMockDbStoreRandom_1(t *testing.T) { - testDbStoreRandom(1, true, t) -} - -func TestMockDbStoreCorrect_1(t *testing.T) { - testDbStoreCorrect(1, true, t) -} - -func TestMockDbStoreRandom_1k(t *testing.T) { - testDbStoreRandom(1000, true, t) -} - -func TestMockDbStoreCorrect_1k(t *testing.T) { - testDbStoreCorrect(1000, true, t) -} - -func testDbStoreNotFound(t *testing.T, mock bool) { - db, cleanup, err := newTestDbStore(mock, false) - defer cleanup() - if err != nil { - t.Fatalf("init dbStore failed: %v", err) - } - - _, err = db.Get(context.TODO(), ZeroAddr) - if err != ErrChunkNotFound { - t.Errorf("Expected ErrChunkNotFound, got %v", err) - } -} - -func TestDbStoreNotFound(t *testing.T) { - testDbStoreNotFound(t, false) -} -func TestMockDbStoreNotFound(t *testing.T) { - testDbStoreNotFound(t, true) -} - -func testIterator(t *testing.T, mock bool) { - var chunkcount int = 32 - var i int - var poc uint - chunkkeys := NewAddressCollection(chunkcount) - chunkkeys_results := NewAddressCollection(chunkcount) - - db, cleanup, err := newTestDbStore(mock, false) - defer cleanup() - if err != nil { - t.Fatalf("init dbStore failed: %v", err) - } - - chunks := GenerateRandomChunks(ch.DefaultSize, chunkcount) - - for i = 0; i < len(chunks); i++ { - chunkkeys[i] = chunks[i].Address() - err := db.Put(context.TODO(), chunks[i]) - if err != nil { - t.Fatalf("dbStore.Put failed: %v", err) - } - } - - for i = 0; i < len(chunkkeys); i++ { - log.Trace(fmt.Sprintf("Chunk array pos %d/%d: '%v'", i, chunkcount, chunkkeys[i])) - } - i = 0 - for poc = 0; poc <= 255; poc++ { - err := db.SyncIterator(0, uint64(chunkkeys.Len()), uint8(poc), func(k Address, n uint64) bool { - log.Trace(fmt.Sprintf("Got key %v number %d poc %d", k, n, uint8(poc))) - chunkkeys_results[n] = k - i++ - return true - }) - if err != nil { - t.Fatalf("Iterator call failed: %v", err) - } - } - - for i = 0; i < chunkcount; i++ { - if !bytes.Equal(chunkkeys[i], chunkkeys_results[i]) { - t.Fatalf("Chunk put #%d key '%v' does not match iterator's key '%v'", i, chunkkeys[i], chunkkeys_results[i]) - } - } - -} - -func TestIterator(t *testing.T) { - testIterator(t, false) -} -func TestMockIterator(t *testing.T) { - testIterator(t, true) -} - -func benchmarkDbStorePut(n int, mock bool, b *testing.B) { - db, cleanup, err := newTestDbStore(mock, true) - defer cleanup() - if err != nil { - b.Fatalf("init dbStore failed: %v", err) - } - benchmarkStorePut(db, n, b) -} - -func benchmarkDbStoreGet(n int, mock bool, b *testing.B) { - db, cleanup, err := newTestDbStore(mock, true) - defer cleanup() - if err != nil { - b.Fatalf("init dbStore failed: %v", err) - } - benchmarkStoreGet(db, n, b) -} - -func BenchmarkDbStorePut_500(b *testing.B) { - benchmarkDbStorePut(500, false, b) -} - -func BenchmarkDbStoreGet_500(b *testing.B) { - benchmarkDbStoreGet(500, false, b) -} - -func BenchmarkMockDbStorePut_500(b *testing.B) { - benchmarkDbStorePut(500, true, b) -} - -func BenchmarkMockDbStoreGet_500(b *testing.B) { - benchmarkDbStoreGet(500, true, b) -} - -// TestLDBStoreWithoutCollectGarbage tests that we can put a number of random chunks in the LevelDB store, and -// retrieve them, provided we don't hit the garbage collection -func TestLDBStoreWithoutCollectGarbage(t *testing.T) { - capacity := 50 - n := 10 - - ldb, cleanup := newLDBStore(t) - ldb.setCapacity(uint64(capacity)) - defer cleanup() - - chunks, err := mputRandomChunks(ldb, n) - if err != nil { - t.Fatal(err.Error()) - } - - log.Info("ldbstore", "entrycnt", ldb.entryCnt, "accesscnt", ldb.accessCnt) - - for _, ch := range chunks { - ret, err := ldb.Get(context.TODO(), ch.Address()) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(ret.Data(), ch.Data()) { - t.Fatal("expected to get the same data back, but got smth else") - } - } - - if ldb.entryCnt != uint64(n) { - t.Fatalf("expected entryCnt to be equal to %v, but got %v", n, ldb.entryCnt) - } - - if ldb.accessCnt != uint64(2*n) { - t.Fatalf("expected accessCnt to be equal to %v, but got %v", 2*n, ldb.accessCnt) - } -} - -// TestLDBStoreCollectGarbage tests that we can put more chunks than LevelDB's capacity, and -// retrieve only some of them, because garbage collection must have partially cleared the store -// Also tests that we can delete chunks and that we can trigger garbage collection -func TestLDBStoreCollectGarbage(t *testing.T) { - - // below max ronud - initialCap := defaultMaxGCRound / 100 - cap := initialCap / 2 - t.Run(fmt.Sprintf("A/%d/%d", cap, cap*4), testLDBStoreCollectGarbage) - t.Run(fmt.Sprintf("B/%d/%d", cap, cap*4), testLDBStoreRemoveThenCollectGarbage) - - // at max round - cap = initialCap - t.Run(fmt.Sprintf("A/%d/%d", cap, cap*4), testLDBStoreCollectGarbage) - t.Run(fmt.Sprintf("B/%d/%d", cap, cap*4), testLDBStoreRemoveThenCollectGarbage) - - // more than max around, not on threshold - cap = initialCap + 500 - t.Run(fmt.Sprintf("A/%d/%d", cap, cap*4), testLDBStoreCollectGarbage) - t.Run(fmt.Sprintf("B/%d/%d", cap, cap*4), testLDBStoreRemoveThenCollectGarbage) - -} - -func testLDBStoreCollectGarbage(t *testing.T) { - params := strings.Split(t.Name(), "/") - capacity, err := strconv.Atoi(params[2]) - if err != nil { - t.Fatal(err) - } - n, err := strconv.Atoi(params[3]) - if err != nil { - t.Fatal(err) - } - - ldb, cleanup := newLDBStore(t) - ldb.setCapacity(uint64(capacity)) - defer cleanup() - - // retrieve the gc round target count for the db capacity - ldb.startGC(capacity) - roundTarget := ldb.gc.target - - // split put counts to gc target count threshold, and wait for gc to finish in between - var allChunks []Chunk - remaining := n - for remaining > 0 { - var putCount int - if remaining < roundTarget { - putCount = remaining - } else { - putCount = roundTarget - } - remaining -= putCount - chunks, err := mputRandomChunks(ldb, putCount) - if err != nil { - t.Fatal(err.Error()) - } - allChunks = append(allChunks, chunks...) - log.Debug("ldbstore", "entrycnt", ldb.entryCnt, "accesscnt", ldb.accessCnt, "cap", capacity, "n", n) - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - defer cancel() - waitGc(ctx, ldb) - } - - // attempt gets on all put chunks - var missing int - for _, ch := range allChunks { - ret, err := ldb.Get(context.TODO(), ch.Address()) - if err == ErrChunkNotFound || err == ldberrors.ErrNotFound { - missing++ - continue - } - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(ret.Data(), ch.Data()) { - t.Fatal("expected to get the same data back, but got smth else") - } - - log.Trace("got back chunk", "chunk", ret) - } - - // all surplus chunks should be missing - expectMissing := roundTarget + (((n - capacity) / roundTarget) * roundTarget) - if missing != expectMissing { - t.Fatalf("gc failure: expected to miss %v chunks, but only %v are actually missing", expectMissing, missing) - } - - log.Info("ldbstore", "total", n, "missing", missing, "entrycnt", ldb.entryCnt, "accesscnt", ldb.accessCnt) -} - -// TestLDBStoreAddRemove tests that we can put and then delete a given chunk -func TestLDBStoreAddRemove(t *testing.T) { - ldb, cleanup := newLDBStore(t) - ldb.setCapacity(200) - defer cleanup() - - n := 100 - chunks, err := mputRandomChunks(ldb, n) - if err != nil { - t.Fatalf(err.Error()) - } - - for i := 0; i < n; i++ { - // delete all even index chunks - if i%2 == 0 { - ldb.Delete(chunks[i].Address()) - } - } - - log.Info("ldbstore", "entrycnt", ldb.entryCnt, "accesscnt", ldb.accessCnt) - - for i := 0; i < n; i++ { - ret, err := ldb.Get(context.TODO(), chunks[i].Address()) - - if i%2 == 0 { - // expect even chunks to be missing - if err == nil { - t.Fatal("expected chunk to be missing, but got no error") - } - } else { - // expect odd chunks to be retrieved successfully - if err != nil { - t.Fatalf("expected no error, but got %s", err) - } - - if !bytes.Equal(ret.Data(), chunks[i].Data()) { - t.Fatal("expected to get the same data back, but got smth else") - } - } - } -} - -func testLDBStoreRemoveThenCollectGarbage(t *testing.T) { - - params := strings.Split(t.Name(), "/") - capacity, err := strconv.Atoi(params[2]) - if err != nil { - t.Fatal(err) - } - n, err := strconv.Atoi(params[3]) - if err != nil { - t.Fatal(err) - } - - ldb, cleanup := newLDBStore(t) - defer cleanup() - ldb.setCapacity(uint64(capacity)) - - // put capacity count number of chunks - chunks := make([]Chunk, n) - for i := 0; i < n; i++ { - c := GenerateRandomChunk(ch.DefaultSize) - chunks[i] = c - log.Trace("generate random chunk", "idx", i, "chunk", c) - } - - for i := 0; i < n; i++ { - err := ldb.Put(context.TODO(), chunks[i]) - if err != nil { - t.Fatal(err) - } - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - defer cancel() - waitGc(ctx, ldb) - - // delete all chunks - // (only count the ones actually deleted, the rest will have been gc'd) - deletes := 0 - for i := 0; i < n; i++ { - if ldb.Delete(chunks[i].Address()) == nil { - deletes++ - } - } - - log.Info("ldbstore", "entrycnt", ldb.entryCnt, "accesscnt", ldb.accessCnt) - - if ldb.entryCnt != 0 { - t.Fatalf("ldb.entrCnt expected 0 got %v", ldb.entryCnt) - } - - // the manual deletes will have increased accesscnt, so we need to add this when we verify the current count - expAccessCnt := uint64(n) - if ldb.accessCnt != expAccessCnt { - t.Fatalf("ldb.accessCnt expected %v got %v", expAccessCnt, ldb.accessCnt) - } - - // retrieve the gc round target count for the db capacity - ldb.startGC(capacity) - roundTarget := ldb.gc.target - - remaining := n - var puts int - for remaining > 0 { - var putCount int - if remaining < roundTarget { - putCount = remaining - } else { - putCount = roundTarget - } - remaining -= putCount - for putCount > 0 { - ldb.Put(context.TODO(), chunks[puts]) - log.Debug("ldbstore", "entrycnt", ldb.entryCnt, "accesscnt", ldb.accessCnt, "cap", capacity, "n", n, "puts", puts, "remaining", remaining, "roundtarget", roundTarget) - puts++ - putCount-- - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - defer cancel() - waitGc(ctx, ldb) - } - - // expect first surplus chunks to be missing, because they have the smallest access value - expectMissing := roundTarget + (((n - capacity) / roundTarget) * roundTarget) - for i := 0; i < expectMissing; i++ { - _, err := ldb.Get(context.TODO(), chunks[i].Address()) - if err == nil { - t.Fatalf("expected surplus chunk %d to be missing, but got no error", i) - } - } - - // expect last chunks to be present, as they have the largest access value - for i := expectMissing; i < n; i++ { - ret, err := ldb.Get(context.TODO(), chunks[i].Address()) - if err != nil { - t.Fatalf("chunk %v: expected no error, but got %s", i, err) - } - if !bytes.Equal(ret.Data(), chunks[i].Data()) { - t.Fatal("expected to get the same data back, but got smth else") - } - } -} - -// TestLDBStoreCollectGarbageAccessUnlikeIndex tests garbage collection where accesscount differs from indexcount -func TestLDBStoreCollectGarbageAccessUnlikeIndex(t *testing.T) { - - capacity := defaultMaxGCRound / 100 * 2 - n := capacity - 1 - - ldb, cleanup := newLDBStore(t) - ldb.setCapacity(uint64(capacity)) - defer cleanup() - - chunks, err := mputRandomChunks(ldb, n) - if err != nil { - t.Fatal(err.Error()) - } - log.Info("ldbstore", "entrycnt", ldb.entryCnt, "accesscnt", ldb.accessCnt) - - // set first added capacity/2 chunks to highest accesscount - for i := 0; i < capacity/2; i++ { - _, err := ldb.Get(context.TODO(), chunks[i].Address()) - if err != nil { - t.Fatalf("fail add chunk #%d - %s: %v", i, chunks[i].Address(), err) - } - } - _, err = mputRandomChunks(ldb, 2) - if err != nil { - t.Fatal(err.Error()) - } - - // wait for garbage collection to kick in on the responsible actor - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - defer cancel() - waitGc(ctx, ldb) - - var missing int - for i, ch := range chunks[2 : capacity/2] { - ret, err := ldb.Get(context.TODO(), ch.Address()) - if err == ErrChunkNotFound || err == ldberrors.ErrNotFound { - t.Fatalf("fail find chunk #%d - %s: %v", i, ch.Address(), err) - } - - if !bytes.Equal(ret.Data(), ch.Data()) { - t.Fatal("expected to get the same data back, but got smth else") - } - log.Trace("got back chunk", "chunk", ret) - } - - log.Info("ldbstore", "total", n, "missing", missing, "entrycnt", ldb.entryCnt, "accesscnt", ldb.accessCnt) -} - -func TestCleanIndex(t *testing.T) { - capacity := 5000 - n := 3 - - ldb, cleanup := newLDBStore(t) - ldb.setCapacity(uint64(capacity)) - defer cleanup() - - chunks, err := mputRandomChunks(ldb, n) - if err != nil { - t.Fatal(err) - } - - // remove the data of the first chunk - po := ldb.po(chunks[0].Address()[:]) - dataKey := make([]byte, 10) - dataKey[0] = keyData - dataKey[1] = byte(po) - // dataKey[2:10] = first chunk has storageIdx 0 on [2:10] - if _, err := ldb.db.Get(dataKey); err != nil { - t.Fatal(err) - } - if err := ldb.db.Delete(dataKey); err != nil { - t.Fatal(err) - } - - // remove the gc index row for the first chunk - gcFirstCorrectKey := make([]byte, 9) - gcFirstCorrectKey[0] = keyGCIdx - if err := ldb.db.Delete(gcFirstCorrectKey); err != nil { - t.Fatal(err) - } - - // warp the gc data of the second chunk - // this data should be correct again after the clean - gcSecondCorrectKey := make([]byte, 9) - gcSecondCorrectKey[0] = keyGCIdx - binary.BigEndian.PutUint64(gcSecondCorrectKey[1:], uint64(1)) - gcSecondCorrectVal, err := ldb.db.Get(gcSecondCorrectKey) - if err != nil { - t.Fatal(err) - } - warpedGCVal := make([]byte, len(gcSecondCorrectVal)+1) - copy(warpedGCVal[1:], gcSecondCorrectVal) - if err := ldb.db.Delete(gcSecondCorrectKey); err != nil { - t.Fatal(err) - } - if err := ldb.db.Put(gcSecondCorrectKey, warpedGCVal); err != nil { - t.Fatal(err) - } - - if err := ldb.CleanGCIndex(); err != nil { - t.Fatal(err) - } - - // the index without corresponding data should have been deleted - idxKey := make([]byte, 33) - idxKey[0] = keyIndex - copy(idxKey[1:], chunks[0].Address()) - if _, err := ldb.db.Get(idxKey); err == nil { - t.Fatalf("expected chunk 0 idx to be pruned: %v", idxKey) - } - - // the two other indices should be present - copy(idxKey[1:], chunks[1].Address()) - if _, err := ldb.db.Get(idxKey); err != nil { - t.Fatalf("expected chunk 1 idx to be present: %v", idxKey) - } - - copy(idxKey[1:], chunks[2].Address()) - if _, err := ldb.db.Get(idxKey); err != nil { - t.Fatalf("expected chunk 2 idx to be present: %v", idxKey) - } - - // first gc index should still be gone - if _, err := ldb.db.Get(gcFirstCorrectKey); err == nil { - t.Fatalf("expected gc 0 idx to be pruned: %v", idxKey) - } - - // second gc index should still be fixed - if _, err := ldb.db.Get(gcSecondCorrectKey); err != nil { - t.Fatalf("expected gc 1 idx to be present: %v", idxKey) - } - - // third gc index should be unchanged - binary.BigEndian.PutUint64(gcSecondCorrectKey[1:], uint64(2)) - if _, err := ldb.db.Get(gcSecondCorrectKey); err != nil { - t.Fatalf("expected gc 2 idx to be present: %v", idxKey) - } - - c, err := ldb.db.Get(keyEntryCnt) - if err != nil { - t.Fatalf("expected gc 2 idx to be present: %v", idxKey) - } - - // entrycount should now be one less - entryCount := binary.BigEndian.Uint64(c) - if entryCount != 2 { - t.Fatalf("expected entrycnt to be 2, was %d", c) - } - - // the chunks might accidentally be in the same bin - // if so that bin counter will now be 2 - the highest added index. - // if not, the total of them will be 3 - poBins := []uint8{ldb.po(chunks[1].Address()), ldb.po(chunks[2].Address())} - if poBins[0] == poBins[1] { - poBins = poBins[:1] - } - - var binTotal uint64 - var currentBin [2]byte - currentBin[0] = keyDistanceCnt - if len(poBins) == 1 { - currentBin[1] = poBins[0] - c, err := ldb.db.Get(currentBin[:]) - if err != nil { - t.Fatalf("expected gc 2 idx to be present: %v", idxKey) - } - binCount := binary.BigEndian.Uint64(c) - if binCount != 2 { - t.Fatalf("expected entrycnt to be 2, was %d", binCount) - } - } else { - for _, bin := range poBins { - currentBin[1] = bin - c, err := ldb.db.Get(currentBin[:]) - if err != nil { - t.Fatalf("expected gc 2 idx to be present: %v", idxKey) - } - binCount := binary.BigEndian.Uint64(c) - binTotal += binCount - - } - if binTotal != 3 { - t.Fatalf("expected sum of bin indices to be 3, was %d", binTotal) - } - } - - // check that the iterator quits properly - chunks, err = mputRandomChunks(ldb, 4100) - if err != nil { - t.Fatal(err) - } - - po = ldb.po(chunks[4099].Address()[:]) - dataKey = make([]byte, 10) - dataKey[0] = keyData - dataKey[1] = byte(po) - binary.BigEndian.PutUint64(dataKey[2:], 4099+3) - if _, err := ldb.db.Get(dataKey); err != nil { - t.Fatal(err) - } - if err := ldb.db.Delete(dataKey); err != nil { - t.Fatal(err) - } - - if err := ldb.CleanGCIndex(); err != nil { - t.Fatal(err) - } - - // entrycount should now be one less of added chunks - c, err = ldb.db.Get(keyEntryCnt) - if err != nil { - t.Fatalf("expected gc 2 idx to be present: %v", idxKey) - } - entryCount = binary.BigEndian.Uint64(c) - if entryCount != 4099+2 { - t.Fatalf("expected entrycnt to be 2, was %d", c) - } -} - -func waitGc(ctx context.Context, ldb *LDBStore) { - <-ldb.gc.runC - ldb.gc.runC <- struct{}{} -} diff --git a/swarm/storage/localstore.go b/swarm/storage/localstore.go deleted file mode 100644 index 7324c08fb6e0..000000000000 --- a/swarm/storage/localstore.go +++ /dev/null @@ -1,251 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package storage - -import ( - "context" - "path/filepath" - "sync" - - "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/storage/mock" -) - -type LocalStoreParams struct { - *StoreParams - ChunkDbPath string - Validators []ChunkValidator `toml:"-"` -} - -func NewDefaultLocalStoreParams() *LocalStoreParams { - return &LocalStoreParams{ - StoreParams: NewDefaultStoreParams(), - } -} - -//this can only finally be set after all config options (file, cmd line, env vars) -//have been evaluated -func (p *LocalStoreParams) Init(path string) { - if p.ChunkDbPath == "" { - p.ChunkDbPath = filepath.Join(path, "chunks") - } -} - -// LocalStore is a combination of inmemory db over a disk persisted db -// implements a Get/Put with fallback (caching) logic using any 2 ChunkStores -type LocalStore struct { - Validators []ChunkValidator - memStore *MemStore - DbStore *LDBStore - mu sync.Mutex -} - -// This constructor uses MemStore and DbStore as components -func NewLocalStore(params *LocalStoreParams, mockStore *mock.NodeStore) (*LocalStore, error) { - ldbparams := NewLDBStoreParams(params.StoreParams, params.ChunkDbPath) - dbStore, err := NewMockDbStore(ldbparams, mockStore) - if err != nil { - return nil, err - } - return &LocalStore{ - memStore: NewMemStore(params.StoreParams, dbStore), - DbStore: dbStore, - Validators: params.Validators, - }, nil -} - -func NewTestLocalStoreForAddr(params *LocalStoreParams) (*LocalStore, error) { - ldbparams := NewLDBStoreParams(params.StoreParams, params.ChunkDbPath) - dbStore, err := NewLDBStore(ldbparams) - if err != nil { - return nil, err - } - localStore := &LocalStore{ - memStore: NewMemStore(params.StoreParams, dbStore), - DbStore: dbStore, - Validators: params.Validators, - } - return localStore, nil -} - -// isValid returns true if chunk passes any of the LocalStore Validators. -// isValid also returns true if LocalStore has no Validators. -func (ls *LocalStore) isValid(chunk Chunk) bool { - // by default chunks are valid. if we have 0 validators, then all chunks are valid. - valid := true - - // ls.Validators contains a list of one validator per chunk type. - // if one validator succeeds, then the chunk is valid - for _, v := range ls.Validators { - if valid = v.Validate(chunk); valid { - break - } - } - return valid -} - -// Put is responsible for doing validation and storage of the chunk -// by using configured ChunkValidators, MemStore and LDBStore. -// If the chunk is not valid, its GetErrored function will -// return ErrChunkInvalid. -// This method will check if the chunk is already in the MemStore -// and it will return it if it is. If there is an error from -// the MemStore.Get, it will be returned by calling GetErrored -// on the chunk. -// This method is responsible for closing Chunk.ReqC channel -// when the chunk is stored in memstore. -// After the LDBStore.Put, it is ensured that the MemStore -// contains the chunk with the same data, but nil ReqC channel. -func (ls *LocalStore) Put(ctx context.Context, chunk Chunk) error { - if !ls.isValid(chunk) { - return ErrChunkInvalid - } - - log.Trace("localstore.put", "key", chunk.Address()) - ls.mu.Lock() - defer ls.mu.Unlock() - - _, err := ls.memStore.Get(ctx, chunk.Address()) - if err == nil { - return nil - } - if err != nil && err != ErrChunkNotFound { - return err - } - ls.memStore.Put(ctx, chunk) - err = ls.DbStore.Put(ctx, chunk) - return err -} - -// Has queries the underlying DbStore if a chunk with the given address -// is being stored there. -// Returns true if it is stored, false if not -func (ls *LocalStore) Has(ctx context.Context, addr Address) bool { - return ls.DbStore.Has(ctx, addr) -} - -// Get(chunk *Chunk) looks up a chunk in the local stores -// This method is blocking until the chunk is retrieved -// so additional timeout may be needed to wrap this call if -// ChunkStores are remote and can have long latency -func (ls *LocalStore) Get(ctx context.Context, addr Address) (chunk Chunk, err error) { - ls.mu.Lock() - defer ls.mu.Unlock() - - return ls.get(ctx, addr) -} - -func (ls *LocalStore) get(ctx context.Context, addr Address) (chunk Chunk, err error) { - chunk, err = ls.memStore.Get(ctx, addr) - - if err != nil && err != ErrChunkNotFound { - metrics.GetOrRegisterCounter("localstore.get.error", nil).Inc(1) - return nil, err - } - - if err == nil { - metrics.GetOrRegisterCounter("localstore.get.cachehit", nil).Inc(1) - go ls.DbStore.MarkAccessed(addr) - return chunk, nil - } - - metrics.GetOrRegisterCounter("localstore.get.cachemiss", nil).Inc(1) - chunk, err = ls.DbStore.Get(ctx, addr) - if err != nil { - metrics.GetOrRegisterCounter("localstore.get.error", nil).Inc(1) - return nil, err - } - - ls.memStore.Put(ctx, chunk) - return chunk, nil -} - -func (ls *LocalStore) FetchFunc(ctx context.Context, addr Address) func(context.Context) error { - ls.mu.Lock() - defer ls.mu.Unlock() - - _, err := ls.get(ctx, addr) - if err == nil { - return nil - } - return func(context.Context) error { - return err - } -} - -func (ls *LocalStore) BinIndex(po uint8) uint64 { - return ls.DbStore.BinIndex(po) -} - -func (ls *LocalStore) Iterator(from uint64, to uint64, po uint8, f func(Address, uint64) bool) error { - return ls.DbStore.SyncIterator(from, to, po, f) -} - -// Close the local store -func (ls *LocalStore) Close() { - ls.DbStore.Close() -} - -// Migrate checks the datastore schema vs the runtime schema and runs -// migrations if they don't match -func (ls *LocalStore) Migrate() error { - actualDbSchema, err := ls.DbStore.GetSchema() - if err != nil { - log.Error(err.Error()) - return err - } - - if actualDbSchema == CurrentDbSchema { - return nil - } - - log.Debug("running migrations for", "schema", actualDbSchema, "runtime-schema", CurrentDbSchema) - - if actualDbSchema == DbSchemaNone { - ls.migrateFromNoneToPurity() - actualDbSchema = DbSchemaPurity - } - - if err := ls.DbStore.PutSchema(actualDbSchema); err != nil { - return err - } - - if actualDbSchema == DbSchemaPurity { - if err := ls.migrateFromPurityToHalloween(); err != nil { - return err - } - actualDbSchema = DbSchemaHalloween - } - - if err := ls.DbStore.PutSchema(actualDbSchema); err != nil { - return err - } - return nil -} - -func (ls *LocalStore) migrateFromNoneToPurity() { - // delete chunks that are not valid, i.e. chunks that do not pass - // any of the ls.Validators - ls.DbStore.Cleanup(func(c *chunk) bool { - return !ls.isValid(c) - }) -} - -func (ls *LocalStore) migrateFromPurityToHalloween() error { - return ls.DbStore.CleanGCIndex() -} diff --git a/swarm/storage/localstore/doc.go b/swarm/storage/localstore/doc.go deleted file mode 100644 index 98f6fc40aa91..000000000000 --- a/swarm/storage/localstore/doc.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -/* -Package localstore provides disk storage layer for Swarm Chunk persistence. -It uses swarm/shed abstractions on top of github.com/syndtr/goleveldb LevelDB -implementation. - -The main type is DB which manages the storage by providing methods to -access and add Chunks and to manage their status. - -Modes are abstractions that do specific changes to Chunks. There are three -mode types: - - - ModeGet, for Chunk access - - ModePut, for adding Chunks to the database - - ModeSet, for changing Chunk statuses - -Every mode type has a corresponding type (Getter, Putter and Setter) -that provides adequate method to perform the opperation and that type -should be injected into localstore consumers instead the whole DB. -This provides more clear insight which operations consumer is performing -on the database. - -Getters, Putters and Setters accept different get, put and set modes -to perform different actions. For example, ModeGet has two different -variables ModeGetRequest and ModeGetSync and two different Getters -can be constructed with them that are used when the chunk is requested -or when the chunk is synced as this two events are differently changing -the database. - -Subscription methods are implemented for a specific purpose of -continuous iterations over Chunks that should be provided to -Push and Pull syncing. - -DB implements an internal garbage collector that removes only synced -Chunks from the database based on their most recent access time. - -Internally, DB stores Chunk data and any required information, such as -store and access timestamps in different shed indexes that can be -iterated on by garbage collector or subscriptions. -*/ -package localstore diff --git a/swarm/storage/localstore/gc.go b/swarm/storage/localstore/gc.go deleted file mode 100644 index a8ac895e8803..000000000000 --- a/swarm/storage/localstore/gc.go +++ /dev/null @@ -1,302 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -/* -Counting number of items in garbage collection index - -The number of items in garbage collection index is not the same as the number of -chunks in retrieval index (total number of stored chunks). Chunk can be garbage -collected only when it is set to a synced state by ModSetSync, and only then can -be counted into garbage collection size, which determines whether a number of -chunk should be removed from the storage by the garbage collection. This opens a -possibility that the storage size exceeds the limit if files are locally -uploaded and the node is not connected to other nodes or there is a problem with -syncing. - -Tracking of garbage collection size (gcSize) is focused on performance. Key -points: - - 1. counting the number of key/value pairs in LevelDB takes around 0.7s for 1e6 - on a very fast ssd (unacceptable long time in reality) - 2. locking leveldb batch writes with a global mutex (serial batch writes) is - not acceptable, we should use locking per chunk address - -Because of point 1. we cannot count the number of items in garbage collection -index in New constructor as it could last very long for realistic scenarios -where limit is 5e6 and nodes are running on slower hdd disks or cloud providers -with low IOPS. - -Point 2. is a performance optimization to allow parallel batch writes with -getters, putters and setters. Every single batch that they create contain only -information related to a single chunk, no relations with other chunks or shared -statistical data (like gcSize). This approach avoids race conditions on writing -batches in parallel, but creates a problem of synchronizing statistical data -values like gcSize. With global mutex lock, any data could be written by any -batch, but would not use utilize the full potential of leveldb parallel writes. - -To mitigate this two problems, the implementation of counting and persisting -gcSize is split into two parts. One is the in-memory value (gcSize) that is fast -to read and write with a dedicated mutex (gcSizeMu) if the batch which adds or -removes items from garbage collection index is successful. The second part is -the reliable persistence of this value to leveldb database, as storedGCSize -field. This database field is saved by writeGCSizeWorker and writeGCSize -functions when in-memory gcSize variable is changed, but no too often to avoid -very frequent database writes. This database writes are triggered by -writeGCSizeTrigger when a call is made to function incGCSize. Trigger ensures -that no database writes are done only when gcSize is changed (contrary to a -simpler periodic writes or checks). A backoff of 10s in writeGCSizeWorker -ensures that no frequent batch writes are made. Saving the storedGCSize on -database Close function ensures that in-memory gcSize is persisted when database -is closed. - -This persistence must be resilient to failures like panics. For this purpose, a -collection of hashes that are added to the garbage collection index, but still -not persisted to storedGCSize, must be tracked to count them in when DB is -constructed again with New function after the failure (swarm node restarts). On -every batch write that adds a new item to garbage collection index, the same -hash is added to gcUncountedHashesIndex. This ensures that there is a persisted -information which hashes were added to the garbage collection index. But, when -the storedGCSize is saved by writeGCSize function, this values are removed in -the same batch in which storedGCSize is changed to ensure consistency. When the -panic happen, or database Close method is not saved. The database storage -contains all information to reliably and efficiently get the correct number of -items in garbage collection index. This is performed in the New function when -all hashes in gcUncountedHashesIndex are counted, added to the storedGCSize and -saved to the disk before the database is constructed again. Index -gcUncountedHashesIndex is acting as dirty bit for recovery that provides -information what needs to be corrected. With a simple dirty bit, the whole -garbage collection index should me counted on recovery instead only the items in -gcUncountedHashesIndex. Because of the triggering mechanizm of writeGCSizeWorker -and relatively short backoff time, the number of hashes in -gcUncountedHashesIndex should be low and it should take a very short time to -recover from the previous failure. If there was no failure and -gcUncountedHashesIndex is empty, which is the usual case, New function will take -the minimal time to return. -*/ - -package localstore - -import ( - "time" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/swarm/shed" - "github.com/syndtr/goleveldb/leveldb" -) - -var ( - // gcTargetRatio defines the target number of items - // in garbage collection index that will not be removed - // on garbage collection. The target number of items - // is calculated by gcTarget function. This value must be - // in range (0,1]. For example, with 0.9 value, - // garbage collection will leave 90% of defined capacity - // in database after its run. This prevents frequent - // garbage collection runs. - gcTargetRatio = 0.9 - // gcBatchSize limits the number of chunks in a single - // leveldb batch on garbage collection. - gcBatchSize int64 = 1000 -) - -// collectGarbageWorker is a long running function that waits for -// collectGarbageTrigger channel to signal a garbage collection -// run. GC run iterates on gcIndex and removes older items -// form retrieval and other indexes. -func (db *DB) collectGarbageWorker() { - for { - select { - case <-db.collectGarbageTrigger: - // run a single collect garbage run and - // if done is false, gcBatchSize is reached and - // another collect garbage run is needed - collectedCount, done, err := db.collectGarbage() - if err != nil { - log.Error("localstore collect garbage", "err", err) - } - // check if another gc run is needed - if !done { - db.triggerGarbageCollection() - } - - if testHookCollectGarbage != nil { - testHookCollectGarbage(collectedCount) - } - case <-db.close: - return - } - } -} - -// collectGarbage removes chunks from retrieval and other -// indexes if maximal number of chunks in database is reached. -// This function returns the number of removed chunks. If done -// is false, another call to this function is needed to collect -// the rest of the garbage as the batch size limit is reached. -// This function is called in collectGarbageWorker. -func (db *DB) collectGarbage() (collectedCount int64, done bool, err error) { - batch := new(leveldb.Batch) - target := db.gcTarget() - - done = true - err = db.gcIndex.Iterate(func(item shed.Item) (stop bool, err error) { - // protect parallel updates - unlock, err := db.lockAddr(item.Address) - if err != nil { - return false, err - } - defer unlock() - - gcSize := db.getGCSize() - if gcSize-collectedCount <= target { - return true, nil - } - // delete from retrieve, pull, gc - db.retrievalDataIndex.DeleteInBatch(batch, item) - db.retrievalAccessIndex.DeleteInBatch(batch, item) - db.pullIndex.DeleteInBatch(batch, item) - db.gcIndex.DeleteInBatch(batch, item) - collectedCount++ - if collectedCount >= gcBatchSize { - // bach size limit reached, - // another gc run is needed - done = false - return true, nil - } - return false, nil - }, nil) - if err != nil { - return 0, false, err - } - - err = db.shed.WriteBatch(batch) - if err != nil { - return 0, false, err - } - // batch is written, decrement gcSize - db.incGCSize(-collectedCount) - return collectedCount, done, nil -} - -// gcTrigger retruns the absolute value for garbage collection -// target value, calculated from db.capacity and gcTargetRatio. -func (db *DB) gcTarget() (target int64) { - return int64(float64(db.capacity) * gcTargetRatio) -} - -// incGCSize increments gcSize by the provided number. -// If count is negative, it will decrement gcSize. -func (db *DB) incGCSize(count int64) { - if count == 0 { - return - } - - db.gcSizeMu.Lock() - new := db.gcSize + count - db.gcSize = new - db.gcSizeMu.Unlock() - - select { - case db.writeGCSizeTrigger <- struct{}{}: - default: - } - if new >= db.capacity { - db.triggerGarbageCollection() - } -} - -// getGCSize returns gcSize value by locking it -// with gcSizeMu mutex. -func (db *DB) getGCSize() (count int64) { - db.gcSizeMu.RLock() - count = db.gcSize - db.gcSizeMu.RUnlock() - return count -} - -// triggerGarbageCollection signals collectGarbageWorker -// to call collectGarbage. -func (db *DB) triggerGarbageCollection() { - select { - case db.collectGarbageTrigger <- struct{}{}: - case <-db.close: - default: - } -} - -// writeGCSizeWorker writes gcSize on trigger event -// and waits writeGCSizeDelay after each write. -// It implements a linear backoff with delay of -// writeGCSizeDelay duration to avoid very frequent -// database operations. -func (db *DB) writeGCSizeWorker() { - for { - select { - case <-db.writeGCSizeTrigger: - err := db.writeGCSize(db.getGCSize()) - if err != nil { - log.Error("localstore write gc size", "err", err) - } - // Wait some time before writing gc size in the next - // iteration. This prevents frequent I/O operations. - select { - case <-time.After(10 * time.Second): - case <-db.close: - return - } - case <-db.close: - return - } - } -} - -// writeGCSize stores the number of items in gcIndex. -// It removes all hashes from gcUncountedHashesIndex -// not to include them on the next DB initialization -// (New function) when gcSize is counted. -func (db *DB) writeGCSize(gcSize int64) (err error) { - const maxBatchSize = 1000 - - batch := new(leveldb.Batch) - db.storedGCSize.PutInBatch(batch, uint64(gcSize)) - batchSize := 1 - - // use only one iterator as it acquires its snapshot - // not to remove hashes from index that are added - // after stored gc size is written - err = db.gcUncountedHashesIndex.Iterate(func(item shed.Item) (stop bool, err error) { - db.gcUncountedHashesIndex.DeleteInBatch(batch, item) - batchSize++ - if batchSize >= maxBatchSize { - err = db.shed.WriteBatch(batch) - if err != nil { - return false, err - } - batch.Reset() - batchSize = 0 - } - return false, nil - }, nil) - if err != nil { - return err - } - return db.shed.WriteBatch(batch) -} - -// testHookCollectGarbage is a hook that can provide -// information when a garbage collection run is done -// and how many items it removed. -var testHookCollectGarbage func(collectedCount int64) diff --git a/swarm/storage/localstore/gc_test.go b/swarm/storage/localstore/gc_test.go deleted file mode 100644 index bfb68ddebf6b..000000000000 --- a/swarm/storage/localstore/gc_test.go +++ /dev/null @@ -1,358 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package localstore - -import ( - "io/ioutil" - "math/rand" - "os" - "testing" - "time" - - "github.com/ubiq/go-ubiq/swarm/storage" -) - -// TestDB_collectGarbageWorker tests garbage collection runs -// by uploading and syncing a number of chunks. -func TestDB_collectGarbageWorker(t *testing.T) { - testDB_collectGarbageWorker(t) -} - -// TestDB_collectGarbageWorker_multipleBatches tests garbage -// collection runs by uploading and syncing a number of -// chunks by having multiple smaller batches. -func TestDB_collectGarbageWorker_multipleBatches(t *testing.T) { - // lower the maximal number of chunks in a single - // gc batch to ensure multiple batches. - defer func(s int64) { gcBatchSize = s }(gcBatchSize) - gcBatchSize = 2 - - testDB_collectGarbageWorker(t) -} - -// testDB_collectGarbageWorker is a helper test function to test -// garbage collection runs by uploading and syncing a number of chunks. -func testDB_collectGarbageWorker(t *testing.T) { - chunkCount := 150 - - testHookCollectGarbageChan := make(chan int64) - defer setTestHookCollectGarbage(func(collectedCount int64) { - testHookCollectGarbageChan <- collectedCount - })() - - db, cleanupFunc := newTestDB(t, &Options{ - Capacity: 100, - }) - defer cleanupFunc() - - uploader := db.NewPutter(ModePutUpload) - syncer := db.NewSetter(ModeSetSync) - - addrs := make([]storage.Address, 0) - - // upload random chunks - for i := 0; i < chunkCount; i++ { - chunk := generateRandomChunk() - - err := uploader.Put(chunk) - if err != nil { - t.Fatal(err) - } - - err = syncer.Set(chunk.Address()) - if err != nil { - t.Fatal(err) - } - - addrs = append(addrs, chunk.Address()) - } - - gcTarget := db.gcTarget() - - for { - select { - case <-testHookCollectGarbageChan: - case <-time.After(10 * time.Second): - t.Error("collect garbage timeout") - } - gcSize := db.getGCSize() - if gcSize == gcTarget { - break - } - } - - t.Run("pull index count", newItemsCountTest(db.pullIndex, int(gcTarget))) - - t.Run("gc index count", newItemsCountTest(db.gcIndex, int(gcTarget))) - - t.Run("gc size", newIndexGCSizeTest(db)) - - // the first synced chunk should be removed - t.Run("get the first synced chunk", func(t *testing.T) { - _, err := db.NewGetter(ModeGetRequest).Get(addrs[0]) - if err != storage.ErrChunkNotFound { - t.Errorf("got error %v, want %v", err, storage.ErrChunkNotFound) - } - }) - - // last synced chunk should not be removed - t.Run("get most recent synced chunk", func(t *testing.T) { - _, err := db.NewGetter(ModeGetRequest).Get(addrs[len(addrs)-1]) - if err != nil { - t.Fatal(err) - } - }) - - // cleanup: drain the last testHookCollectGarbageChan - // element before calling deferred functions not to block - // collectGarbageWorker loop, preventing the race in - // setting testHookCollectGarbage function - select { - case <-testHookCollectGarbageChan: - default: - } -} - -// TestDB_collectGarbageWorker_withRequests is a helper test function -// to test garbage collection runs by uploading, syncing and -// requesting a number of chunks. -func TestDB_collectGarbageWorker_withRequests(t *testing.T) { - db, cleanupFunc := newTestDB(t, &Options{ - Capacity: 100, - }) - defer cleanupFunc() - - uploader := db.NewPutter(ModePutUpload) - syncer := db.NewSetter(ModeSetSync) - - testHookCollectGarbageChan := make(chan int64) - defer setTestHookCollectGarbage(func(collectedCount int64) { - testHookCollectGarbageChan <- collectedCount - })() - - addrs := make([]storage.Address, 0) - - // upload random chunks just up to the capacity - for i := 0; i < int(db.capacity)-1; i++ { - chunk := generateRandomChunk() - - err := uploader.Put(chunk) - if err != nil { - t.Fatal(err) - } - - err = syncer.Set(chunk.Address()) - if err != nil { - t.Fatal(err) - } - - addrs = append(addrs, chunk.Address()) - } - - // request the latest synced chunk - // to prioritize it in the gc index - // not to be collected - _, err := db.NewGetter(ModeGetRequest).Get(addrs[0]) - if err != nil { - t.Fatal(err) - } - - // upload and sync another chunk to trigger - // garbage collection - chunk := generateRandomChunk() - err = uploader.Put(chunk) - if err != nil { - t.Fatal(err) - } - err = syncer.Set(chunk.Address()) - if err != nil { - t.Fatal(err) - } - addrs = append(addrs, chunk.Address()) - - // wait for garbage collection - - gcTarget := db.gcTarget() - - var totalCollectedCount int64 - for { - select { - case c := <-testHookCollectGarbageChan: - totalCollectedCount += c - case <-time.After(10 * time.Second): - t.Error("collect garbage timeout") - } - gcSize := db.getGCSize() - if gcSize == gcTarget { - break - } - } - - wantTotalCollectedCount := int64(len(addrs)) - gcTarget - if totalCollectedCount != wantTotalCollectedCount { - t.Errorf("total collected chunks %v, want %v", totalCollectedCount, wantTotalCollectedCount) - } - - t.Run("pull index count", newItemsCountTest(db.pullIndex, int(gcTarget))) - - t.Run("gc index count", newItemsCountTest(db.gcIndex, int(gcTarget))) - - t.Run("gc size", newIndexGCSizeTest(db)) - - // requested chunk should not be removed - t.Run("get requested chunk", func(t *testing.T) { - _, err := db.NewGetter(ModeGetRequest).Get(addrs[0]) - if err != nil { - t.Fatal(err) - } - }) - - // the second synced chunk should be removed - t.Run("get gc-ed chunk", func(t *testing.T) { - _, err := db.NewGetter(ModeGetRequest).Get(addrs[1]) - if err != storage.ErrChunkNotFound { - t.Errorf("got error %v, want %v", err, storage.ErrChunkNotFound) - } - }) - - // last synced chunk should not be removed - t.Run("get most recent synced chunk", func(t *testing.T) { - _, err := db.NewGetter(ModeGetRequest).Get(addrs[len(addrs)-1]) - if err != nil { - t.Fatal(err) - } - }) -} - -// TestDB_gcSize checks if gcSize has a correct value after -// database is initialized with existing data. -func TestDB_gcSize(t *testing.T) { - dir, err := ioutil.TempDir("", "localstore-stored-gc-size") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - baseKey := make([]byte, 32) - if _, err := rand.Read(baseKey); err != nil { - t.Fatal(err) - } - db, err := New(dir, baseKey, nil) - if err != nil { - t.Fatal(err) - } - - uploader := db.NewPutter(ModePutUpload) - syncer := db.NewSetter(ModeSetSync) - - count := 100 - - for i := 0; i < count; i++ { - chunk := generateRandomChunk() - - err := uploader.Put(chunk) - if err != nil { - t.Fatal(err) - } - - err = syncer.Set(chunk.Address()) - if err != nil { - t.Fatal(err) - } - } - - // DB.Close writes gc size to disk, so - // Instead calling Close, simulate database shutdown - // without it. - close(db.close) - db.updateGCWG.Wait() - err = db.shed.Close() - if err != nil { - t.Fatal(err) - } - - db, err = New(dir, baseKey, nil) - if err != nil { - t.Fatal(err) - } - - t.Run("gc index size", newIndexGCSizeTest(db)) - - t.Run("gc uncounted hashes index count", newItemsCountTest(db.gcUncountedHashesIndex, 0)) -} - -// setTestHookCollectGarbage sets testHookCollectGarbage and -// returns a function that will reset it to the -// value before the change. -func setTestHookCollectGarbage(h func(collectedCount int64)) (reset func()) { - current := testHookCollectGarbage - reset = func() { testHookCollectGarbage = current } - testHookCollectGarbage = h - return reset -} - -// TestSetTestHookCollectGarbage tests if setTestHookCollectGarbage changes -// testHookCollectGarbage function correctly and if its reset function -// resets the original function. -func TestSetTestHookCollectGarbage(t *testing.T) { - // Set the current function after the test finishes. - defer func(h func(collectedCount int64)) { testHookCollectGarbage = h }(testHookCollectGarbage) - - // expected value for the unchanged function - original := 1 - // expected value for the changed function - changed := 2 - - // this variable will be set with two different functions - var got int - - // define the original (unchanged) functions - testHookCollectGarbage = func(_ int64) { - got = original - } - - // set got variable - testHookCollectGarbage(0) - - // test if got variable is set correctly - if got != original { - t.Errorf("got hook value %v, want %v", got, original) - } - - // set the new function - reset := setTestHookCollectGarbage(func(_ int64) { - got = changed - }) - - // set got variable - testHookCollectGarbage(0) - - // test if got variable is set correctly to changed value - if got != changed { - t.Errorf("got hook value %v, want %v", got, changed) - } - - // set the function to the original one - reset() - - // set got variable - testHookCollectGarbage(0) - - // test if got variable is set correctly to original value - if got != original { - t.Errorf("got hook value %v, want %v", got, original) - } -} diff --git a/swarm/storage/localstore/index_test.go b/swarm/storage/localstore/index_test.go deleted file mode 100644 index 05d6c5c4c608..000000000000 --- a/swarm/storage/localstore/index_test.go +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package localstore - -import ( - "bytes" - "math/rand" - "testing" - - "github.com/ubiq/go-ubiq/swarm/storage" -) - -// TestDB_pullIndex validates the ordering of keys in pull index. -// Pull index key contains PO prefix which is calculated from -// DB base key and chunk address. This is not an Item field -// which are checked in Mode tests. -// This test uploads chunks, sorts them in expected order and -// validates that pull index iterator will iterate it the same -// order. -func TestDB_pullIndex(t *testing.T) { - db, cleanupFunc := newTestDB(t, nil) - defer cleanupFunc() - - uploader := db.NewPutter(ModePutUpload) - - chunkCount := 50 - - chunks := make([]testIndexChunk, chunkCount) - - // upload random chunks - for i := 0; i < chunkCount; i++ { - chunk := generateRandomChunk() - - err := uploader.Put(chunk) - if err != nil { - t.Fatal(err) - } - - chunks[i] = testIndexChunk{ - Chunk: chunk, - // this timestamp is not the same as in - // the index, but given that uploads - // are sequential and that only ordering - // of events matter, this information is - // sufficient - storeTimestamp: now(), - } - } - - testItemsOrder(t, db.pullIndex, chunks, func(i, j int) (less bool) { - poi := storage.Proximity(db.baseKey, chunks[i].Address()) - poj := storage.Proximity(db.baseKey, chunks[j].Address()) - if poi < poj { - return true - } - if poi > poj { - return false - } - if chunks[i].storeTimestamp < chunks[j].storeTimestamp { - return true - } - if chunks[i].storeTimestamp > chunks[j].storeTimestamp { - return false - } - return bytes.Compare(chunks[i].Address(), chunks[j].Address()) == -1 - }) -} - -// TestDB_gcIndex validates garbage collection index by uploading -// a chunk with and performing operations using synced, access and -// request modes. -func TestDB_gcIndex(t *testing.T) { - db, cleanupFunc := newTestDB(t, nil) - defer cleanupFunc() - - uploader := db.NewPutter(ModePutUpload) - - chunkCount := 50 - - chunks := make([]testIndexChunk, chunkCount) - - // upload random chunks - for i := 0; i < chunkCount; i++ { - chunk := generateRandomChunk() - - err := uploader.Put(chunk) - if err != nil { - t.Fatal(err) - } - - chunks[i] = testIndexChunk{ - Chunk: chunk, - } - } - - // check if all chunks are stored - newItemsCountTest(db.pullIndex, chunkCount)(t) - - // check that chunks are not collectable for garbage - newItemsCountTest(db.gcIndex, 0)(t) - - // set update gc test hook to signal when - // update gc goroutine is done by sending to - // testHookUpdateGCChan channel, which is - // used to wait for indexes change verifications - testHookUpdateGCChan := make(chan struct{}) - defer setTestHookUpdateGC(func() { - testHookUpdateGCChan <- struct{}{} - })() - - t.Run("request unsynced", func(t *testing.T) { - chunk := chunks[1] - - _, err := db.NewGetter(ModeGetRequest).Get(chunk.Address()) - if err != nil { - t.Fatal(err) - } - // wait for update gc goroutine to be done - <-testHookUpdateGCChan - - // the chunk is not synced - // should not be in the garbace collection index - newItemsCountTest(db.gcIndex, 0)(t) - - newIndexGCSizeTest(db)(t) - }) - - t.Run("sync one chunk", func(t *testing.T) { - chunk := chunks[0] - - err := db.NewSetter(ModeSetSync).Set(chunk.Address()) - if err != nil { - t.Fatal(err) - } - - // the chunk is synced and should be in gc index - newItemsCountTest(db.gcIndex, 1)(t) - - newIndexGCSizeTest(db)(t) - }) - - t.Run("sync all chunks", func(t *testing.T) { - setter := db.NewSetter(ModeSetSync) - - for i := range chunks { - err := setter.Set(chunks[i].Address()) - if err != nil { - t.Fatal(err) - } - } - - testItemsOrder(t, db.gcIndex, chunks, nil) - - newIndexGCSizeTest(db)(t) - }) - - t.Run("request one chunk", func(t *testing.T) { - i := 6 - - _, err := db.NewGetter(ModeGetRequest).Get(chunks[i].Address()) - if err != nil { - t.Fatal(err) - } - // wait for update gc goroutine to be done - <-testHookUpdateGCChan - - // move the chunk to the end of the expected gc - c := chunks[i] - chunks = append(chunks[:i], chunks[i+1:]...) - chunks = append(chunks, c) - - testItemsOrder(t, db.gcIndex, chunks, nil) - - newIndexGCSizeTest(db)(t) - }) - - t.Run("random chunk request", func(t *testing.T) { - requester := db.NewGetter(ModeGetRequest) - - rand.Shuffle(len(chunks), func(i, j int) { - chunks[i], chunks[j] = chunks[j], chunks[i] - }) - - for _, chunk := range chunks { - _, err := requester.Get(chunk.Address()) - if err != nil { - t.Fatal(err) - } - // wait for update gc goroutine to be done - <-testHookUpdateGCChan - } - - testItemsOrder(t, db.gcIndex, chunks, nil) - - newIndexGCSizeTest(db)(t) - }) - - t.Run("remove one chunk", func(t *testing.T) { - i := 3 - - err := db.NewSetter(modeSetRemove).Set(chunks[i].Address()) - if err != nil { - t.Fatal(err) - } - - // remove the chunk from the expected chunks in gc index - chunks = append(chunks[:i], chunks[i+1:]...) - - testItemsOrder(t, db.gcIndex, chunks, nil) - - newIndexGCSizeTest(db)(t) - }) -} diff --git a/swarm/storage/localstore/localstore.go b/swarm/storage/localstore/localstore.go deleted file mode 100644 index 49acbb4da90c..000000000000 --- a/swarm/storage/localstore/localstore.go +++ /dev/null @@ -1,431 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package localstore - -import ( - "encoding/binary" - "encoding/hex" - "errors" - "sync" - "time" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/swarm/shed" - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/ubiq/go-ubiq/swarm/storage/mock" -) - -var ( - // ErrInvalidMode is retuned when an unknown Mode - // is provided to the function. - ErrInvalidMode = errors.New("invalid mode") - // ErrAddressLockTimeout is returned when the same chunk - // is updated in parallel and one of the updates - // takes longer then the configured timeout duration. - ErrAddressLockTimeout = errors.New("address lock timeout") -) - -var ( - // Default value for Capacity DB option. - defaultCapacity int64 = 5000000 - // Limit the number of goroutines created by Getters - // that call updateGC function. Value 0 sets no limit. - maxParallelUpdateGC = 1000 -) - -// DB is the local store implementation and holds -// database related objects. -type DB struct { - shed *shed.DB - - // schema name of loaded data - schemaName shed.StringField - // field that stores number of intems in gc index - storedGCSize shed.Uint64Field - - // retrieval indexes - retrievalDataIndex shed.Index - retrievalAccessIndex shed.Index - // push syncing index - pushIndex shed.Index - // push syncing subscriptions triggers - pushTriggers []chan struct{} - pushTriggersMu sync.RWMutex - - // pull syncing index - pullIndex shed.Index - // pull syncing subscriptions triggers per bin - pullTriggers map[uint8][]chan struct{} - pullTriggersMu sync.RWMutex - - // garbage collection index - gcIndex shed.Index - // index that stores hashes that are not - // counted in and saved to storedGCSize - gcUncountedHashesIndex shed.Index - - // number of elements in garbage collection index - // it must be always read by getGCSize and - // set with incGCSize which are locking gcSizeMu - gcSize int64 - gcSizeMu sync.RWMutex - // garbage collection is triggered when gcSize exceeds - // the capacity value - capacity int64 - - // triggers garbage collection event loop - collectGarbageTrigger chan struct{} - // triggers write gc size event loop - writeGCSizeTrigger chan struct{} - - // a buffered channel acting as a semaphore - // to limit the maximal number of goroutines - // created by Getters to call updateGC function - updateGCSem chan struct{} - // a wait group to ensure all updateGC goroutines - // are done before closing the database - updateGCWG sync.WaitGroup - - baseKey []byte - - addressLocks sync.Map - - // this channel is closed when close function is called - // to terminate other goroutines - close chan struct{} -} - -// Options struct holds optional parameters for configuring DB. -type Options struct { - // MockStore is a mock node store that is used to store - // chunk data in a central store. It can be used to reduce - // total storage space requirements in testing large number - // of swarm nodes with chunk data deduplication provided by - // the mock global store. - MockStore *mock.NodeStore - // Capacity is a limit that triggers garbage collection when - // number of items in gcIndex equals or exceeds it. - Capacity int64 - // MetricsPrefix defines a prefix for metrics names. - MetricsPrefix string -} - -// New returns a new DB. All fields and indexes are initialized -// and possible conflicts with schema from existing database is checked. -// One goroutine for writing batches is created. -func New(path string, baseKey []byte, o *Options) (db *DB, err error) { - if o == nil { - o = new(Options) - } - db = &DB{ - capacity: o.Capacity, - baseKey: baseKey, - // channels collectGarbageTrigger and writeGCSizeTrigger - // need to be buffered with the size of 1 - // to signal another event if it - // is triggered during already running function - collectGarbageTrigger: make(chan struct{}, 1), - writeGCSizeTrigger: make(chan struct{}, 1), - close: make(chan struct{}), - } - if db.capacity <= 0 { - db.capacity = defaultCapacity - } - if maxParallelUpdateGC > 0 { - db.updateGCSem = make(chan struct{}, maxParallelUpdateGC) - } - - db.shed, err = shed.NewDB(path, o.MetricsPrefix) - if err != nil { - return nil, err - } - // Identify current storage schema by arbitrary name. - db.schemaName, err = db.shed.NewStringField("schema-name") - if err != nil { - return nil, err - } - // Persist gc size. - db.storedGCSize, err = db.shed.NewUint64Field("gc-size") - if err != nil { - return nil, err - } - // Functions for retrieval data index. - var ( - encodeValueFunc func(fields shed.Item) (value []byte, err error) - decodeValueFunc func(keyItem shed.Item, value []byte) (e shed.Item, err error) - ) - if o.MockStore != nil { - encodeValueFunc = func(fields shed.Item) (value []byte, err error) { - b := make([]byte, 8) - binary.BigEndian.PutUint64(b, uint64(fields.StoreTimestamp)) - err = o.MockStore.Put(fields.Address, fields.Data) - if err != nil { - return nil, err - } - return b, nil - } - decodeValueFunc = func(keyItem shed.Item, value []byte) (e shed.Item, err error) { - e.StoreTimestamp = int64(binary.BigEndian.Uint64(value[:8])) - e.Data, err = o.MockStore.Get(keyItem.Address) - return e, err - } - } else { - encodeValueFunc = func(fields shed.Item) (value []byte, err error) { - b := make([]byte, 8) - binary.BigEndian.PutUint64(b, uint64(fields.StoreTimestamp)) - value = append(b, fields.Data...) - return value, nil - } - decodeValueFunc = func(keyItem shed.Item, value []byte) (e shed.Item, err error) { - e.StoreTimestamp = int64(binary.BigEndian.Uint64(value[:8])) - e.Data = value[8:] - return e, nil - } - } - // Index storing actual chunk address, data and store timestamp. - db.retrievalDataIndex, err = db.shed.NewIndex("Address->StoreTimestamp|Data", shed.IndexFuncs{ - EncodeKey: func(fields shed.Item) (key []byte, err error) { - return fields.Address, nil - }, - DecodeKey: func(key []byte) (e shed.Item, err error) { - e.Address = key - return e, nil - }, - EncodeValue: encodeValueFunc, - DecodeValue: decodeValueFunc, - }) - if err != nil { - return nil, err - } - // Index storing access timestamp for a particular address. - // It is needed in order to update gc index keys for iteration order. - db.retrievalAccessIndex, err = db.shed.NewIndex("Address->AccessTimestamp", shed.IndexFuncs{ - EncodeKey: func(fields shed.Item) (key []byte, err error) { - return fields.Address, nil - }, - DecodeKey: func(key []byte) (e shed.Item, err error) { - e.Address = key - return e, nil - }, - EncodeValue: func(fields shed.Item) (value []byte, err error) { - b := make([]byte, 8) - binary.BigEndian.PutUint64(b, uint64(fields.AccessTimestamp)) - return b, nil - }, - DecodeValue: func(keyItem shed.Item, value []byte) (e shed.Item, err error) { - e.AccessTimestamp = int64(binary.BigEndian.Uint64(value)) - return e, nil - }, - }) - if err != nil { - return nil, err - } - // pull index allows history and live syncing per po bin - db.pullIndex, err = db.shed.NewIndex("PO|StoredTimestamp|Hash->nil", shed.IndexFuncs{ - EncodeKey: func(fields shed.Item) (key []byte, err error) { - key = make([]byte, 41) - key[0] = db.po(fields.Address) - binary.BigEndian.PutUint64(key[1:9], uint64(fields.StoreTimestamp)) - copy(key[9:], fields.Address[:]) - return key, nil - }, - DecodeKey: func(key []byte) (e shed.Item, err error) { - e.Address = key[9:] - e.StoreTimestamp = int64(binary.BigEndian.Uint64(key[1:9])) - return e, nil - }, - EncodeValue: func(fields shed.Item) (value []byte, err error) { - return nil, nil - }, - DecodeValue: func(keyItem shed.Item, value []byte) (e shed.Item, err error) { - return e, nil - }, - }) - if err != nil { - return nil, err - } - // create a pull syncing triggers used by SubscribePull function - db.pullTriggers = make(map[uint8][]chan struct{}) - // push index contains as yet unsynced chunks - db.pushIndex, err = db.shed.NewIndex("StoredTimestamp|Hash->nil", shed.IndexFuncs{ - EncodeKey: func(fields shed.Item) (key []byte, err error) { - key = make([]byte, 40) - binary.BigEndian.PutUint64(key[:8], uint64(fields.StoreTimestamp)) - copy(key[8:], fields.Address[:]) - return key, nil - }, - DecodeKey: func(key []byte) (e shed.Item, err error) { - e.Address = key[8:] - e.StoreTimestamp = int64(binary.BigEndian.Uint64(key[:8])) - return e, nil - }, - EncodeValue: func(fields shed.Item) (value []byte, err error) { - return nil, nil - }, - DecodeValue: func(keyItem shed.Item, value []byte) (e shed.Item, err error) { - return e, nil - }, - }) - if err != nil { - return nil, err - } - // create a push syncing triggers used by SubscribePush function - db.pushTriggers = make([]chan struct{}, 0) - // gc index for removable chunk ordered by ascending last access time - db.gcIndex, err = db.shed.NewIndex("AccessTimestamp|StoredTimestamp|Hash->nil", shed.IndexFuncs{ - EncodeKey: func(fields shed.Item) (key []byte, err error) { - b := make([]byte, 16, 16+len(fields.Address)) - binary.BigEndian.PutUint64(b[:8], uint64(fields.AccessTimestamp)) - binary.BigEndian.PutUint64(b[8:16], uint64(fields.StoreTimestamp)) - key = append(b, fields.Address...) - return key, nil - }, - DecodeKey: func(key []byte) (e shed.Item, err error) { - e.AccessTimestamp = int64(binary.BigEndian.Uint64(key[:8])) - e.StoreTimestamp = int64(binary.BigEndian.Uint64(key[8:16])) - e.Address = key[16:] - return e, nil - }, - EncodeValue: func(fields shed.Item) (value []byte, err error) { - return nil, nil - }, - DecodeValue: func(keyItem shed.Item, value []byte) (e shed.Item, err error) { - return e, nil - }, - }) - if err != nil { - return nil, err - } - // gc uncounted hashes index keeps hashes that are in gc index - // but not counted in and saved to storedGCSize - db.gcUncountedHashesIndex, err = db.shed.NewIndex("Hash->nil", shed.IndexFuncs{ - EncodeKey: func(fields shed.Item) (key []byte, err error) { - return fields.Address, nil - }, - DecodeKey: func(key []byte) (e shed.Item, err error) { - e.Address = key - return e, nil - }, - EncodeValue: func(fields shed.Item) (value []byte, err error) { - return nil, nil - }, - DecodeValue: func(keyItem shed.Item, value []byte) (e shed.Item, err error) { - return e, nil - }, - }) - if err != nil { - return nil, err - } - - // count number of elements in garbage collection index - gcSize, err := db.storedGCSize.Get() - if err != nil { - return nil, err - } - // get number of uncounted hashes - gcUncountedSize, err := db.gcUncountedHashesIndex.Count() - if err != nil { - return nil, err - } - gcSize += uint64(gcUncountedSize) - // remove uncounted hashes from the index and - // save the total gcSize after uncounted hashes are removed - err = db.writeGCSize(int64(gcSize)) - if err != nil { - return nil, err - } - db.incGCSize(int64(gcSize)) - - // start worker to write gc size - go db.writeGCSizeWorker() - // start garbage collection worker - go db.collectGarbageWorker() - return db, nil -} - -// Close closes the underlying database. -func (db *DB) Close() (err error) { - close(db.close) - db.updateGCWG.Wait() - if err := db.writeGCSize(db.getGCSize()); err != nil { - log.Error("localstore: write gc size", "err", err) - } - return db.shed.Close() -} - -// po computes the proximity order between the address -// and database base key. -func (db *DB) po(addr storage.Address) (bin uint8) { - return uint8(storage.Proximity(db.baseKey, addr)) -} - -var ( - // Maximal time for lockAddr to wait until it - // returns error. - addressLockTimeout = 3 * time.Second - // duration between two lock checks in lockAddr. - addressLockCheckDelay = 30 * time.Microsecond -) - -// lockAddr sets the lock on a particular address -// using addressLocks sync.Map and returns unlock function. -// If the address is locked this function will check it -// in a for loop for addressLockTimeout time, after which -// it will return ErrAddressLockTimeout error. -func (db *DB) lockAddr(addr storage.Address) (unlock func(), err error) { - start := time.Now() - lockKey := hex.EncodeToString(addr) - for { - _, loaded := db.addressLocks.LoadOrStore(lockKey, struct{}{}) - if !loaded { - break - } - time.Sleep(addressLockCheckDelay) - if time.Since(start) > addressLockTimeout { - return nil, ErrAddressLockTimeout - } - } - return func() { db.addressLocks.Delete(lockKey) }, nil -} - -// chunkToItem creates new Item with data provided by the Chunk. -func chunkToItem(ch storage.Chunk) shed.Item { - return shed.Item{ - Address: ch.Address(), - Data: ch.Data(), - } -} - -// addressToItem creates new Item with a provided address. -func addressToItem(addr storage.Address) shed.Item { - return shed.Item{ - Address: addr, - } -} - -// now is a helper function that returns a current unix timestamp -// in UTC timezone. -// It is set in the init function for usage in production, and -// optionally overridden in tests for data validation. -var now func() int64 - -func init() { - // set the now function - now = func() (t int64) { - return time.Now().UTC().UnixNano() - } -} diff --git a/swarm/storage/localstore/localstore_test.go b/swarm/storage/localstore/localstore_test.go deleted file mode 100644 index 56f993d1dc1e..000000000000 --- a/swarm/storage/localstore/localstore_test.go +++ /dev/null @@ -1,520 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package localstore - -import ( - "bytes" - "fmt" - "io/ioutil" - "math/rand" - "os" - "sort" - "strconv" - "sync" - "testing" - "time" - - ch "github.com/ubiq/go-ubiq/swarm/chunk" - "github.com/ubiq/go-ubiq/swarm/shed" - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/syndtr/goleveldb/leveldb" -) - -// TestDB validates if the chunk can be uploaded and -// correctly retrieved. -func TestDB(t *testing.T) { - db, cleanupFunc := newTestDB(t, nil) - defer cleanupFunc() - - chunk := generateRandomChunk() - - err := db.NewPutter(ModePutUpload).Put(chunk) - if err != nil { - t.Fatal(err) - } - - got, err := db.NewGetter(ModeGetRequest).Get(chunk.Address()) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(got.Address(), chunk.Address()) { - t.Errorf("got address %x, want %x", got.Address(), chunk.Address()) - } - if !bytes.Equal(got.Data(), chunk.Data()) { - t.Errorf("got data %x, want %x", got.Data(), chunk.Data()) - } -} - -// TestDB_updateGCSem tests maxParallelUpdateGC limit. -// This test temporary sets the limit to a low number, -// makes updateGC function execution time longer by -// setting a custom testHookUpdateGC function with a sleep -// and a count current and maximal number of goroutines. -func TestDB_updateGCSem(t *testing.T) { - updateGCSleep := time.Second - var count int - var max int - var mu sync.Mutex - defer setTestHookUpdateGC(func() { - mu.Lock() - // add to the count of current goroutines - count++ - if count > max { - // set maximal detected numbers of goroutines - max = count - } - mu.Unlock() - - // wait for some time to ensure multiple parallel goroutines - time.Sleep(updateGCSleep) - - mu.Lock() - count-- - mu.Unlock() - })() - - defer func(m int) { maxParallelUpdateGC = m }(maxParallelUpdateGC) - maxParallelUpdateGC = 3 - - db, cleanupFunc := newTestDB(t, nil) - defer cleanupFunc() - - chunk := generateRandomChunk() - - err := db.NewPutter(ModePutUpload).Put(chunk) - if err != nil { - t.Fatal(err) - } - - getter := db.NewGetter(ModeGetRequest) - - // get more chunks then maxParallelUpdateGC - // in time shorter then updateGCSleep - for i := 0; i < 5; i++ { - _, err = getter.Get(chunk.Address()) - if err != nil { - t.Fatal(err) - } - } - - if max != maxParallelUpdateGC { - t.Errorf("got max %v, want %v", max, maxParallelUpdateGC) - } -} - -// BenchmarkNew measures the time that New function -// needs to initialize and count the number of key/value -// pairs in GC index. -// This benchmark generates a number of chunks, uploads them, -// sets them to synced state for them to enter the GC index, -// and measures the execution time of New function by creating -// new databases with the same data directory. -// -// This benchmark takes significant amount of time. -// -// Measurements on MacBook Pro (Retina, 15-inch, Mid 2014) show -// that New function executes around 1s for database with 1M chunks. -// -// # go test -benchmem -run=none github.com/ubiq/go-ubiq/swarm/storage/localstore -bench BenchmarkNew -v -timeout 20m -// goos: darwin -// goarch: amd64 -// pkg: github.com/ubiq/go-ubiq/swarm/storage/localstore -// BenchmarkNew/1000-8 200 11672414 ns/op 9570960 B/op 10008 allocs/op -// BenchmarkNew/10000-8 100 14890609 ns/op 10490118 B/op 7759 allocs/op -// BenchmarkNew/100000-8 20 58334080 ns/op 17763157 B/op 22978 allocs/op -// BenchmarkNew/1000000-8 2 748595153 ns/op 45297404 B/op 253242 allocs/op -// PASS -func BenchmarkNew(b *testing.B) { - if testing.Short() { - b.Skip("skipping benchmark in short mode") - } - for _, count := range []int{ - 1000, - 10000, - 100000, - 1000000, - } { - b.Run(strconv.Itoa(count), func(b *testing.B) { - dir, err := ioutil.TempDir("", "localstore-new-benchmark") - if err != nil { - b.Fatal(err) - } - defer os.RemoveAll(dir) - baseKey := make([]byte, 32) - if _, err := rand.Read(baseKey); err != nil { - b.Fatal(err) - } - db, err := New(dir, baseKey, nil) - if err != nil { - b.Fatal(err) - } - uploader := db.NewPutter(ModePutUpload) - syncer := db.NewSetter(ModeSetSync) - for i := 0; i < count; i++ { - chunk := generateFakeRandomChunk() - err := uploader.Put(chunk) - if err != nil { - b.Fatal(err) - } - err = syncer.Set(chunk.Address()) - if err != nil { - b.Fatal(err) - } - } - err = db.Close() - if err != nil { - b.Fatal(err) - } - b.ResetTimer() - - for n := 0; n < b.N; n++ { - b.StartTimer() - db, err := New(dir, baseKey, nil) - b.StopTimer() - - if err != nil { - b.Fatal(err) - } - err = db.Close() - if err != nil { - b.Fatal(err) - } - } - }) - } -} - -// newTestDB is a helper function that constructs a -// temporary database and returns a cleanup function that must -// be called to remove the data. -func newTestDB(t testing.TB, o *Options) (db *DB, cleanupFunc func()) { - t.Helper() - - dir, err := ioutil.TempDir("", "localstore-test") - if err != nil { - t.Fatal(err) - } - cleanupFunc = func() { os.RemoveAll(dir) } - baseKey := make([]byte, 32) - if _, err := rand.Read(baseKey); err != nil { - t.Fatal(err) - } - db, err = New(dir, baseKey, o) - if err != nil { - cleanupFunc() - t.Fatal(err) - } - cleanupFunc = func() { - err := db.Close() - if err != nil { - t.Error(err) - } - os.RemoveAll(dir) - } - return db, cleanupFunc -} - -// generateRandomChunk generates a valid Chunk with -// data size of default chunk size. -func generateRandomChunk() storage.Chunk { - return storage.GenerateRandomChunk(ch.DefaultSize) -} - -func init() { - // needed for generateFakeRandomChunk - rand.Seed(time.Now().UnixNano()) -} - -// generateFakeRandomChunk generates a Chunk that is not -// valid, but it contains a random key and a random value. -// This function is faster then storage.GenerateRandomChunk -// which generates a valid chunk. -// Some tests in this package do not need valid chunks, just -// random data, and their execution time can be decreased -// using this function. -func generateFakeRandomChunk() storage.Chunk { - data := make([]byte, ch.DefaultSize) - rand.Read(data) - key := make([]byte, 32) - rand.Read(key) - return storage.NewChunk(key, data) -} - -// TestGenerateFakeRandomChunk validates that -// generateFakeRandomChunk returns random data by comparing -// two generated chunks. -func TestGenerateFakeRandomChunk(t *testing.T) { - c1 := generateFakeRandomChunk() - c2 := generateFakeRandomChunk() - addrLen := len(c1.Address()) - if addrLen != 32 { - t.Errorf("first chunk address length %v, want %v", addrLen, 32) - } - dataLen := len(c1.Data()) - if dataLen != ch.DefaultSize { - t.Errorf("first chunk data length %v, want %v", dataLen, ch.DefaultSize) - } - addrLen = len(c2.Address()) - if addrLen != 32 { - t.Errorf("second chunk address length %v, want %v", addrLen, 32) - } - dataLen = len(c2.Data()) - if dataLen != ch.DefaultSize { - t.Errorf("second chunk data length %v, want %v", dataLen, ch.DefaultSize) - } - if bytes.Equal(c1.Address(), c2.Address()) { - t.Error("fake chunks addresses do not differ") - } - if bytes.Equal(c1.Data(), c2.Data()) { - t.Error("fake chunks data bytes do not differ") - } -} - -// newRetrieveIndexesTest returns a test function that validates if the right -// chunk values are in the retrieval indexes. -func newRetrieveIndexesTest(db *DB, chunk storage.Chunk, storeTimestamp, accessTimestamp int64) func(t *testing.T) { - return func(t *testing.T) { - item, err := db.retrievalDataIndex.Get(addressToItem(chunk.Address())) - if err != nil { - t.Fatal(err) - } - validateItem(t, item, chunk.Address(), chunk.Data(), storeTimestamp, 0) - - // access index should not be set - wantErr := leveldb.ErrNotFound - item, err = db.retrievalAccessIndex.Get(addressToItem(chunk.Address())) - if err != wantErr { - t.Errorf("got error %v, want %v", err, wantErr) - } - } -} - -// newRetrieveIndexesTestWithAccess returns a test function that validates if the right -// chunk values are in the retrieval indexes when access time must be stored. -func newRetrieveIndexesTestWithAccess(db *DB, chunk storage.Chunk, storeTimestamp, accessTimestamp int64) func(t *testing.T) { - return func(t *testing.T) { - item, err := db.retrievalDataIndex.Get(addressToItem(chunk.Address())) - if err != nil { - t.Fatal(err) - } - validateItem(t, item, chunk.Address(), chunk.Data(), storeTimestamp, 0) - - if accessTimestamp > 0 { - item, err = db.retrievalAccessIndex.Get(addressToItem(chunk.Address())) - if err != nil { - t.Fatal(err) - } - validateItem(t, item, chunk.Address(), nil, 0, accessTimestamp) - } - } -} - -// newPullIndexTest returns a test function that validates if the right -// chunk values are in the pull index. -func newPullIndexTest(db *DB, chunk storage.Chunk, storeTimestamp int64, wantError error) func(t *testing.T) { - return func(t *testing.T) { - item, err := db.pullIndex.Get(shed.Item{ - Address: chunk.Address(), - StoreTimestamp: storeTimestamp, - }) - if err != wantError { - t.Errorf("got error %v, want %v", err, wantError) - } - if err == nil { - validateItem(t, item, chunk.Address(), nil, storeTimestamp, 0) - } - } -} - -// newPushIndexTest returns a test function that validates if the right -// chunk values are in the push index. -func newPushIndexTest(db *DB, chunk storage.Chunk, storeTimestamp int64, wantError error) func(t *testing.T) { - return func(t *testing.T) { - item, err := db.pushIndex.Get(shed.Item{ - Address: chunk.Address(), - StoreTimestamp: storeTimestamp, - }) - if err != wantError { - t.Errorf("got error %v, want %v", err, wantError) - } - if err == nil { - validateItem(t, item, chunk.Address(), nil, storeTimestamp, 0) - } - } -} - -// newGCIndexTest returns a test function that validates if the right -// chunk values are in the push index. -func newGCIndexTest(db *DB, chunk storage.Chunk, storeTimestamp, accessTimestamp int64) func(t *testing.T) { - return func(t *testing.T) { - item, err := db.gcIndex.Get(shed.Item{ - Address: chunk.Address(), - StoreTimestamp: storeTimestamp, - AccessTimestamp: accessTimestamp, - }) - if err != nil { - t.Fatal(err) - } - validateItem(t, item, chunk.Address(), nil, storeTimestamp, accessTimestamp) - } -} - -// newItemsCountTest returns a test function that validates if -// an index contains expected number of key/value pairs. -func newItemsCountTest(i shed.Index, want int) func(t *testing.T) { - return func(t *testing.T) { - var c int - err := i.Iterate(func(item shed.Item) (stop bool, err error) { - c++ - return - }, nil) - if err != nil { - t.Fatal(err) - } - if c != want { - t.Errorf("got %v items in index, want %v", c, want) - } - } -} - -// newIndexGCSizeTest retruns a test function that validates if DB.gcSize -// value is the same as the number of items in DB.gcIndex. -func newIndexGCSizeTest(db *DB) func(t *testing.T) { - return func(t *testing.T) { - var want int64 - err := db.gcIndex.Iterate(func(item shed.Item) (stop bool, err error) { - want++ - return - }, nil) - if err != nil { - t.Fatal(err) - } - got := db.getGCSize() - if got != want { - t.Errorf("got gc size %v, want %v", got, want) - } - } -} - -// testIndexChunk embeds storageChunk with additional data that is stored -// in database. It is used for index values validations. -type testIndexChunk struct { - storage.Chunk - storeTimestamp int64 -} - -// testItemsOrder tests the order of chunks in the index. If sortFunc is not nil, -// chunks will be sorted with it before validation. -func testItemsOrder(t *testing.T, i shed.Index, chunks []testIndexChunk, sortFunc func(i, j int) (less bool)) { - newItemsCountTest(i, len(chunks))(t) - - if sortFunc != nil { - sort.Slice(chunks, sortFunc) - } - - var cursor int - err := i.Iterate(func(item shed.Item) (stop bool, err error) { - want := chunks[cursor].Address() - got := item.Address - if !bytes.Equal(got, want) { - return true, fmt.Errorf("got address %x at position %v, want %x", got, cursor, want) - } - cursor++ - return false, nil - }, nil) - if err != nil { - t.Fatal(err) - } -} - -// validateItem is a helper function that checks Item values. -func validateItem(t *testing.T, item shed.Item, address, data []byte, storeTimestamp, accessTimestamp int64) { - t.Helper() - - if !bytes.Equal(item.Address, address) { - t.Errorf("got item address %x, want %x", item.Address, address) - } - if !bytes.Equal(item.Data, data) { - t.Errorf("got item data %x, want %x", item.Data, data) - } - if item.StoreTimestamp != storeTimestamp { - t.Errorf("got item store timestamp %v, want %v", item.StoreTimestamp, storeTimestamp) - } - if item.AccessTimestamp != accessTimestamp { - t.Errorf("got item access timestamp %v, want %v", item.AccessTimestamp, accessTimestamp) - } -} - -// setNow replaces now function and -// returns a function that will reset it to the -// value before the change. -func setNow(f func() int64) (reset func()) { - current := now - reset = func() { now = current } - now = f - return reset -} - -// TestSetNow tests if setNow function changes now function -// correctly and if its reset function resets the original function. -func TestSetNow(t *testing.T) { - // set the current function after the test finishes - defer func(f func() int64) { now = f }(now) - - // expected value for the unchanged function - var original int64 = 1 - // expected value for the changed function - var changed int64 = 2 - - // define the original (unchanged) functions - now = func() int64 { - return original - } - - // get the time - got := now() - - // test if got variable is set correctly - if got != original { - t.Errorf("got now value %v, want %v", got, original) - } - - // set the new function - reset := setNow(func() int64 { - return changed - }) - - // get the time - got = now() - - // test if got variable is set correctly to changed value - if got != changed { - t.Errorf("got hook value %v, want %v", got, changed) - } - - // set the function to the original one - reset() - - // get the time - got = now() - - // test if got variable is set correctly to original value - if got != original { - t.Errorf("got hook value %v, want %v", got, original) - } -} diff --git a/swarm/storage/localstore/mode_get.go b/swarm/storage/localstore/mode_get.go deleted file mode 100644 index 5d4380473cd2..000000000000 --- a/swarm/storage/localstore/mode_get.go +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package localstore - -import ( - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/swarm/shed" - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/syndtr/goleveldb/leveldb" -) - -// ModeGet enumerates different Getter modes. -type ModeGet int - -// Getter modes. -const ( - // ModeGetRequest: when accessed for retrieval - ModeGetRequest ModeGet = iota - // ModeGetSync: when accessed for syncing or proof of custody request - ModeGetSync -) - -// Getter provides Get method to retrieve Chunks -// from database. -type Getter struct { - db *DB - mode ModeGet -} - -// NewGetter returns a new Getter on database -// with a specific Mode. -func (db *DB) NewGetter(mode ModeGet) *Getter { - return &Getter{ - mode: mode, - db: db, - } -} - -// Get returns a chunk from the database. If the chunk is -// not found storage.ErrChunkNotFound will be returned. -// All required indexes will be updated required by the -// Getter Mode. -func (g *Getter) Get(addr storage.Address) (chunk storage.Chunk, err error) { - out, err := g.db.get(g.mode, addr) - if err != nil { - if err == leveldb.ErrNotFound { - return nil, storage.ErrChunkNotFound - } - return nil, err - } - return storage.NewChunk(out.Address, out.Data), nil -} - -// get returns Item from the retrieval index -// and updates other indexes. -func (db *DB) get(mode ModeGet, addr storage.Address) (out shed.Item, err error) { - item := addressToItem(addr) - - out, err = db.retrievalDataIndex.Get(item) - if err != nil { - return out, err - } - switch mode { - // update the access timestamp and gc index - case ModeGetRequest: - if db.updateGCSem != nil { - // wait before creating new goroutines - // if updateGCSem buffer id full - db.updateGCSem <- struct{}{} - } - db.updateGCWG.Add(1) - go func() { - defer db.updateGCWG.Done() - if db.updateGCSem != nil { - // free a spot in updateGCSem buffer - // for a new goroutine - defer func() { <-db.updateGCSem }() - } - err := db.updateGC(out) - if err != nil { - log.Error("localstore update gc", "err", err) - } - // if gc update hook is defined, call it - if testHookUpdateGC != nil { - testHookUpdateGC() - } - }() - - // no updates to indexes - case ModeGetSync: - default: - return out, ErrInvalidMode - } - return out, nil -} - -// updateGC updates garbage collection index for -// a single item. Provided item is expected to have -// only Address and Data fields with non zero values, -// which is ensured by the get function. -func (db *DB) updateGC(item shed.Item) (err error) { - unlock, err := db.lockAddr(item.Address) - if err != nil { - return err - } - defer unlock() - - batch := new(leveldb.Batch) - - // update accessTimeStamp in retrieve, gc - - i, err := db.retrievalAccessIndex.Get(item) - switch err { - case nil: - item.AccessTimestamp = i.AccessTimestamp - case leveldb.ErrNotFound: - // no chunk accesses - default: - return err - } - if item.AccessTimestamp == 0 { - // chunk is not yet synced - // do not add it to the gc index - return nil - } - // delete current entry from the gc index - db.gcIndex.DeleteInBatch(batch, item) - // update access timestamp - item.AccessTimestamp = now() - // update retrieve access index - db.retrievalAccessIndex.PutInBatch(batch, item) - // add new entry to gc index - db.gcIndex.PutInBatch(batch, item) - - return db.shed.WriteBatch(batch) -} - -// testHookUpdateGC is a hook that can provide -// information when a garbage collection index is updated. -var testHookUpdateGC func() diff --git a/swarm/storage/localstore/mode_get_test.go b/swarm/storage/localstore/mode_get_test.go deleted file mode 100644 index 6615a3b8896e..000000000000 --- a/swarm/storage/localstore/mode_get_test.go +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package localstore - -import ( - "bytes" - "testing" - "time" -) - -// TestModeGetRequest validates ModeGetRequest index values on the provided DB. -func TestModeGetRequest(t *testing.T) { - db, cleanupFunc := newTestDB(t, nil) - defer cleanupFunc() - - uploadTimestamp := time.Now().UTC().UnixNano() - defer setNow(func() (t int64) { - return uploadTimestamp - })() - - chunk := generateRandomChunk() - - err := db.NewPutter(ModePutUpload).Put(chunk) - if err != nil { - t.Fatal(err) - } - - requester := db.NewGetter(ModeGetRequest) - - // set update gc test hook to signal when - // update gc goroutine is done by sending to - // testHookUpdateGCChan channel, which is - // used to wait for garbage colletion index - // changes - testHookUpdateGCChan := make(chan struct{}) - defer setTestHookUpdateGC(func() { - testHookUpdateGCChan <- struct{}{} - })() - - t.Run("get unsynced", func(t *testing.T) { - got, err := requester.Get(chunk.Address()) - if err != nil { - t.Fatal(err) - } - // wait for update gc goroutine to be done - <-testHookUpdateGCChan - - if !bytes.Equal(got.Address(), chunk.Address()) { - t.Errorf("got chunk address %x, want %x", got.Address(), chunk.Address()) - } - - if !bytes.Equal(got.Data(), chunk.Data()) { - t.Errorf("got chunk data %x, want %x", got.Data(), chunk.Data()) - } - - t.Run("retrieve indexes", newRetrieveIndexesTestWithAccess(db, chunk, uploadTimestamp, 0)) - - t.Run("gc index count", newItemsCountTest(db.gcIndex, 0)) - - t.Run("gc size", newIndexGCSizeTest(db)) - }) - - // set chunk to synced state - err = db.NewSetter(ModeSetSync).Set(chunk.Address()) - if err != nil { - t.Fatal(err) - } - - t.Run("first get", func(t *testing.T) { - got, err := requester.Get(chunk.Address()) - if err != nil { - t.Fatal(err) - } - // wait for update gc goroutine to be done - <-testHookUpdateGCChan - - if !bytes.Equal(got.Address(), chunk.Address()) { - t.Errorf("got chunk address %x, want %x", got.Address(), chunk.Address()) - } - - if !bytes.Equal(got.Data(), chunk.Data()) { - t.Errorf("got chunk data %x, want %x", got.Data(), chunk.Data()) - } - - t.Run("retrieve indexes", newRetrieveIndexesTestWithAccess(db, chunk, uploadTimestamp, uploadTimestamp)) - - t.Run("gc index", newGCIndexTest(db, chunk, uploadTimestamp, uploadTimestamp)) - - t.Run("gc index count", newItemsCountTest(db.gcIndex, 1)) - - t.Run("gc size", newIndexGCSizeTest(db)) - }) - - t.Run("second get", func(t *testing.T) { - accessTimestamp := time.Now().UTC().UnixNano() - defer setNow(func() (t int64) { - return accessTimestamp - })() - - got, err := requester.Get(chunk.Address()) - if err != nil { - t.Fatal(err) - } - // wait for update gc goroutine to be done - <-testHookUpdateGCChan - - if !bytes.Equal(got.Address(), chunk.Address()) { - t.Errorf("got chunk address %x, want %x", got.Address(), chunk.Address()) - } - - if !bytes.Equal(got.Data(), chunk.Data()) { - t.Errorf("got chunk data %x, want %x", got.Data(), chunk.Data()) - } - - t.Run("retrieve indexes", newRetrieveIndexesTestWithAccess(db, chunk, uploadTimestamp, accessTimestamp)) - - t.Run("gc index", newGCIndexTest(db, chunk, uploadTimestamp, accessTimestamp)) - - t.Run("gc index count", newItemsCountTest(db.gcIndex, 1)) - - t.Run("gc size", newIndexGCSizeTest(db)) - }) -} - -// TestModeGetSync validates ModeGetSync index values on the provided DB. -func TestModeGetSync(t *testing.T) { - db, cleanupFunc := newTestDB(t, nil) - defer cleanupFunc() - - uploadTimestamp := time.Now().UTC().UnixNano() - defer setNow(func() (t int64) { - return uploadTimestamp - })() - - chunk := generateRandomChunk() - - err := db.NewPutter(ModePutUpload).Put(chunk) - if err != nil { - t.Fatal(err) - } - - got, err := db.NewGetter(ModeGetSync).Get(chunk.Address()) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(got.Address(), chunk.Address()) { - t.Errorf("got chunk address %x, want %x", got.Address(), chunk.Address()) - } - - if !bytes.Equal(got.Data(), chunk.Data()) { - t.Errorf("got chunk data %x, want %x", got.Data(), chunk.Data()) - } - - t.Run("retrieve indexes", newRetrieveIndexesTestWithAccess(db, chunk, uploadTimestamp, 0)) - - t.Run("gc index count", newItemsCountTest(db.gcIndex, 0)) - - t.Run("gc size", newIndexGCSizeTest(db)) -} - -// setTestHookUpdateGC sets testHookUpdateGC and -// returns a function that will reset it to the -// value before the change. -func setTestHookUpdateGC(h func()) (reset func()) { - current := testHookUpdateGC - reset = func() { testHookUpdateGC = current } - testHookUpdateGC = h - return reset -} - -// TestSetTestHookUpdateGC tests if setTestHookUpdateGC changes -// testHookUpdateGC function correctly and if its reset function -// resets the original function. -func TestSetTestHookUpdateGC(t *testing.T) { - // Set the current function after the test finishes. - defer func(h func()) { testHookUpdateGC = h }(testHookUpdateGC) - - // expected value for the unchanged function - original := 1 - // expected value for the changed function - changed := 2 - - // this variable will be set with two different functions - var got int - - // define the original (unchanged) functions - testHookUpdateGC = func() { - got = original - } - - // set got variable - testHookUpdateGC() - - // test if got variable is set correctly - if got != original { - t.Errorf("got hook value %v, want %v", got, original) - } - - // set the new function - reset := setTestHookUpdateGC(func() { - got = changed - }) - - // set got variable - testHookUpdateGC() - - // test if got variable is set correctly to changed value - if got != changed { - t.Errorf("got hook value %v, want %v", got, changed) - } - - // set the function to the original one - reset() - - // set got variable - testHookUpdateGC() - - // test if got variable is set correctly to original value - if got != original { - t.Errorf("got hook value %v, want %v", got, original) - } -} diff --git a/swarm/storage/localstore/mode_put.go b/swarm/storage/localstore/mode_put.go deleted file mode 100644 index 8792f8894916..000000000000 --- a/swarm/storage/localstore/mode_put.go +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package localstore - -import ( - "github.com/ubiq/go-ubiq/swarm/shed" - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/syndtr/goleveldb/leveldb" -) - -// ModePut enumerates different Putter modes. -type ModePut int - -// Putter modes. -const ( - // ModePutRequest: when a chunk is received as a result of retrieve request and delivery - ModePutRequest ModePut = iota - // ModePutSync: when a chunk is received via syncing - ModePutSync - // ModePutUpload: when a chunk is created by local upload - ModePutUpload -) - -// Putter provides Put method to store Chunks -// to database. -type Putter struct { - db *DB - mode ModePut -} - -// NewPutter returns a new Putter on database -// with a specific Mode. -func (db *DB) NewPutter(mode ModePut) *Putter { - return &Putter{ - mode: mode, - db: db, - } -} - -// Put stores the Chunk to database and depending -// on the Putter mode, it updates required indexes. -func (p *Putter) Put(ch storage.Chunk) (err error) { - return p.db.put(p.mode, chunkToItem(ch)) -} - -// put stores Item to database and updates other -// indexes. It acquires lockAddr to protect two calls -// of this function for the same address in parallel. -// Item fields Address and Data must not be -// with their nil values. -func (db *DB) put(mode ModePut, item shed.Item) (err error) { - // protect parallel updates - unlock, err := db.lockAddr(item.Address) - if err != nil { - return err - } - defer unlock() - - batch := new(leveldb.Batch) - - // variables that provide information for operations - // to be done after write batch function successfully executes - var gcSizeChange int64 // number to add or subtract from gcSize - var triggerPullFeed bool // signal pull feed subscriptions to iterate - var triggerPushFeed bool // signal push feed subscriptions to iterate - - switch mode { - case ModePutRequest: - // put to indexes: retrieve, gc; it does not enter the syncpool - - // check if the chunk already is in the database - // as gc index is updated - i, err := db.retrievalAccessIndex.Get(item) - switch err { - case nil: - item.AccessTimestamp = i.AccessTimestamp - case leveldb.ErrNotFound: - // no chunk accesses - default: - return err - } - i, err = db.retrievalDataIndex.Get(item) - switch err { - case nil: - item.StoreTimestamp = i.StoreTimestamp - case leveldb.ErrNotFound: - // no chunk accesses - default: - return err - } - if item.AccessTimestamp != 0 { - // delete current entry from the gc index - db.gcIndex.DeleteInBatch(batch, item) - gcSizeChange-- - } - if item.StoreTimestamp == 0 { - item.StoreTimestamp = now() - } - // update access timestamp - item.AccessTimestamp = now() - // update retrieve access index - db.retrievalAccessIndex.PutInBatch(batch, item) - // add new entry to gc index - db.gcIndex.PutInBatch(batch, item) - db.gcUncountedHashesIndex.PutInBatch(batch, item) - gcSizeChange++ - - db.retrievalDataIndex.PutInBatch(batch, item) - - case ModePutUpload: - // put to indexes: retrieve, push, pull - - item.StoreTimestamp = now() - db.retrievalDataIndex.PutInBatch(batch, item) - db.pullIndex.PutInBatch(batch, item) - triggerPullFeed = true - db.pushIndex.PutInBatch(batch, item) - triggerPushFeed = true - - case ModePutSync: - // put to indexes: retrieve, pull - - item.StoreTimestamp = now() - db.retrievalDataIndex.PutInBatch(batch, item) - db.pullIndex.PutInBatch(batch, item) - triggerPullFeed = true - - default: - return ErrInvalidMode - } - - err = db.shed.WriteBatch(batch) - if err != nil { - return err - } - if gcSizeChange != 0 { - db.incGCSize(gcSizeChange) - } - if triggerPullFeed { - db.triggerPullSubscriptions(db.po(item.Address)) - } - if triggerPushFeed { - db.triggerPushSubscriptions() - } - return nil -} diff --git a/swarm/storage/localstore/mode_put_test.go b/swarm/storage/localstore/mode_put_test.go deleted file mode 100644 index a7cef8a369de..000000000000 --- a/swarm/storage/localstore/mode_put_test.go +++ /dev/null @@ -1,300 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package localstore - -import ( - "bytes" - "fmt" - "sync" - "testing" - "time" - - "github.com/ubiq/go-ubiq/swarm/storage" -) - -// TestModePutRequest validates ModePutRequest index values on the provided DB. -func TestModePutRequest(t *testing.T) { - db, cleanupFunc := newTestDB(t, nil) - defer cleanupFunc() - - putter := db.NewPutter(ModePutRequest) - - chunk := generateRandomChunk() - - // keep the record when the chunk is stored - var storeTimestamp int64 - - t.Run("first put", func(t *testing.T) { - wantTimestamp := time.Now().UTC().UnixNano() - defer setNow(func() (t int64) { - return wantTimestamp - })() - - storeTimestamp = wantTimestamp - - err := putter.Put(chunk) - if err != nil { - t.Fatal(err) - } - - t.Run("retrieve indexes", newRetrieveIndexesTestWithAccess(db, chunk, wantTimestamp, wantTimestamp)) - - t.Run("gc index count", newItemsCountTest(db.gcIndex, 1)) - - t.Run("gc size", newIndexGCSizeTest(db)) - }) - - t.Run("second put", func(t *testing.T) { - wantTimestamp := time.Now().UTC().UnixNano() - defer setNow(func() (t int64) { - return wantTimestamp - })() - - err := putter.Put(chunk) - if err != nil { - t.Fatal(err) - } - - t.Run("retrieve indexes", newRetrieveIndexesTestWithAccess(db, chunk, storeTimestamp, wantTimestamp)) - - t.Run("gc index count", newItemsCountTest(db.gcIndex, 1)) - - t.Run("gc size", newIndexGCSizeTest(db)) - }) -} - -// TestModePutSync validates ModePutSync index values on the provided DB. -func TestModePutSync(t *testing.T) { - db, cleanupFunc := newTestDB(t, nil) - defer cleanupFunc() - - wantTimestamp := time.Now().UTC().UnixNano() - defer setNow(func() (t int64) { - return wantTimestamp - })() - - chunk := generateRandomChunk() - - err := db.NewPutter(ModePutSync).Put(chunk) - if err != nil { - t.Fatal(err) - } - - t.Run("retrieve indexes", newRetrieveIndexesTest(db, chunk, wantTimestamp, 0)) - - t.Run("pull index", newPullIndexTest(db, chunk, wantTimestamp, nil)) -} - -// TestModePutUpload validates ModePutUpload index values on the provided DB. -func TestModePutUpload(t *testing.T) { - db, cleanupFunc := newTestDB(t, nil) - defer cleanupFunc() - - wantTimestamp := time.Now().UTC().UnixNano() - defer setNow(func() (t int64) { - return wantTimestamp - })() - - chunk := generateRandomChunk() - - err := db.NewPutter(ModePutUpload).Put(chunk) - if err != nil { - t.Fatal(err) - } - - t.Run("retrieve indexes", newRetrieveIndexesTest(db, chunk, wantTimestamp, 0)) - - t.Run("pull index", newPullIndexTest(db, chunk, wantTimestamp, nil)) - - t.Run("push index", newPushIndexTest(db, chunk, wantTimestamp, nil)) -} - -// TestModePutUpload_parallel uploads chunks in parallel -// and validates if all chunks can be retrieved with correct data. -func TestModePutUpload_parallel(t *testing.T) { - db, cleanupFunc := newTestDB(t, nil) - defer cleanupFunc() - - chunkCount := 1000 - workerCount := 100 - - chunkChan := make(chan storage.Chunk) - errChan := make(chan error) - doneChan := make(chan struct{}) - defer close(doneChan) - - // start uploader workers - for i := 0; i < workerCount; i++ { - go func(i int) { - uploader := db.NewPutter(ModePutUpload) - for { - select { - case chunk, ok := <-chunkChan: - if !ok { - return - } - err := uploader.Put(chunk) - select { - case errChan <- err: - case <-doneChan: - } - case <-doneChan: - return - } - } - }(i) - } - - chunks := make([]storage.Chunk, 0) - var chunksMu sync.Mutex - - // send chunks to workers - go func() { - for i := 0; i < chunkCount; i++ { - chunk := generateRandomChunk() - select { - case chunkChan <- chunk: - case <-doneChan: - return - } - chunksMu.Lock() - chunks = append(chunks, chunk) - chunksMu.Unlock() - } - - close(chunkChan) - }() - - // validate every error from workers - for i := 0; i < chunkCount; i++ { - err := <-errChan - if err != nil { - t.Fatal(err) - } - } - - // get every chunk and validate its data - getter := db.NewGetter(ModeGetRequest) - - chunksMu.Lock() - defer chunksMu.Unlock() - for _, chunk := range chunks { - got, err := getter.Get(chunk.Address()) - if err != nil { - t.Fatal(err) - } - if !bytes.Equal(got.Data(), chunk.Data()) { - t.Fatalf("got chunk %s data %x, want %x", chunk.Address().Hex(), got.Data(), chunk.Data()) - } - } -} - -// BenchmarkPutUpload runs a series of benchmarks that upload -// a specific number of chunks in parallel. -// -// Measurements on MacBook Pro (Retina, 15-inch, Mid 2014) -// -// # go test -benchmem -run=none github.com/ubiq/go-ubiq/swarm/storage/localstore -bench BenchmarkPutUpload -v -// -// goos: darwin -// goarch: amd64 -// pkg: github.com/ubiq/go-ubiq/swarm/storage/localstore -// BenchmarkPutUpload/count_100_parallel_1-8 300 5107704 ns/op 2081461 B/op 2374 allocs/op -// BenchmarkPutUpload/count_100_parallel_2-8 300 5411742 ns/op 2081608 B/op 2364 allocs/op -// BenchmarkPutUpload/count_100_parallel_4-8 500 3704964 ns/op 2081696 B/op 2324 allocs/op -// BenchmarkPutUpload/count_100_parallel_8-8 500 2932663 ns/op 2082594 B/op 2295 allocs/op -// BenchmarkPutUpload/count_100_parallel_16-8 500 3117157 ns/op 2085438 B/op 2282 allocs/op -// BenchmarkPutUpload/count_100_parallel_32-8 500 3449122 ns/op 2089721 B/op 2286 allocs/op -// BenchmarkPutUpload/count_1000_parallel_1-8 20 79784470 ns/op 25211240 B/op 23225 allocs/op -// BenchmarkPutUpload/count_1000_parallel_2-8 20 75422164 ns/op 25210730 B/op 23187 allocs/op -// BenchmarkPutUpload/count_1000_parallel_4-8 20 70698378 ns/op 25206522 B/op 22692 allocs/op -// BenchmarkPutUpload/count_1000_parallel_8-8 20 71285528 ns/op 25213436 B/op 22345 allocs/op -// BenchmarkPutUpload/count_1000_parallel_16-8 20 71301826 ns/op 25205040 B/op 22090 allocs/op -// BenchmarkPutUpload/count_1000_parallel_32-8 30 57713506 ns/op 25219781 B/op 21848 allocs/op -// BenchmarkPutUpload/count_10000_parallel_1-8 2 656719345 ns/op 216792908 B/op 248940 allocs/op -// BenchmarkPutUpload/count_10000_parallel_2-8 2 646301962 ns/op 216730800 B/op 248270 allocs/op -// BenchmarkPutUpload/count_10000_parallel_4-8 2 532784228 ns/op 216667080 B/op 241910 allocs/op -// BenchmarkPutUpload/count_10000_parallel_8-8 3 494290188 ns/op 216297749 B/op 236247 allocs/op -// BenchmarkPutUpload/count_10000_parallel_16-8 3 483485315 ns/op 216060384 B/op 231090 allocs/op -// BenchmarkPutUpload/count_10000_parallel_32-8 3 434461294 ns/op 215371280 B/op 224800 allocs/op -// BenchmarkPutUpload/count_100000_parallel_1-8 1 22767894338 ns/op 2331372088 B/op 4049876 allocs/op -// BenchmarkPutUpload/count_100000_parallel_2-8 1 25347872677 ns/op 2344140160 B/op 4106763 allocs/op -// BenchmarkPutUpload/count_100000_parallel_4-8 1 23580460174 ns/op 2338582576 B/op 4027452 allocs/op -// BenchmarkPutUpload/count_100000_parallel_8-8 1 22197559193 ns/op 2321803496 B/op 3877553 allocs/op -// BenchmarkPutUpload/count_100000_parallel_16-8 1 22527046476 ns/op 2327854800 B/op 3885455 allocs/op -// BenchmarkPutUpload/count_100000_parallel_32-8 1 21332243613 ns/op 2299654568 B/op 3697181 allocs/op -// PASS -func BenchmarkPutUpload(b *testing.B) { - for _, count := range []int{ - 100, - 1000, - 10000, - 100000, - } { - for _, maxParallelUploads := range []int{ - 1, - 2, - 4, - 8, - 16, - 32, - } { - name := fmt.Sprintf("count %v parallel %v", count, maxParallelUploads) - b.Run(name, func(b *testing.B) { - for n := 0; n < b.N; n++ { - benchmarkPutUpload(b, nil, count, maxParallelUploads) - } - }) - } - } -} - -// benchmarkPutUpload runs a benchmark by uploading a specific number -// of chunks with specified max parallel uploads. -func benchmarkPutUpload(b *testing.B, o *Options, count, maxParallelUploads int) { - b.StopTimer() - db, cleanupFunc := newTestDB(b, o) - defer cleanupFunc() - - uploader := db.NewPutter(ModePutUpload) - chunks := make([]storage.Chunk, count) - for i := 0; i < count; i++ { - chunks[i] = generateFakeRandomChunk() - } - errs := make(chan error) - b.StartTimer() - - go func() { - sem := make(chan struct{}, maxParallelUploads) - for i := 0; i < count; i++ { - sem <- struct{}{} - - go func(i int) { - defer func() { <-sem }() - - errs <- uploader.Put(chunks[i]) - }(i) - } - }() - - for i := 0; i < count; i++ { - err := <-errs - if err != nil { - b.Fatal(err) - } - } -} diff --git a/swarm/storage/localstore/mode_set.go b/swarm/storage/localstore/mode_set.go deleted file mode 100644 index e04baf3c2f2c..000000000000 --- a/swarm/storage/localstore/mode_set.go +++ /dev/null @@ -1,205 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package localstore - -import ( - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/syndtr/goleveldb/leveldb" -) - -// ModeSet enumerates different Setter modes. -type ModeSet int - -// Setter modes. -const ( - // ModeSetAccess: when an update request is received for a chunk or chunk is retrieved for delivery - ModeSetAccess ModeSet = iota - // ModeSetSync: when push sync receipt is received - ModeSetSync - // modeSetRemove: when GC-d - // unexported as no external packages should remove chunks from database - modeSetRemove -) - -// Setter sets the state of a particular -// Chunk in database by changing indexes. -type Setter struct { - db *DB - mode ModeSet -} - -// NewSetter returns a new Setter on database -// with a specific Mode. -func (db *DB) NewSetter(mode ModeSet) *Setter { - return &Setter{ - mode: mode, - db: db, - } -} - -// Set updates database indexes for a specific -// chunk represented by the address. -func (s *Setter) Set(addr storage.Address) (err error) { - return s.db.set(s.mode, addr) -} - -// set updates database indexes for a specific -// chunk represented by the address. -// It acquires lockAddr to protect two calls -// of this function for the same address in parallel. -func (db *DB) set(mode ModeSet, addr storage.Address) (err error) { - // protect parallel updates - unlock, err := db.lockAddr(addr) - if err != nil { - return err - } - defer unlock() - - batch := new(leveldb.Batch) - - // variables that provide information for operations - // to be done after write batch function successfully executes - var gcSizeChange int64 // number to add or subtract from gcSize - var triggerPullFeed bool // signal pull feed subscriptions to iterate - - item := addressToItem(addr) - - switch mode { - case ModeSetAccess: - // add to pull, insert to gc - - // need to get access timestamp here as it is not - // provided by the access function, and it is not - // a property of a chunk provided to Accessor.Put. - - i, err := db.retrievalDataIndex.Get(item) - switch err { - case nil: - item.StoreTimestamp = i.StoreTimestamp - case leveldb.ErrNotFound: - db.pushIndex.DeleteInBatch(batch, item) - item.StoreTimestamp = now() - default: - return err - } - - i, err = db.retrievalAccessIndex.Get(item) - switch err { - case nil: - item.AccessTimestamp = i.AccessTimestamp - db.gcIndex.DeleteInBatch(batch, item) - gcSizeChange-- - case leveldb.ErrNotFound: - // the chunk is not accessed before - default: - return err - } - item.AccessTimestamp = now() - db.retrievalAccessIndex.PutInBatch(batch, item) - db.pullIndex.PutInBatch(batch, item) - triggerPullFeed = true - db.gcIndex.PutInBatch(batch, item) - db.gcUncountedHashesIndex.PutInBatch(batch, item) - gcSizeChange++ - - case ModeSetSync: - // delete from push, insert to gc - - // need to get access timestamp here as it is not - // provided by the access function, and it is not - // a property of a chunk provided to Accessor.Put. - i, err := db.retrievalDataIndex.Get(item) - if err != nil { - if err == leveldb.ErrNotFound { - // chunk is not found, - // no need to update gc index - // just delete from the push index - // if it is there - db.pushIndex.DeleteInBatch(batch, item) - return nil - } - return err - } - item.StoreTimestamp = i.StoreTimestamp - - i, err = db.retrievalAccessIndex.Get(item) - switch err { - case nil: - item.AccessTimestamp = i.AccessTimestamp - db.gcIndex.DeleteInBatch(batch, item) - gcSizeChange-- - case leveldb.ErrNotFound: - // the chunk is not accessed before - default: - return err - } - item.AccessTimestamp = now() - db.retrievalAccessIndex.PutInBatch(batch, item) - db.pushIndex.DeleteInBatch(batch, item) - db.gcIndex.PutInBatch(batch, item) - db.gcUncountedHashesIndex.PutInBatch(batch, item) - gcSizeChange++ - - case modeSetRemove: - // delete from retrieve, pull, gc - - // need to get access timestamp here as it is not - // provided by the access function, and it is not - // a property of a chunk provided to Accessor.Put. - - i, err := db.retrievalAccessIndex.Get(item) - switch err { - case nil: - item.AccessTimestamp = i.AccessTimestamp - case leveldb.ErrNotFound: - default: - return err - } - i, err = db.retrievalDataIndex.Get(item) - if err != nil { - return err - } - item.StoreTimestamp = i.StoreTimestamp - - db.retrievalDataIndex.DeleteInBatch(batch, item) - db.retrievalAccessIndex.DeleteInBatch(batch, item) - db.pullIndex.DeleteInBatch(batch, item) - db.gcIndex.DeleteInBatch(batch, item) - db.gcUncountedHashesIndex.DeleteInBatch(batch, item) - // a check is needed for decrementing gcSize - // as delete is not reporting if the key/value pair - // is deleted or not - if _, err := db.gcIndex.Get(item); err == nil { - gcSizeChange = -1 - } - - default: - return ErrInvalidMode - } - - err = db.shed.WriteBatch(batch) - if err != nil { - return err - } - if gcSizeChange != 0 { - db.incGCSize(gcSizeChange) - } - if triggerPullFeed { - db.triggerPullSubscriptions(db.po(item.Address)) - } - return nil -} diff --git a/swarm/storage/localstore/mode_set_test.go b/swarm/storage/localstore/mode_set_test.go deleted file mode 100644 index 94cd0a3e2c9d..000000000000 --- a/swarm/storage/localstore/mode_set_test.go +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package localstore - -import ( - "testing" - "time" - - "github.com/syndtr/goleveldb/leveldb" -) - -// TestModeSetAccess validates ModeSetAccess index values on the provided DB. -func TestModeSetAccess(t *testing.T) { - db, cleanupFunc := newTestDB(t, nil) - defer cleanupFunc() - - chunk := generateRandomChunk() - - wantTimestamp := time.Now().UTC().UnixNano() - defer setNow(func() (t int64) { - return wantTimestamp - })() - - err := db.NewSetter(ModeSetAccess).Set(chunk.Address()) - if err != nil { - t.Fatal(err) - } - - t.Run("pull index", newPullIndexTest(db, chunk, wantTimestamp, nil)) - - t.Run("pull index count", newItemsCountTest(db.pullIndex, 1)) - - t.Run("gc index", newGCIndexTest(db, chunk, wantTimestamp, wantTimestamp)) - - t.Run("gc index count", newItemsCountTest(db.gcIndex, 1)) - - t.Run("gc size", newIndexGCSizeTest(db)) -} - -// TestModeSetSync validates ModeSetSync index values on the provided DB. -func TestModeSetSync(t *testing.T) { - db, cleanupFunc := newTestDB(t, nil) - defer cleanupFunc() - - chunk := generateRandomChunk() - - wantTimestamp := time.Now().UTC().UnixNano() - defer setNow(func() (t int64) { - return wantTimestamp - })() - - err := db.NewPutter(ModePutUpload).Put(chunk) - if err != nil { - t.Fatal(err) - } - - err = db.NewSetter(ModeSetSync).Set(chunk.Address()) - if err != nil { - t.Fatal(err) - } - - t.Run("retrieve indexes", newRetrieveIndexesTestWithAccess(db, chunk, wantTimestamp, wantTimestamp)) - - t.Run("push index", newPushIndexTest(db, chunk, wantTimestamp, leveldb.ErrNotFound)) - - t.Run("gc index", newGCIndexTest(db, chunk, wantTimestamp, wantTimestamp)) - - t.Run("gc index count", newItemsCountTest(db.gcIndex, 1)) - - t.Run("gc size", newIndexGCSizeTest(db)) -} - -// TestModeSetRemove validates ModeSetRemove index values on the provided DB. -func TestModeSetRemove(t *testing.T) { - db, cleanupFunc := newTestDB(t, nil) - defer cleanupFunc() - - chunk := generateRandomChunk() - - err := db.NewPutter(ModePutUpload).Put(chunk) - if err != nil { - t.Fatal(err) - } - - err = db.NewSetter(modeSetRemove).Set(chunk.Address()) - if err != nil { - t.Fatal(err) - } - - t.Run("retrieve indexes", func(t *testing.T) { - wantErr := leveldb.ErrNotFound - _, err := db.retrievalDataIndex.Get(addressToItem(chunk.Address())) - if err != wantErr { - t.Errorf("got error %v, want %v", err, wantErr) - } - t.Run("retrieve data index count", newItemsCountTest(db.retrievalDataIndex, 0)) - - // access index should not be set - _, err = db.retrievalAccessIndex.Get(addressToItem(chunk.Address())) - if err != wantErr { - t.Errorf("got error %v, want %v", err, wantErr) - } - t.Run("retrieve access index count", newItemsCountTest(db.retrievalAccessIndex, 0)) - }) - - t.Run("pull index", newPullIndexTest(db, chunk, 0, leveldb.ErrNotFound)) - - t.Run("pull index count", newItemsCountTest(db.pullIndex, 0)) - - t.Run("gc index count", newItemsCountTest(db.gcIndex, 0)) - - t.Run("gc size", newIndexGCSizeTest(db)) - -} diff --git a/swarm/storage/localstore/retrieval_index_test.go b/swarm/storage/localstore/retrieval_index_test.go deleted file mode 100644 index a184a354923e..000000000000 --- a/swarm/storage/localstore/retrieval_index_test.go +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package localstore - -import ( - "strconv" - "testing" - - "github.com/ubiq/go-ubiq/swarm/storage" -) - -// BenchmarkRetrievalIndexes uploads a number of chunks in order to measure -// total time of updating their retrieval indexes by setting them -// to synced state and requesting them. -// -// This benchmark takes significant amount of time. -// -// Measurements on MacBook Pro (Retina, 15-inch, Mid 2014) show -// that two separated indexes perform better. -// -// # go test -benchmem -run=none github.com/ubiq/go-ubiq/swarm/storage/localstore -bench BenchmarkRetrievalIndexes -v -// goos: darwin -// goarch: amd64 -// pkg: github.com/ubiq/go-ubiq/swarm/storage/localstore -// BenchmarkRetrievalIndexes/1000-8 20 75556686 ns/op 19033493 B/op 84500 allocs/op -// BenchmarkRetrievalIndexes/10000-8 1 1079084922 ns/op 382792064 B/op 1429644 allocs/op -// BenchmarkRetrievalIndexes/100000-8 1 16891305737 ns/op 2629165304 B/op 12465019 allocs/op -// PASS -func BenchmarkRetrievalIndexes(b *testing.B) { - for _, count := range []int{ - 1000, - 10000, - 100000, - } { - b.Run(strconv.Itoa(count)+"-split", func(b *testing.B) { - for n := 0; n < b.N; n++ { - benchmarkRetrievalIndexes(b, nil, count) - } - }) - } -} - -// benchmarkRetrievalIndexes is used in BenchmarkRetrievalIndexes -// to do benchmarks with a specific number of chunks and different -// database options. -func benchmarkRetrievalIndexes(b *testing.B, o *Options, count int) { - b.StopTimer() - db, cleanupFunc := newTestDB(b, o) - defer cleanupFunc() - uploader := db.NewPutter(ModePutUpload) - syncer := db.NewSetter(ModeSetSync) - requester := db.NewGetter(ModeGetRequest) - addrs := make([]storage.Address, count) - for i := 0; i < count; i++ { - chunk := generateFakeRandomChunk() - err := uploader.Put(chunk) - if err != nil { - b.Fatal(err) - } - addrs[i] = chunk.Address() - } - // set update gc test hook to signal when - // update gc goroutine is done by sending to - // testHookUpdateGCChan channel, which is - // used to wait for gc index updates to be - // included in the benchmark time - testHookUpdateGCChan := make(chan struct{}) - defer setTestHookUpdateGC(func() { - testHookUpdateGCChan <- struct{}{} - })() - b.StartTimer() - - for i := 0; i < count; i++ { - err := syncer.Set(addrs[i]) - if err != nil { - b.Fatal(err) - } - - _, err = requester.Get(addrs[i]) - if err != nil { - b.Fatal(err) - } - // wait for update gc goroutine to be done - <-testHookUpdateGCChan - } -} - -// BenchmarkUpload compares uploading speed for different -// retrieval indexes and various number of chunks. -// -// Measurements on MacBook Pro (Retina, 15-inch, Mid 2014). -// -// go test -benchmem -run=none github.com/ubiq/go-ubiq/swarm/storage/localstore -bench BenchmarkUpload -v -// goos: darwin -// goarch: amd64 -// pkg: github.com/ubiq/go-ubiq/swarm/storage/localstore -// BenchmarkUpload/1000-8 20 59437463 ns/op 25205193 B/op 23208 allocs/op -// BenchmarkUpload/10000-8 2 580646362 ns/op 216532932 B/op 248090 allocs/op -// BenchmarkUpload/100000-8 1 22373390892 ns/op 2323055312 B/op 3995903 allocs/op -// PASS -func BenchmarkUpload(b *testing.B) { - for _, count := range []int{ - 1000, - 10000, - 100000, - } { - b.Run(strconv.Itoa(count), func(b *testing.B) { - for n := 0; n < b.N; n++ { - benchmarkUpload(b, nil, count) - } - }) - } -} - -// benchmarkUpload is used in BenchmarkUpload -// to do benchmarks with a specific number of chunks and different -// database options. -func benchmarkUpload(b *testing.B, o *Options, count int) { - b.StopTimer() - db, cleanupFunc := newTestDB(b, o) - defer cleanupFunc() - uploader := db.NewPutter(ModePutUpload) - chunks := make([]storage.Chunk, count) - for i := 0; i < count; i++ { - chunk := generateFakeRandomChunk() - chunks[i] = chunk - } - b.StartTimer() - - for i := 0; i < count; i++ { - err := uploader.Put(chunks[i]) - if err != nil { - b.Fatal(err) - } - } -} diff --git a/swarm/storage/localstore/subscription_pull.go b/swarm/storage/localstore/subscription_pull.go deleted file mode 100644 index a61741ccfad1..000000000000 --- a/swarm/storage/localstore/subscription_pull.go +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package localstore - -import ( - "bytes" - "context" - "errors" - "fmt" - "sync" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/swarm/shed" - "github.com/ubiq/go-ubiq/swarm/storage" -) - -// SubscribePull returns a channel that provides chunk addresses and stored times from pull syncing index. -// Pull syncing index can be only subscribed to a particular proximity order bin. If since -// is not nil, the iteration will start from the first item stored after that timestamp. If until is not nil, -// only chunks stored up to this timestamp will be send to the channel, and the returned channel will be -// closed. The since-until interval is open on the left and closed on the right (since,until]. Returned stop -// function will terminate current and further iterations without errors, and also close the returned channel. -// Make sure that you check the second returned parameter from the channel to stop iteration when its value -// is false. -func (db *DB) SubscribePull(ctx context.Context, bin uint8, since, until *ChunkDescriptor) (c <-chan ChunkDescriptor, stop func()) { - chunkDescriptors := make(chan ChunkDescriptor) - trigger := make(chan struct{}, 1) - - db.pullTriggersMu.Lock() - if _, ok := db.pullTriggers[bin]; !ok { - db.pullTriggers[bin] = make([]chan struct{}, 0) - } - db.pullTriggers[bin] = append(db.pullTriggers[bin], trigger) - db.pullTriggersMu.Unlock() - - // send signal for the initial iteration - trigger <- struct{}{} - - stopChan := make(chan struct{}) - var stopChanOnce sync.Once - - // used to provide information from the iterator to - // stop subscription when until chunk descriptor is reached - var errStopSubscription = errors.New("stop subscription") - - go func() { - // close the returned ChunkDescriptor channel at the end to - // signal that the subscription is done - defer close(chunkDescriptors) - // sinceItem is the Item from which the next iteration - // should start. The first iteration starts from the first Item. - var sinceItem *shed.Item - if since != nil { - sinceItem = &shed.Item{ - Address: since.Address, - StoreTimestamp: since.StoreTimestamp, - } - } - for { - select { - case <-trigger: - // iterate until: - // - last index Item is reached - // - subscription stop is called - // - context is done - err := db.pullIndex.Iterate(func(item shed.Item) (stop bool, err error) { - select { - case chunkDescriptors <- ChunkDescriptor{ - Address: item.Address, - StoreTimestamp: item.StoreTimestamp, - }: - // until chunk descriptor is sent - // break the iteration - if until != nil && - (item.StoreTimestamp >= until.StoreTimestamp || - bytes.Equal(item.Address, until.Address)) { - return true, errStopSubscription - } - // set next iteration start item - // when its chunk is successfully sent to channel - sinceItem = &item - return false, nil - case <-stopChan: - // gracefully stop the iteration - // on stop - return true, nil - case <-db.close: - // gracefully stop the iteration - // on database close - return true, nil - case <-ctx.Done(): - return true, ctx.Err() - } - }, &shed.IterateOptions{ - StartFrom: sinceItem, - // sinceItem was sent as the last Address in the previous - // iterator call, skip it in this one - SkipStartFromItem: true, - Prefix: []byte{bin}, - }) - if err != nil { - if err == errStopSubscription { - // stop subscription without any errors - // if until is reached - return - } - log.Error("localstore pull subscription iteration", "bin", bin, "since", since, "until", until, "err", err) - return - } - case <-stopChan: - // terminate the subscription - // on stop - return - case <-db.close: - // terminate the subscription - // on database close - return - case <-ctx.Done(): - err := ctx.Err() - if err != nil { - log.Error("localstore pull subscription", "bin", bin, "since", since, "until", until, "err", err) - } - return - } - } - }() - - stop = func() { - stopChanOnce.Do(func() { - close(stopChan) - }) - - db.pullTriggersMu.Lock() - defer db.pullTriggersMu.Unlock() - - for i, t := range db.pullTriggers[bin] { - if t == trigger { - db.pullTriggers[bin] = append(db.pullTriggers[bin][:i], db.pullTriggers[bin][i+1:]...) - break - } - } - } - - return chunkDescriptors, stop -} - -// ChunkDescriptor holds information required for Pull syncing. This struct -// is provided by subscribing to pull index. -type ChunkDescriptor struct { - Address storage.Address - StoreTimestamp int64 -} - -func (c *ChunkDescriptor) String() string { - if c == nil { - return "none" - } - return fmt.Sprintf("%s stored at %v", c.Address.Hex(), c.StoreTimestamp) -} - -// triggerPullSubscriptions is used internally for starting iterations -// on Pull subscriptions for a particular bin. When new item with address -// that is in particular bin for DB's baseKey is added to pull index -// this function should be called. -func (db *DB) triggerPullSubscriptions(bin uint8) { - db.pullTriggersMu.RLock() - triggers, ok := db.pullTriggers[bin] - db.pullTriggersMu.RUnlock() - if !ok { - return - } - - for _, t := range triggers { - select { - case t <- struct{}{}: - default: - } - } -} diff --git a/swarm/storage/localstore/subscription_pull_test.go b/swarm/storage/localstore/subscription_pull_test.go deleted file mode 100644 index a902ab250826..000000000000 --- a/swarm/storage/localstore/subscription_pull_test.go +++ /dev/null @@ -1,478 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package localstore - -import ( - "bytes" - "context" - "fmt" - "sync" - "testing" - "time" - - "github.com/ubiq/go-ubiq/swarm/storage" -) - -// TestDB_SubscribePull uploads some chunks before and after -// pull syncing subscription is created and validates if -// all addresses are received in the right order -// for expected proximity order bins. -func TestDB_SubscribePull(t *testing.T) { - db, cleanupFunc := newTestDB(t, nil) - defer cleanupFunc() - - uploader := db.NewPutter(ModePutUpload) - - addrs := make(map[uint8][]storage.Address) - var addrsMu sync.Mutex - var wantedChunksCount int - - // prepopulate database with some chunks - // before the subscription - uploadRandomChunksBin(t, db, uploader, addrs, &addrsMu, &wantedChunksCount, 10) - - // set a timeout on subscription - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - // collect all errors from validating addresses, even nil ones - // to validate the number of addresses received by the subscription - errChan := make(chan error) - - for bin := uint8(0); bin <= uint8(storage.MaxPO); bin++ { - ch, stop := db.SubscribePull(ctx, bin, nil, nil) - defer stop() - - // receive and validate addresses from the subscription - go readPullSubscriptionBin(ctx, bin, ch, addrs, &addrsMu, errChan) - } - - // upload some chunks just after subscribe - uploadRandomChunksBin(t, db, uploader, addrs, &addrsMu, &wantedChunksCount, 5) - - time.Sleep(200 * time.Millisecond) - - // upload some chunks after some short time - // to ensure that subscription will include them - // in a dynamic environment - uploadRandomChunksBin(t, db, uploader, addrs, &addrsMu, &wantedChunksCount, 3) - - checkErrChan(ctx, t, errChan, wantedChunksCount) -} - -// TestDB_SubscribePull_multiple uploads chunks before and after -// multiple pull syncing subscriptions are created and -// validates if all addresses are received in the right order -// for expected proximity order bins. -func TestDB_SubscribePull_multiple(t *testing.T) { - db, cleanupFunc := newTestDB(t, nil) - defer cleanupFunc() - - uploader := db.NewPutter(ModePutUpload) - - addrs := make(map[uint8][]storage.Address) - var addrsMu sync.Mutex - var wantedChunksCount int - - // prepopulate database with some chunks - // before the subscription - uploadRandomChunksBin(t, db, uploader, addrs, &addrsMu, &wantedChunksCount, 10) - - // set a timeout on subscription - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - // collect all errors from validating addresses, even nil ones - // to validate the number of addresses received by the subscription - errChan := make(chan error) - - subsCount := 10 - - // start a number of subscriptions - // that all of them will write every address error to errChan - for j := 0; j < subsCount; j++ { - for bin := uint8(0); bin <= uint8(storage.MaxPO); bin++ { - ch, stop := db.SubscribePull(ctx, bin, nil, nil) - defer stop() - - // receive and validate addresses from the subscription - go readPullSubscriptionBin(ctx, bin, ch, addrs, &addrsMu, errChan) - } - } - - // upload some chunks just after subscribe - uploadRandomChunksBin(t, db, uploader, addrs, &addrsMu, &wantedChunksCount, 5) - - time.Sleep(200 * time.Millisecond) - - // upload some chunks after some short time - // to ensure that subscription will include them - // in a dynamic environment - uploadRandomChunksBin(t, db, uploader, addrs, &addrsMu, &wantedChunksCount, 3) - - checkErrChan(ctx, t, errChan, wantedChunksCount*subsCount) -} - -// TestDB_SubscribePull_since uploads chunks before and after -// pull syncing subscriptions are created with a since argument -// and validates if all expected addresses are received in the -// right order for expected proximity order bins. -func TestDB_SubscribePull_since(t *testing.T) { - db, cleanupFunc := newTestDB(t, nil) - defer cleanupFunc() - - uploader := db.NewPutter(ModePutUpload) - - addrs := make(map[uint8][]storage.Address) - var addrsMu sync.Mutex - var wantedChunksCount int - - lastTimestamp := time.Now().UTC().UnixNano() - var lastTimestampMu sync.RWMutex - defer setNow(func() (t int64) { - lastTimestampMu.Lock() - defer lastTimestampMu.Unlock() - lastTimestamp++ - return lastTimestamp - })() - - uploadRandomChunks := func(count int, wanted bool) (last map[uint8]ChunkDescriptor) { - last = make(map[uint8]ChunkDescriptor) - for i := 0; i < count; i++ { - chunk := generateRandomChunk() - - err := uploader.Put(chunk) - if err != nil { - t.Fatal(err) - } - - bin := db.po(chunk.Address()) - - addrsMu.Lock() - if _, ok := addrs[bin]; !ok { - addrs[bin] = make([]storage.Address, 0) - } - if wanted { - addrs[bin] = append(addrs[bin], chunk.Address()) - wantedChunksCount++ - } - addrsMu.Unlock() - - lastTimestampMu.RLock() - storeTimestamp := lastTimestamp - lastTimestampMu.RUnlock() - - last[bin] = ChunkDescriptor{ - Address: chunk.Address(), - StoreTimestamp: storeTimestamp, - } - } - return last - } - - // prepopulate database with some chunks - // before the subscription - last := uploadRandomChunks(30, false) - - uploadRandomChunks(25, true) - - // set a timeout on subscription - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - // collect all errors from validating addresses, even nil ones - // to validate the number of addresses received by the subscription - errChan := make(chan error) - - for bin := uint8(0); bin <= uint8(storage.MaxPO); bin++ { - var since *ChunkDescriptor - if c, ok := last[bin]; ok { - since = &c - } - ch, stop := db.SubscribePull(ctx, bin, since, nil) - defer stop() - - // receive and validate addresses from the subscription - go readPullSubscriptionBin(ctx, bin, ch, addrs, &addrsMu, errChan) - - } - - // upload some chunks just after subscribe - uploadRandomChunks(15, true) - - checkErrChan(ctx, t, errChan, wantedChunksCount) -} - -// TestDB_SubscribePull_until uploads chunks before and after -// pull syncing subscriptions are created with an until argument -// and validates if all expected addresses are received in the -// right order for expected proximity order bins. -func TestDB_SubscribePull_until(t *testing.T) { - db, cleanupFunc := newTestDB(t, nil) - defer cleanupFunc() - - uploader := db.NewPutter(ModePutUpload) - - addrs := make(map[uint8][]storage.Address) - var addrsMu sync.Mutex - var wantedChunksCount int - - lastTimestamp := time.Now().UTC().UnixNano() - var lastTimestampMu sync.RWMutex - defer setNow(func() (t int64) { - lastTimestampMu.Lock() - defer lastTimestampMu.Unlock() - lastTimestamp++ - return lastTimestamp - })() - - uploadRandomChunks := func(count int, wanted bool) (last map[uint8]ChunkDescriptor) { - last = make(map[uint8]ChunkDescriptor) - for i := 0; i < count; i++ { - chunk := generateRandomChunk() - - err := uploader.Put(chunk) - if err != nil { - t.Fatal(err) - } - - bin := db.po(chunk.Address()) - - addrsMu.Lock() - if _, ok := addrs[bin]; !ok { - addrs[bin] = make([]storage.Address, 0) - } - if wanted { - addrs[bin] = append(addrs[bin], chunk.Address()) - wantedChunksCount++ - } - addrsMu.Unlock() - - lastTimestampMu.RLock() - storeTimestamp := lastTimestamp - lastTimestampMu.RUnlock() - - last[bin] = ChunkDescriptor{ - Address: chunk.Address(), - StoreTimestamp: storeTimestamp, - } - } - return last - } - - // prepopulate database with some chunks - // before the subscription - last := uploadRandomChunks(30, true) - - uploadRandomChunks(25, false) - - // set a timeout on subscription - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - // collect all errors from validating addresses, even nil ones - // to validate the number of addresses received by the subscription - errChan := make(chan error) - - for bin := uint8(0); bin <= uint8(storage.MaxPO); bin++ { - until, ok := last[bin] - if !ok { - continue - } - ch, stop := db.SubscribePull(ctx, bin, nil, &until) - defer stop() - - // receive and validate addresses from the subscription - go readPullSubscriptionBin(ctx, bin, ch, addrs, &addrsMu, errChan) - } - - // upload some chunks just after subscribe - uploadRandomChunks(15, false) - - checkErrChan(ctx, t, errChan, wantedChunksCount) -} - -// TestDB_SubscribePull_sinceAndUntil uploads chunks before and -// after pull syncing subscriptions are created with since -// and until arguments, and validates if all expected addresses -// are received in the right order for expected proximity order bins. -func TestDB_SubscribePull_sinceAndUntil(t *testing.T) { - db, cleanupFunc := newTestDB(t, nil) - defer cleanupFunc() - - uploader := db.NewPutter(ModePutUpload) - - addrs := make(map[uint8][]storage.Address) - var addrsMu sync.Mutex - var wantedChunksCount int - - lastTimestamp := time.Now().UTC().UnixNano() - var lastTimestampMu sync.RWMutex - defer setNow(func() (t int64) { - lastTimestampMu.Lock() - defer lastTimestampMu.Unlock() - lastTimestamp++ - return lastTimestamp - })() - - uploadRandomChunks := func(count int, wanted bool) (last map[uint8]ChunkDescriptor) { - last = make(map[uint8]ChunkDescriptor) - for i := 0; i < count; i++ { - chunk := generateRandomChunk() - - err := uploader.Put(chunk) - if err != nil { - t.Fatal(err) - } - - bin := db.po(chunk.Address()) - - addrsMu.Lock() - if _, ok := addrs[bin]; !ok { - addrs[bin] = make([]storage.Address, 0) - } - if wanted { - addrs[bin] = append(addrs[bin], chunk.Address()) - wantedChunksCount++ - } - addrsMu.Unlock() - - lastTimestampMu.RLock() - storeTimestamp := lastTimestamp - lastTimestampMu.RUnlock() - - last[bin] = ChunkDescriptor{ - Address: chunk.Address(), - StoreTimestamp: storeTimestamp, - } - } - return last - } - - // all chunks from upload1 are not expected - // as upload1 chunk is used as since for subscriptions - upload1 := uploadRandomChunks(100, false) - - // all chunks from upload2 are expected - // as upload2 chunk is used as until for subscriptions - upload2 := uploadRandomChunks(100, true) - - // upload some chunks before subscribe but after - // wanted chunks - uploadRandomChunks(8, false) - - // set a timeout on subscription - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - // collect all errors from validating addresses, even nil ones - // to validate the number of addresses received by the subscription - errChan := make(chan error) - - for bin := uint8(0); bin <= uint8(storage.MaxPO); bin++ { - var since *ChunkDescriptor - if c, ok := upload1[bin]; ok { - since = &c - } - until, ok := upload2[bin] - if !ok { - // no chunks un this bin uploaded in the upload2 - // skip this bin from testing - continue - } - ch, stop := db.SubscribePull(ctx, bin, since, &until) - defer stop() - - // receive and validate addresses from the subscription - go readPullSubscriptionBin(ctx, bin, ch, addrs, &addrsMu, errChan) - } - - // upload some chunks just after subscribe - uploadRandomChunks(15, false) - - checkErrChan(ctx, t, errChan, wantedChunksCount) -} - -// uploadRandomChunksBin uploads random chunks to database and adds them to -// the map of addresses ber bin. -func uploadRandomChunksBin(t *testing.T, db *DB, uploader *Putter, addrs map[uint8][]storage.Address, addrsMu *sync.Mutex, wantedChunksCount *int, count int) { - for i := 0; i < count; i++ { - chunk := generateRandomChunk() - - err := uploader.Put(chunk) - if err != nil { - t.Fatal(err) - } - - addrsMu.Lock() - bin := db.po(chunk.Address()) - if _, ok := addrs[bin]; !ok { - addrs[bin] = make([]storage.Address, 0) - } - addrs[bin] = append(addrs[bin], chunk.Address()) - addrsMu.Unlock() - - *wantedChunksCount++ - } -} - -// readPullSubscriptionBin is a helper function that reads all ChunkDescriptors from a channel and -// sends error to errChan, even if it is nil, to count the number of ChunkDescriptors -// returned by the channel. -func readPullSubscriptionBin(ctx context.Context, bin uint8, ch <-chan ChunkDescriptor, addrs map[uint8][]storage.Address, addrsMu *sync.Mutex, errChan chan error) { - var i int // address index - for { - select { - case got, ok := <-ch: - if !ok { - return - } - addrsMu.Lock() - if i+1 > len(addrs[bin]) { - errChan <- fmt.Errorf("got more chunk addresses %v, then expected %v, for bin %v", i+1, len(addrs[bin]), bin) - } - want := addrs[bin][i] - addrsMu.Unlock() - var err error - if !bytes.Equal(got.Address, want) { - err = fmt.Errorf("got chunk address %v in bin %v %s, want %s", i, bin, got.Address.Hex(), want) - } - i++ - // send one and only one error per received address - errChan <- err - case <-ctx.Done(): - return - } - } -} - -// checkErrChan expects the number of wantedChunksCount errors from errChan -// and calls t.Error for the ones that are not nil. -func checkErrChan(ctx context.Context, t *testing.T, errChan chan error, wantedChunksCount int) { - t.Helper() - - for i := 0; i < wantedChunksCount; i++ { - select { - case err := <-errChan: - if err != nil { - t.Error(err) - } - case <-ctx.Done(): - t.Fatal(ctx.Err()) - } - } -} diff --git a/swarm/storage/localstore/subscription_push.go b/swarm/storage/localstore/subscription_push.go deleted file mode 100644 index 557ba307a858..000000000000 --- a/swarm/storage/localstore/subscription_push.go +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package localstore - -import ( - "context" - "sync" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/swarm/shed" - "github.com/ubiq/go-ubiq/swarm/storage" -) - -// SubscribePush returns a channel that provides storage chunks with ordering from push syncing index. -// Returned stop function will terminate current and further iterations, and also it will close -// the returned channel without any errors. Make sure that you check the second returned parameter -// from the channel to stop iteration when its value is false. -func (db *DB) SubscribePush(ctx context.Context) (c <-chan storage.Chunk, stop func()) { - chunks := make(chan storage.Chunk) - trigger := make(chan struct{}, 1) - - db.pushTriggersMu.Lock() - db.pushTriggers = append(db.pushTriggers, trigger) - db.pushTriggersMu.Unlock() - - // send signal for the initial iteration - trigger <- struct{}{} - - stopChan := make(chan struct{}) - var stopChanOnce sync.Once - - go func() { - // close the returned chunkInfo channel at the end to - // signal that the subscription is done - defer close(chunks) - // sinceItem is the Item from which the next iteration - // should start. The first iteration starts from the first Item. - var sinceItem *shed.Item - for { - select { - case <-trigger: - // iterate until: - // - last index Item is reached - // - subscription stop is called - // - context is done - err := db.pushIndex.Iterate(func(item shed.Item) (stop bool, err error) { - // get chunk data - dataItem, err := db.retrievalDataIndex.Get(item) - if err != nil { - return true, err - } - - select { - case chunks <- storage.NewChunk(dataItem.Address, dataItem.Data): - // set next iteration start item - // when its chunk is successfully sent to channel - sinceItem = &item - return false, nil - case <-stopChan: - // gracefully stop the iteration - // on stop - return true, nil - case <-db.close: - // gracefully stop the iteration - // on database close - return true, nil - case <-ctx.Done(): - return true, ctx.Err() - } - }, &shed.IterateOptions{ - StartFrom: sinceItem, - // sinceItem was sent as the last Address in the previous - // iterator call, skip it in this one - SkipStartFromItem: true, - }) - if err != nil { - log.Error("localstore push subscription iteration", "err", err) - return - } - case <-stopChan: - // terminate the subscription - // on stop - return - case <-db.close: - // terminate the subscription - // on database close - return - case <-ctx.Done(): - err := ctx.Err() - if err != nil { - log.Error("localstore push subscription", "err", err) - } - return - } - } - }() - - stop = func() { - stopChanOnce.Do(func() { - close(stopChan) - }) - - db.pushTriggersMu.Lock() - defer db.pushTriggersMu.Unlock() - - for i, t := range db.pushTriggers { - if t == trigger { - db.pushTriggers = append(db.pushTriggers[:i], db.pushTriggers[i+1:]...) - break - } - } - } - - return chunks, stop -} - -// triggerPushSubscriptions is used internally for starting iterations -// on Push subscriptions. Whenever new item is added to the push index, -// this function should be called. -func (db *DB) triggerPushSubscriptions() { - db.pushTriggersMu.RLock() - triggers := db.pushTriggers - db.pushTriggersMu.RUnlock() - - for _, t := range triggers { - select { - case t <- struct{}{}: - default: - } - } -} diff --git a/swarm/storage/localstore/subscription_push_test.go b/swarm/storage/localstore/subscription_push_test.go deleted file mode 100644 index 4e31a767f567..000000000000 --- a/swarm/storage/localstore/subscription_push_test.go +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright 2019 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package localstore - -import ( - "bytes" - "context" - "fmt" - "sync" - "testing" - "time" - - "github.com/ubiq/go-ubiq/swarm/storage" -) - -// TestDB_SubscribePush uploads some chunks before and after -// push syncing subscription is created and validates if -// all addresses are received in the right order. -func TestDB_SubscribePush(t *testing.T) { - db, cleanupFunc := newTestDB(t, nil) - defer cleanupFunc() - - uploader := db.NewPutter(ModePutUpload) - - chunks := make([]storage.Chunk, 0) - var chunksMu sync.Mutex - - uploadRandomChunks := func(count int) { - for i := 0; i < count; i++ { - chunk := generateRandomChunk() - - err := uploader.Put(chunk) - if err != nil { - t.Fatal(err) - } - - chunksMu.Lock() - chunks = append(chunks, chunk) - chunksMu.Unlock() - } - } - - // prepopulate database with some chunks - // before the subscription - uploadRandomChunks(10) - - // set a timeout on subscription - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - // collect all errors from validating addresses, even nil ones - // to validate the number of addresses received by the subscription - errChan := make(chan error) - - ch, stop := db.SubscribePush(ctx) - defer stop() - - // receive and validate addresses from the subscription - go func() { - var i int // address index - for { - select { - case got, ok := <-ch: - if !ok { - return - } - chunksMu.Lock() - want := chunks[i] - chunksMu.Unlock() - var err error - if !bytes.Equal(got.Data(), want.Data()) { - err = fmt.Errorf("got chunk %v data %x, want %x", i, got.Data(), want.Data()) - } - if !bytes.Equal(got.Address(), want.Address()) { - err = fmt.Errorf("got chunk %v address %s, want %s", i, got.Address().Hex(), want.Address().Hex()) - } - i++ - // send one and only one error per received address - errChan <- err - case <-ctx.Done(): - return - } - } - }() - - // upload some chunks just after subscribe - uploadRandomChunks(5) - - time.Sleep(200 * time.Millisecond) - - // upload some chunks after some short time - // to ensure that subscription will include them - // in a dynamic environment - uploadRandomChunks(3) - - checkErrChan(ctx, t, errChan, len(chunks)) -} - -// TestDB_SubscribePush_multiple uploads chunks before and after -// multiple push syncing subscriptions are created and -// validates if all addresses are received in the right order. -func TestDB_SubscribePush_multiple(t *testing.T) { - db, cleanupFunc := newTestDB(t, nil) - defer cleanupFunc() - - uploader := db.NewPutter(ModePutUpload) - - addrs := make([]storage.Address, 0) - var addrsMu sync.Mutex - - uploadRandomChunks := func(count int) { - for i := 0; i < count; i++ { - chunk := generateRandomChunk() - - err := uploader.Put(chunk) - if err != nil { - t.Fatal(err) - } - - addrsMu.Lock() - addrs = append(addrs, chunk.Address()) - addrsMu.Unlock() - } - } - - // prepopulate database with some chunks - // before the subscription - uploadRandomChunks(10) - - // set a timeout on subscription - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - // collect all errors from validating addresses, even nil ones - // to validate the number of addresses received by the subscription - errChan := make(chan error) - - subsCount := 10 - - // start a number of subscriptions - // that all of them will write every addresses error to errChan - for j := 0; j < subsCount; j++ { - ch, stop := db.SubscribePush(ctx) - defer stop() - - // receive and validate addresses from the subscription - go func(j int) { - var i int // address index - for { - select { - case got, ok := <-ch: - if !ok { - return - } - addrsMu.Lock() - want := addrs[i] - addrsMu.Unlock() - var err error - if !bytes.Equal(got.Address(), want) { - err = fmt.Errorf("got chunk %v address on subscription %v %s, want %s", i, j, got, want) - } - i++ - // send one and only one error per received address - errChan <- err - case <-ctx.Done(): - return - } - } - }(j) - } - - // upload some chunks just after subscribe - uploadRandomChunks(5) - - time.Sleep(200 * time.Millisecond) - - // upload some chunks after some short time - // to ensure that subscription will include them - // in a dynamic environment - uploadRandomChunks(3) - - // number of addresses received by all subscriptions - wantedChunksCount := len(addrs) * subsCount - - checkErrChan(ctx, t, errChan, wantedChunksCount) -} diff --git a/swarm/storage/localstore_test.go b/swarm/storage/localstore_test.go deleted file mode 100644 index 54aba32f3591..000000000000 --- a/swarm/storage/localstore_test.go +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package storage - -import ( - "context" - "io/ioutil" - "os" - "testing" - "time" - - ch "github.com/ubiq/go-ubiq/swarm/chunk" -) - -var ( - hashfunc = MakeHashFunc(DefaultHash) -) - -// tests that the content address validator correctly checks the data -// tests that feed update chunks are passed through content address validator -// the test checking the resouce update validator internal correctness is found in storage/feeds/handler_test.go -func TestValidator(t *testing.T) { - // set up localstore - datadir, err := ioutil.TempDir("", "storage-testvalidator") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(datadir) - - params := NewDefaultLocalStoreParams() - params.Init(datadir) - store, err := NewLocalStore(params, nil) - if err != nil { - t.Fatal(err) - } - - // check puts with no validators, both succeed - chunks := GenerateRandomChunks(259, 2) - goodChunk := chunks[0] - badChunk := chunks[1] - copy(badChunk.Data(), goodChunk.Data()) - - errs := putChunks(store, goodChunk, badChunk) - if errs[0] != nil { - t.Fatalf("expected no error on good content address chunk in spite of no validation, but got: %s", err) - } - if errs[1] != nil { - t.Fatalf("expected no error on bad content address chunk in spite of no validation, but got: %s", err) - } - - // add content address validator and check puts - // bad should fail, good should pass - store.Validators = append(store.Validators, NewContentAddressValidator(hashfunc)) - chunks = GenerateRandomChunks(ch.DefaultSize, 2) - goodChunk = chunks[0] - badChunk = chunks[1] - copy(badChunk.Data(), goodChunk.Data()) - - errs = putChunks(store, goodChunk, badChunk) - if errs[0] != nil { - t.Fatalf("expected no error on good content address chunk with content address validator only, but got: %s", err) - } - if errs[1] == nil { - t.Fatal("expected error on bad content address chunk with content address validator only, but got nil") - } - - // append a validator that always denies - // bad should fail, good should pass, - var negV boolTestValidator - store.Validators = append(store.Validators, negV) - - chunks = GenerateRandomChunks(ch.DefaultSize, 2) - goodChunk = chunks[0] - badChunk = chunks[1] - copy(badChunk.Data(), goodChunk.Data()) - - errs = putChunks(store, goodChunk, badChunk) - if errs[0] != nil { - t.Fatalf("expected no error on good content address chunk with content address validator only, but got: %s", err) - } - if errs[1] == nil { - t.Fatal("expected error on bad content address chunk with content address validator only, but got nil") - } - - // append a validator that always approves - // all shall pass - var posV boolTestValidator = true - store.Validators = append(store.Validators, posV) - - chunks = GenerateRandomChunks(ch.DefaultSize, 2) - goodChunk = chunks[0] - badChunk = chunks[1] - copy(badChunk.Data(), goodChunk.Data()) - - errs = putChunks(store, goodChunk, badChunk) - if errs[0] != nil { - t.Fatalf("expected no error on good content address chunk with content address validator only, but got: %s", err) - } - if errs[1] != nil { - t.Fatalf("expected no error on bad content address chunk in spite of no validation, but got: %s", err) - } - -} - -type boolTestValidator bool - -func (self boolTestValidator) Validate(chunk Chunk) bool { - return bool(self) -} - -// putChunks adds chunks to localstore -// It waits for receive on the stored channel -// It logs but does not fail on delivery error -func putChunks(store *LocalStore, chunks ...Chunk) []error { - i := 0 - f := func(n int64) Chunk { - chunk := chunks[i] - i++ - return chunk - } - _, errs := put(store, len(chunks), f) - return errs -} - -func put(store *LocalStore, n int, f func(i int64) Chunk) (hs []Address, errs []error) { - for i := int64(0); i < int64(n); i++ { - chunk := f(ch.DefaultSize) - err := store.Put(context.TODO(), chunk) - errs = append(errs, err) - hs = append(hs, chunk.Address()) - } - return hs, errs -} - -// TestGetFrequentlyAccessedChunkWontGetGarbageCollected tests that the most -// frequently accessed chunk is not garbage collected from LDBStore, i.e., -// from disk when we are at the capacity and garbage collector runs. For that -// we start putting random chunks into the DB while continuously accessing the -// chunk we care about then check if we can still retrieve it from disk. -func TestGetFrequentlyAccessedChunkWontGetGarbageCollected(t *testing.T) { - ldbCap := defaultGCRatio - store, cleanup := setupLocalStore(t, ldbCap) - defer cleanup() - - var chunks []Chunk - for i := 0; i < ldbCap; i++ { - chunks = append(chunks, GenerateRandomChunk(ch.DefaultSize)) - } - - mostAccessed := chunks[0].Address() - for _, chunk := range chunks { - if err := store.Put(context.Background(), chunk); err != nil { - t.Fatal(err) - } - - if _, err := store.Get(context.Background(), mostAccessed); err != nil { - t.Fatal(err) - } - // Add time for MarkAccessed() to be able to finish in a separate Goroutine - time.Sleep(1 * time.Millisecond) - } - - store.DbStore.collectGarbage() - if _, err := store.DbStore.Get(context.Background(), mostAccessed); err != nil { - t.Logf("most frequntly accessed chunk not found on disk (key: %v)", mostAccessed) - t.Fatal(err) - } - -} - -func setupLocalStore(t *testing.T, ldbCap int) (ls *LocalStore, cleanup func()) { - t.Helper() - - var err error - datadir, err := ioutil.TempDir("", "storage") - if err != nil { - t.Fatal(err) - } - - params := &LocalStoreParams{ - StoreParams: NewStoreParams(uint64(ldbCap), uint(ldbCap), nil, nil), - } - params.Init(datadir) - - store, err := NewLocalStore(params, nil) - if err != nil { - _ = os.RemoveAll(datadir) - t.Fatal(err) - } - - cleanup = func() { - store.Close() - _ = os.RemoveAll(datadir) - } - - return store, cleanup -} - -func TestHas(t *testing.T) { - ldbCap := defaultGCRatio - store, cleanup := setupLocalStore(t, ldbCap) - defer cleanup() - - nonStoredAddr := GenerateRandomChunk(128).Address() - - has := store.Has(context.Background(), nonStoredAddr) - if has { - t.Fatal("Expected Has() to return false, but returned true!") - } - - storeChunks := GenerateRandomChunks(128, 3) - for _, ch := range storeChunks { - err := store.Put(context.Background(), ch) - if err != nil { - t.Fatalf("Expected store to store chunk, but it failed: %v", err) - } - - has := store.Has(context.Background(), ch.Address()) - if !has { - t.Fatal("Expected Has() to return true, but returned false!") - } - } - - //let's be paranoic and test again that the non-existent chunk returns false - has = store.Has(context.Background(), nonStoredAddr) - if has { - t.Fatal("Expected Has() to return false, but returned true!") - } - -} diff --git a/swarm/storage/memstore.go b/swarm/storage/memstore.go deleted file mode 100644 index 611ac3bc51da..000000000000 --- a/swarm/storage/memstore.go +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// memory storage layer for the package blockhash - -package storage - -import ( - "context" - - lru "github.com/hashicorp/golang-lru" -) - -type MemStore struct { - cache *lru.Cache - disabled bool -} - -//NewMemStore is instantiating a MemStore cache keeping all frequently requested -//chunks in the `cache` LRU cache. -func NewMemStore(params *StoreParams, _ *LDBStore) (m *MemStore) { - if params.CacheCapacity == 0 { - return &MemStore{ - disabled: true, - } - } - - c, err := lru.New(int(params.CacheCapacity)) - if err != nil { - panic(err) - } - - return &MemStore{ - cache: c, - } -} - -// Has needed to implement SyncChunkStore -func (m *MemStore) Has(_ context.Context, addr Address) bool { - return m.cache.Contains(addr) -} - -func (m *MemStore) Get(_ context.Context, addr Address) (Chunk, error) { - if m.disabled { - return nil, ErrChunkNotFound - } - - c, ok := m.cache.Get(string(addr)) - if !ok { - return nil, ErrChunkNotFound - } - return c.(Chunk), nil -} - -func (m *MemStore) Put(_ context.Context, c Chunk) error { - if m.disabled { - return nil - } - - m.cache.Add(string(c.Address()), c) - return nil -} - -func (m *MemStore) setCapacity(n int) { - if n <= 0 { - m.disabled = true - } else { - c, err := lru.New(n) - if err != nil { - panic(err) - } - - *m = MemStore{ - cache: c, - } - } -} - -func (s *MemStore) Close() {} diff --git a/swarm/storage/memstore_test.go b/swarm/storage/memstore_test.go deleted file mode 100644 index c28857479bc5..000000000000 --- a/swarm/storage/memstore_test.go +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package storage - -import ( - "context" - "testing" - - "github.com/ubiq/go-ubiq/swarm/log" -) - -func newTestMemStore() *MemStore { - storeparams := NewDefaultStoreParams() - return NewMemStore(storeparams, nil) -} - -func testMemStoreRandom(n int, t *testing.T) { - m := newTestMemStore() - defer m.Close() - testStoreRandom(m, n, t) -} - -func testMemStoreCorrect(n int, t *testing.T) { - m := newTestMemStore() - defer m.Close() - testStoreCorrect(m, n, t) -} - -func TestMemStoreRandom_1(t *testing.T) { - testMemStoreRandom(1, t) -} - -func TestMemStoreCorrect_1(t *testing.T) { - testMemStoreCorrect(1, t) -} - -func TestMemStoreRandom_1k(t *testing.T) { - testMemStoreRandom(1000, t) -} - -func TestMemStoreCorrect_1k(t *testing.T) { - testMemStoreCorrect(100, t) -} - -func TestMemStoreNotFound(t *testing.T) { - m := newTestMemStore() - defer m.Close() - - _, err := m.Get(context.TODO(), ZeroAddr) - if err != ErrChunkNotFound { - t.Errorf("Expected ErrChunkNotFound, got %v", err) - } -} - -func benchmarkMemStorePut(n int, b *testing.B) { - m := newTestMemStore() - defer m.Close() - benchmarkStorePut(m, n, b) -} - -func benchmarkMemStoreGet(n int, b *testing.B) { - m := newTestMemStore() - defer m.Close() - benchmarkStoreGet(m, n, b) -} - -func BenchmarkMemStorePut_500(b *testing.B) { - benchmarkMemStorePut(500, b) -} - -func BenchmarkMemStoreGet_500(b *testing.B) { - benchmarkMemStoreGet(500, b) -} - -func TestMemStoreAndLDBStore(t *testing.T) { - ldb, cleanup := newLDBStore(t) - ldb.setCapacity(4000) - defer cleanup() - - cacheCap := 200 - memStore := NewMemStore(NewStoreParams(4000, 200, nil, nil), nil) - - tests := []struct { - n int // number of chunks to push to memStore - chunkSize int64 // size of chunk (by default in Swarm - 4096) - }{ - { - n: 1, - chunkSize: 4096, - }, - { - n: 101, - chunkSize: 4096, - }, - { - n: 501, - chunkSize: 4096, - }, - { - n: 1100, - chunkSize: 4096, - }, - } - - for i, tt := range tests { - log.Info("running test", "idx", i, "tt", tt) - var chunks []Chunk - - for i := 0; i < tt.n; i++ { - c := GenerateRandomChunk(tt.chunkSize) - chunks = append(chunks, c) - } - - for i := 0; i < tt.n; i++ { - err := ldb.Put(context.TODO(), chunks[i]) - if err != nil { - t.Fatal(err) - } - err = memStore.Put(context.TODO(), chunks[i]) - if err != nil { - t.Fatal(err) - } - - if got := memStore.cache.Len(); got > cacheCap { - t.Fatalf("expected to get cache capacity less than %v, but got %v", cacheCap, got) - } - - } - - for i := 0; i < tt.n; i++ { - _, err := memStore.Get(context.TODO(), chunks[i].Address()) - if err != nil { - if err == ErrChunkNotFound { - _, err := ldb.Get(context.TODO(), chunks[i].Address()) - if err != nil { - t.Fatalf("couldn't get chunk %v from ldb, got error: %v", i, err) - } - } else { - t.Fatalf("got error from memstore: %v", err) - } - } - } - } -} diff --git a/swarm/storage/mock/db/db.go b/swarm/storage/mock/db/db.go deleted file mode 100644 index 429ffeade14c..000000000000 --- a/swarm/storage/mock/db/db.go +++ /dev/null @@ -1,243 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Package db implements a mock store that keeps all chunk data in LevelDB database. -package db - -import ( - "archive/tar" - "bytes" - "encoding/json" - "io" - "io/ioutil" - - "github.com/syndtr/goleveldb/leveldb" - "github.com/syndtr/goleveldb/leveldb/util" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/swarm/storage/mock" -) - -// GlobalStore contains the LevelDB database that is storing -// chunk data for all swarm nodes. -// Closing the GlobalStore with Close method is required to -// release resources used by the database. -type GlobalStore struct { - db *leveldb.DB -} - -// NewGlobalStore creates a new instance of GlobalStore. -func NewGlobalStore(path string) (s *GlobalStore, err error) { - db, err := leveldb.OpenFile(path, nil) - if err != nil { - return nil, err - } - return &GlobalStore{ - db: db, - }, nil -} - -// Close releases the resources used by the underlying LevelDB. -func (s *GlobalStore) Close() error { - return s.db.Close() -} - -// NewNodeStore returns a new instance of NodeStore that retrieves and stores -// chunk data only for a node with address addr. -func (s *GlobalStore) NewNodeStore(addr common.Address) *mock.NodeStore { - return mock.NewNodeStore(addr, s) -} - -// Get returns chunk data if the chunk with key exists for node -// on address addr. -func (s *GlobalStore) Get(addr common.Address, key []byte) (data []byte, err error) { - has, err := s.db.Has(nodeDBKey(addr, key), nil) - if err != nil { - return nil, mock.ErrNotFound - } - if !has { - return nil, mock.ErrNotFound - } - data, err = s.db.Get(dataDBKey(key), nil) - if err == leveldb.ErrNotFound { - err = mock.ErrNotFound - } - return -} - -// Put saves the chunk data for node with address addr. -func (s *GlobalStore) Put(addr common.Address, key []byte, data []byte) error { - batch := new(leveldb.Batch) - batch.Put(nodeDBKey(addr, key), nil) - batch.Put(dataDBKey(key), data) - return s.db.Write(batch, nil) -} - -// Delete removes the chunk reference to node with address addr. -func (s *GlobalStore) Delete(addr common.Address, key []byte) error { - batch := new(leveldb.Batch) - batch.Delete(nodeDBKey(addr, key)) - return s.db.Write(batch, nil) -} - -// HasKey returns whether a node with addr contains the key. -func (s *GlobalStore) HasKey(addr common.Address, key []byte) bool { - has, err := s.db.Has(nodeDBKey(addr, key), nil) - if err != nil { - has = false - } - return has -} - -// Import reads tar archive from a reader that contains exported chunk data. -// It returns the number of chunks imported and an error. -func (s *GlobalStore) Import(r io.Reader) (n int, err error) { - tr := tar.NewReader(r) - - for { - hdr, err := tr.Next() - if err != nil { - if err == io.EOF { - break - } - return n, err - } - - data, err := ioutil.ReadAll(tr) - if err != nil { - return n, err - } - - var c mock.ExportedChunk - if err = json.Unmarshal(data, &c); err != nil { - return n, err - } - - batch := new(leveldb.Batch) - for _, addr := range c.Addrs { - batch.Put(nodeDBKeyHex(addr, hdr.Name), nil) - } - - batch.Put(dataDBKey(common.Hex2Bytes(hdr.Name)), c.Data) - if err = s.db.Write(batch, nil); err != nil { - return n, err - } - - n++ - } - return n, err -} - -// Export writes to a writer a tar archive with all chunk data from -// the store. It returns the number fo chunks exported and an error. -func (s *GlobalStore) Export(w io.Writer) (n int, err error) { - tw := tar.NewWriter(w) - defer tw.Close() - - buf := bytes.NewBuffer(make([]byte, 0, 1024)) - encoder := json.NewEncoder(buf) - - iter := s.db.NewIterator(util.BytesPrefix(nodeKeyPrefix), nil) - defer iter.Release() - - var currentKey string - var addrs []common.Address - - saveChunk := func(hexKey string) error { - key := common.Hex2Bytes(hexKey) - - data, err := s.db.Get(dataDBKey(key), nil) - if err != nil { - return err - } - - buf.Reset() - if err = encoder.Encode(mock.ExportedChunk{ - Addrs: addrs, - Data: data, - }); err != nil { - return err - } - - d := buf.Bytes() - hdr := &tar.Header{ - Name: hexKey, - Mode: 0644, - Size: int64(len(d)), - } - if err := tw.WriteHeader(hdr); err != nil { - return err - } - if _, err := tw.Write(d); err != nil { - return err - } - n++ - return nil - } - - for iter.Next() { - k := bytes.TrimPrefix(iter.Key(), nodeKeyPrefix) - i := bytes.Index(k, []byte("-")) - if i < 0 { - continue - } - hexKey := string(k[:i]) - - if currentKey == "" { - currentKey = hexKey - } - - if hexKey != currentKey { - if err = saveChunk(currentKey); err != nil { - return n, err - } - - addrs = addrs[:0] - } - - currentKey = hexKey - addrs = append(addrs, common.BytesToAddress(k[i:])) - } - - if len(addrs) > 0 { - if err = saveChunk(currentKey); err != nil { - return n, err - } - } - - return n, err -} - -var ( - nodeKeyPrefix = []byte("node-") - dataKeyPrefix = []byte("data-") -) - -// nodeDBKey constructs a database key for key/node mappings. -func nodeDBKey(addr common.Address, key []byte) []byte { - return nodeDBKeyHex(addr, common.Bytes2Hex(key)) -} - -// nodeDBKeyHex constructs a database key for key/node mappings -// using the hexadecimal string representation of the key. -func nodeDBKeyHex(addr common.Address, hexKey string) []byte { - return append(append(nodeKeyPrefix, []byte(hexKey+"-")...), addr[:]...) -} - -// dataDBkey constructs a database key for key/data storage. -func dataDBKey(key []byte) []byte { - return append(dataKeyPrefix, key...) -} diff --git a/swarm/storage/mock/db/db_test.go b/swarm/storage/mock/db/db_test.go deleted file mode 100644 index 1696a31f0649..000000000000 --- a/swarm/storage/mock/db/db_test.go +++ /dev/null @@ -1,75 +0,0 @@ -// +build go1.8 -// -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package db - -import ( - "io/ioutil" - "os" - "testing" - - "github.com/ubiq/go-ubiq/swarm/storage/mock/test" -) - -// TestDBStore is running a test.MockStore tests -// using test.MockStore function. -func TestDBStore(t *testing.T) { - dir, err := ioutil.TempDir("", "mock_"+t.Name()) - if err != nil { - panic(err) - } - defer os.RemoveAll(dir) - - store, err := NewGlobalStore(dir) - if err != nil { - t.Fatal(err) - } - defer store.Close() - - test.MockStore(t, store, 100) -} - -// TestImportExport is running a test.ImportExport tests -// using test.MockStore function. -func TestImportExport(t *testing.T) { - dir1, err := ioutil.TempDir("", "mock_"+t.Name()+"_exporter") - if err != nil { - panic(err) - } - defer os.RemoveAll(dir1) - - store1, err := NewGlobalStore(dir1) - if err != nil { - t.Fatal(err) - } - defer store1.Close() - - dir2, err := ioutil.TempDir("", "mock_"+t.Name()+"_importer") - if err != nil { - panic(err) - } - defer os.RemoveAll(dir2) - - store2, err := NewGlobalStore(dir2) - if err != nil { - t.Fatal(err) - } - defer store2.Close() - - test.ImportExport(t, store1, store2, 100) -} diff --git a/swarm/storage/mock/mem/mem.go b/swarm/storage/mock/mem/mem.go deleted file mode 100644 index 6cdbeae004f0..000000000000 --- a/swarm/storage/mock/mem/mem.go +++ /dev/null @@ -1,191 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Package mem implements a mock store that keeps all chunk data in memory. -// While it can be used for testing on smaller scales, the main purpose of this -// package is to provide the simplest reference implementation of a mock store. -package mem - -import ( - "archive/tar" - "bytes" - "encoding/json" - "io" - "io/ioutil" - "sync" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/swarm/storage/mock" -) - -// GlobalStore stores all chunk data and also keys and node addresses relations. -// It implements mock.GlobalStore interface. -type GlobalStore struct { - nodes map[string]map[common.Address]struct{} - data map[string][]byte - mu sync.Mutex -} - -// NewGlobalStore creates a new instance of GlobalStore. -func NewGlobalStore() *GlobalStore { - return &GlobalStore{ - nodes: make(map[string]map[common.Address]struct{}), - data: make(map[string][]byte), - } -} - -// NewNodeStore returns a new instance of NodeStore that retrieves and stores -// chunk data only for a node with address addr. -func (s *GlobalStore) NewNodeStore(addr common.Address) *mock.NodeStore { - return mock.NewNodeStore(addr, s) -} - -// Get returns chunk data if the chunk with key exists for node -// on address addr. -func (s *GlobalStore) Get(addr common.Address, key []byte) (data []byte, err error) { - s.mu.Lock() - defer s.mu.Unlock() - - if _, ok := s.nodes[string(key)][addr]; !ok { - return nil, mock.ErrNotFound - } - - data, ok := s.data[string(key)] - if !ok { - return nil, mock.ErrNotFound - } - return data, nil -} - -// Put saves the chunk data for node with address addr. -func (s *GlobalStore) Put(addr common.Address, key []byte, data []byte) error { - s.mu.Lock() - defer s.mu.Unlock() - - if _, ok := s.nodes[string(key)]; !ok { - s.nodes[string(key)] = make(map[common.Address]struct{}) - } - s.nodes[string(key)][addr] = struct{}{} - s.data[string(key)] = data - return nil -} - -// Delete removes the chunk data for node with address addr. -func (s *GlobalStore) Delete(addr common.Address, key []byte) error { - s.mu.Lock() - defer s.mu.Unlock() - - var count int - if _, ok := s.nodes[string(key)]; ok { - delete(s.nodes[string(key)], addr) - count = len(s.nodes[string(key)]) - } - if count == 0 { - delete(s.data, string(key)) - } - return nil -} - -// HasKey returns whether a node with addr contains the key. -func (s *GlobalStore) HasKey(addr common.Address, key []byte) bool { - s.mu.Lock() - defer s.mu.Unlock() - - _, ok := s.nodes[string(key)][addr] - return ok -} - -// Import reads tar archive from a reader that contains exported chunk data. -// It returns the number of chunks imported and an error. -func (s *GlobalStore) Import(r io.Reader) (n int, err error) { - s.mu.Lock() - defer s.mu.Unlock() - - tr := tar.NewReader(r) - - for { - hdr, err := tr.Next() - if err != nil { - if err == io.EOF { - break - } - return n, err - } - - data, err := ioutil.ReadAll(tr) - if err != nil { - return n, err - } - - var c mock.ExportedChunk - if err = json.Unmarshal(data, &c); err != nil { - return n, err - } - - addrs := make(map[common.Address]struct{}) - for _, a := range c.Addrs { - addrs[a] = struct{}{} - } - - key := string(common.Hex2Bytes(hdr.Name)) - s.nodes[key] = addrs - s.data[key] = c.Data - n++ - } - return n, err -} - -// Export writes to a writer a tar archive with all chunk data from -// the store. It returns the number of chunks exported and an error. -func (s *GlobalStore) Export(w io.Writer) (n int, err error) { - s.mu.Lock() - defer s.mu.Unlock() - - tw := tar.NewWriter(w) - defer tw.Close() - - buf := bytes.NewBuffer(make([]byte, 0, 1024)) - encoder := json.NewEncoder(buf) - for key, addrs := range s.nodes { - al := make([]common.Address, 0, len(addrs)) - for a := range addrs { - al = append(al, a) - } - - buf.Reset() - if err = encoder.Encode(mock.ExportedChunk{ - Addrs: al, - Data: s.data[key], - }); err != nil { - return n, err - } - - data := buf.Bytes() - hdr := &tar.Header{ - Name: common.Bytes2Hex([]byte(key)), - Mode: 0644, - Size: int64(len(data)), - } - if err := tw.WriteHeader(hdr); err != nil { - return n, err - } - if _, err := tw.Write(data); err != nil { - return n, err - } - n++ - } - return n, err -} diff --git a/swarm/storage/mock/mem/mem_test.go b/swarm/storage/mock/mem/mem_test.go deleted file mode 100644 index c08a4501c2f6..000000000000 --- a/swarm/storage/mock/mem/mem_test.go +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package mem - -import ( - "testing" - - "github.com/ubiq/go-ubiq/swarm/storage/mock/test" -) - -// TestGlobalStore is running test for a GlobalStore -// using test.MockStore function. -func TestGlobalStore(t *testing.T) { - test.MockStore(t, NewGlobalStore(), 100) -} - -// TestImportExport is running tests for importing and -// exporting data between two GlobalStores -// using test.ImportExport function. -func TestImportExport(t *testing.T) { - test.ImportExport(t, NewGlobalStore(), NewGlobalStore(), 100) -} diff --git a/swarm/storage/mock/mock.go b/swarm/storage/mock/mock.go deleted file mode 100644 index 387d884bdb41..000000000000 --- a/swarm/storage/mock/mock.go +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Package mock defines types that are used by different implementations -// of mock storages. -// -// Implementations of mock storages are located in directories -// under this package: -// -// - db - LevelDB backend -// - mem - in memory map backend -// - rpc - RPC client that can connect to other backends -// -// Mock storages can implement Importer and Exporter interfaces -// for importing and exporting all chunk data that they contain. -// The exported file is a tar archive with all files named by -// hexadecimal representations of chunk keys and with content -// with JSON-encoded ExportedChunk structure. Exported format -// should be preserved across all mock store implementations. -package mock - -import ( - "errors" - "io" - - "github.com/ubiq/go-ubiq/common" -) - -// ErrNotFound indicates that the chunk is not found. -var ErrNotFound = errors.New("not found") - -// NodeStore holds the node address and a reference to the GlobalStore -// in order to access and store chunk data only for one node. -type NodeStore struct { - store GlobalStorer - addr common.Address -} - -// NewNodeStore creates a new instance of NodeStore that keeps -// chunk data using GlobalStorer with a provided address. -func NewNodeStore(addr common.Address, store GlobalStorer) *NodeStore { - return &NodeStore{ - store: store, - addr: addr, - } -} - -// Get returns chunk data for a key for a node that has the address -// provided on NodeStore initialization. -func (n *NodeStore) Get(key []byte) (data []byte, err error) { - return n.store.Get(n.addr, key) -} - -// Put saves chunk data for a key for a node that has the address -// provided on NodeStore initialization. -func (n *NodeStore) Put(key []byte, data []byte) error { - return n.store.Put(n.addr, key, data) -} - -// Delete removes chunk data for a key for a node that has the address -// provided on NodeStore initialization. -func (n *NodeStore) Delete(key []byte) error { - return n.store.Delete(n.addr, key) -} - -// GlobalStorer defines methods for mock db store -// that stores chunk data for all swarm nodes. -// It is used in tests to construct mock NodeStores -// for swarm nodes and to track and validate chunks. -type GlobalStorer interface { - Get(addr common.Address, key []byte) (data []byte, err error) - Put(addr common.Address, key []byte, data []byte) error - Delete(addr common.Address, key []byte) error - HasKey(addr common.Address, key []byte) bool - // NewNodeStore creates an instance of NodeStore - // to be used by a single swarm node with - // address addr. - NewNodeStore(addr common.Address) *NodeStore -} - -// Importer defines method for importing mock store data -// from an exported tar archive. -type Importer interface { - Import(r io.Reader) (n int, err error) -} - -// Exporter defines method for exporting mock store data -// to a tar archive. -type Exporter interface { - Export(w io.Writer) (n int, err error) -} - -// ExportedChunk is the structure that is saved in tar archive for -// each chunk as JSON-encoded bytes. -type ExportedChunk struct { - Data []byte `json:"d"` - Addrs []common.Address `json:"a"` -} diff --git a/swarm/storage/mock/rpc/rpc.go b/swarm/storage/mock/rpc/rpc.go deleted file mode 100644 index e960b57a57cf..000000000000 --- a/swarm/storage/mock/rpc/rpc.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Package rpc implements an RPC client that connect to a centralized mock store. -// Centralazied mock store can be any other mock store implementation that is -// registered to Ethereum RPC server under mockStore name. Methods that defines -// mock.GlobalStore are the same that are used by RPC. Example: -// -// server := rpc.NewServer() -// server.RegisterName("mockStore", mem.NewGlobalStore()) -package rpc - -import ( - "fmt" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/rpc" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/storage/mock" -) - -// GlobalStore is rpc.Client that connects to a centralized mock store. -// Closing GlobalStore instance is required to release RPC client resources. -type GlobalStore struct { - client *rpc.Client -} - -// NewGlobalStore creates a new instance of GlobalStore. -func NewGlobalStore(client *rpc.Client) *GlobalStore { - return &GlobalStore{ - client: client, - } -} - -// Close closes RPC client. -func (s *GlobalStore) Close() error { - s.client.Close() - return nil -} - -// NewNodeStore returns a new instance of NodeStore that retrieves and stores -// chunk data only for a node with address addr. -func (s *GlobalStore) NewNodeStore(addr common.Address) *mock.NodeStore { - return mock.NewNodeStore(addr, s) -} - -// Get calls a Get method to RPC server. -func (s *GlobalStore) Get(addr common.Address, key []byte) (data []byte, err error) { - err = s.client.Call(&data, "mockStore_get", addr, key) - if err != nil && err.Error() == "not found" { - // pass the mock package value of error instead an rpc error - return data, mock.ErrNotFound - } - return data, err -} - -// Put calls a Put method to RPC server. -func (s *GlobalStore) Put(addr common.Address, key []byte, data []byte) error { - err := s.client.Call(nil, "mockStore_put", addr, key, data) - return err -} - -// Delete calls a Delete method to RPC server. -func (s *GlobalStore) Delete(addr common.Address, key []byte) error { - err := s.client.Call(nil, "mockStore_delete", addr, key) - return err -} - -// HasKey calls a HasKey method to RPC server. -func (s *GlobalStore) HasKey(addr common.Address, key []byte) bool { - var has bool - if err := s.client.Call(&has, "mockStore_hasKey", addr, key); err != nil { - log.Error(fmt.Sprintf("mock store HasKey: addr %s, key %064x: %v", addr, key, err)) - return false - } - return has -} diff --git a/swarm/storage/mock/rpc/rpc_test.go b/swarm/storage/mock/rpc/rpc_test.go deleted file mode 100644 index 079406c91c0b..000000000000 --- a/swarm/storage/mock/rpc/rpc_test.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package rpc - -import ( - "testing" - - "github.com/ubiq/go-ubiq/rpc" - "github.com/ubiq/go-ubiq/swarm/storage/mock/mem" - "github.com/ubiq/go-ubiq/swarm/storage/mock/test" -) - -// TestDBStore is running test for a GlobalStore -// using test.MockStore function. -func TestRPCStore(t *testing.T) { - serverStore := mem.NewGlobalStore() - - server := rpc.NewServer() - if err := server.RegisterName("mockStore", serverStore); err != nil { - t.Fatal(err) - } - - store := NewGlobalStore(rpc.DialInProc(server)) - defer store.Close() - - test.MockStore(t, store, 30) -} diff --git a/swarm/storage/mock/test/test.go b/swarm/storage/mock/test/test.go deleted file mode 100644 index 75f93332d36b..000000000000 --- a/swarm/storage/mock/test/test.go +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -// Package test provides functions that are used for testing -// GlobalStorer implementations. -package test - -import ( - "bytes" - "fmt" - "io" - "strconv" - "testing" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/ubiq/go-ubiq/swarm/storage/mock" -) - -// MockStore creates NodeStore instances from provided GlobalStorer, -// each one with a unique address, stores different chunks on them -// and checks if they are retrievable or not on all nodes. -// Attribute n defines the number of NodeStores that will be created. -func MockStore(t *testing.T, globalStore mock.GlobalStorer, n int) { - t.Run("GlobalStore", func(t *testing.T) { - addrs := make([]common.Address, n) - for i := 0; i < n; i++ { - addrs[i] = common.HexToAddress(strconv.FormatInt(int64(i)+1, 16)) - } - - for i, addr := range addrs { - chunkAddr := storage.Address(append(addr[:], []byte(strconv.FormatInt(int64(i)+1, 16))...)) - data := []byte(strconv.FormatInt(int64(i)+1, 16)) - data = append(data, make([]byte, 4096-len(data))...) - globalStore.Put(addr, chunkAddr, data) - - for _, cAddr := range addrs { - cData, err := globalStore.Get(cAddr, chunkAddr) - if cAddr == addr { - if err != nil { - t.Fatalf("get data from store %s key %s: %v", cAddr.Hex(), chunkAddr.Hex(), err) - } - if !bytes.Equal(data, cData) { - t.Fatalf("data on store %s: expected %x, got %x", cAddr.Hex(), data, cData) - } - if !globalStore.HasKey(cAddr, chunkAddr) { - t.Fatalf("expected key %s on global store for node %s, but it was not found", chunkAddr.Hex(), cAddr.Hex()) - } - } else { - if err != mock.ErrNotFound { - t.Fatalf("expected error from store %s: %v, got %v", cAddr.Hex(), mock.ErrNotFound, err) - } - if len(cData) > 0 { - t.Fatalf("data on store %s: expected nil, got %x", cAddr.Hex(), cData) - } - if globalStore.HasKey(cAddr, chunkAddr) { - t.Fatalf("not expected key %s on global store for node %s, but it was found", chunkAddr.Hex(), cAddr.Hex()) - } - } - } - } - t.Run("delete", func(t *testing.T) { - chunkAddr := storage.Address([]byte("1234567890abcd")) - for _, addr := range addrs { - err := globalStore.Put(addr, chunkAddr, []byte("data")) - if err != nil { - t.Fatalf("put data to store %s key %s: %v", addr.Hex(), chunkAddr.Hex(), err) - } - } - firstNodeAddr := addrs[0] - if err := globalStore.Delete(firstNodeAddr, chunkAddr); err != nil { - t.Fatalf("delete from store %s key %s: %v", firstNodeAddr.Hex(), chunkAddr.Hex(), err) - } - for i, addr := range addrs { - _, err := globalStore.Get(addr, chunkAddr) - if i == 0 { - if err != mock.ErrNotFound { - t.Errorf("get data from store %s key %s: expected mock.ErrNotFound error, got %v", addr.Hex(), chunkAddr.Hex(), err) - } - } else { - if err != nil { - t.Errorf("get data from store %s key %s: %v", addr.Hex(), chunkAddr.Hex(), err) - } - } - } - }) - }) - - t.Run("NodeStore", func(t *testing.T) { - nodes := make(map[common.Address]*mock.NodeStore) - for i := 0; i < n; i++ { - addr := common.HexToAddress(strconv.FormatInt(int64(i)+1, 16)) - nodes[addr] = globalStore.NewNodeStore(addr) - } - - i := 0 - for addr, store := range nodes { - i++ - chunkAddr := storage.Address(append(addr[:], []byte(fmt.Sprintf("%x", i))...)) - data := []byte(strconv.FormatInt(int64(i)+1, 16)) - data = append(data, make([]byte, 4096-len(data))...) - store.Put(chunkAddr, data) - - for cAddr, cStore := range nodes { - cData, err := cStore.Get(chunkAddr) - if cAddr == addr { - if err != nil { - t.Fatalf("get data from store %s key %s: %v", cAddr.Hex(), chunkAddr.Hex(), err) - } - if !bytes.Equal(data, cData) { - t.Fatalf("data on store %s: expected %x, got %x", cAddr.Hex(), data, cData) - } - if !globalStore.HasKey(cAddr, chunkAddr) { - t.Fatalf("expected key %s on global store for node %s, but it was not found", chunkAddr.Hex(), cAddr.Hex()) - } - } else { - if err != mock.ErrNotFound { - t.Fatalf("expected error from store %s: %v, got %v", cAddr.Hex(), mock.ErrNotFound, err) - } - if len(cData) > 0 { - t.Fatalf("data on store %s: expected nil, got %x", cAddr.Hex(), cData) - } - if globalStore.HasKey(cAddr, chunkAddr) { - t.Fatalf("not expected key %s on global store for node %s, but it was found", chunkAddr.Hex(), cAddr.Hex()) - } - } - } - } - t.Run("delete", func(t *testing.T) { - chunkAddr := storage.Address([]byte("1234567890abcd")) - var chosenStore *mock.NodeStore - for addr, store := range nodes { - if chosenStore == nil { - chosenStore = store - } - err := store.Put(chunkAddr, []byte("data")) - if err != nil { - t.Fatalf("put data to store %s key %s: %v", addr.Hex(), chunkAddr.Hex(), err) - } - } - if err := chosenStore.Delete(chunkAddr); err != nil { - t.Fatalf("delete key %s: %v", chunkAddr.Hex(), err) - } - for addr, store := range nodes { - _, err := store.Get(chunkAddr) - if store == chosenStore { - if err != mock.ErrNotFound { - t.Errorf("get data from store %s key %s: expected mock.ErrNotFound error, got %v", addr.Hex(), chunkAddr.Hex(), err) - } - } else { - if err != nil { - t.Errorf("get data from store %s key %s: %v", addr.Hex(), chunkAddr.Hex(), err) - } - } - } - }) - }) -} - -// ImportExport saves chunks to the outStore, exports them to the tar archive, -// imports tar archive to the inStore and checks if all chunks are imported correctly. -func ImportExport(t *testing.T, outStore, inStore mock.GlobalStorer, n int) { - exporter, ok := outStore.(mock.Exporter) - if !ok { - t.Fatal("outStore does not implement mock.Exporter") - } - importer, ok := inStore.(mock.Importer) - if !ok { - t.Fatal("inStore does not implement mock.Importer") - } - addrs := make([]common.Address, n) - for i := 0; i < n; i++ { - addrs[i] = common.HexToAddress(strconv.FormatInt(int64(i)+1, 16)) - } - - for i, addr := range addrs { - chunkAddr := storage.Address(append(addr[:], []byte(strconv.FormatInt(int64(i)+1, 16))...)) - data := []byte(strconv.FormatInt(int64(i)+1, 16)) - data = append(data, make([]byte, 4096-len(data))...) - outStore.Put(addr, chunkAddr, data) - } - - r, w := io.Pipe() - defer r.Close() - - exportErrChan := make(chan error) - go func() { - defer w.Close() - - _, err := exporter.Export(w) - exportErrChan <- err - }() - - if _, err := importer.Import(r); err != nil { - t.Fatalf("import: %v", err) - } - - if err := <-exportErrChan; err != nil { - t.Fatalf("export: %v", err) - } - - for i, addr := range addrs { - chunkAddr := storage.Address(append(addr[:], []byte(strconv.FormatInt(int64(i)+1, 16))...)) - data := []byte(strconv.FormatInt(int64(i)+1, 16)) - data = append(data, make([]byte, 4096-len(data))...) - for _, cAddr := range addrs { - cData, err := inStore.Get(cAddr, chunkAddr) - if cAddr == addr { - if err != nil { - t.Fatalf("get data from store %s key %s: %v", cAddr.Hex(), chunkAddr.Hex(), err) - } - if !bytes.Equal(data, cData) { - t.Fatalf("data on store %s: expected %x, got %x", cAddr.Hex(), data, cData) - } - if !inStore.HasKey(cAddr, chunkAddr) { - t.Fatalf("expected key %s on global store for node %s, but it was not found", chunkAddr.Hex(), cAddr.Hex()) - } - } else { - if err != mock.ErrNotFound { - t.Fatalf("expected error from store %s: %v, got %v", cAddr.Hex(), mock.ErrNotFound, err) - } - if len(cData) > 0 { - t.Fatalf("data on store %s: expected nil, got %x", cAddr.Hex(), cData) - } - if inStore.HasKey(cAddr, chunkAddr) { - t.Fatalf("not expected key %s on global store for node %s, but it was found", chunkAddr.Hex(), cAddr.Hex()) - } - } - } - } -} diff --git a/swarm/storage/netstore.go b/swarm/storage/netstore.go deleted file mode 100644 index 52705d3a7b38..000000000000 --- a/swarm/storage/netstore.go +++ /dev/null @@ -1,322 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package storage - -import ( - "context" - "encoding/hex" - "fmt" - "sync" - "sync/atomic" - "time" - - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/swarm/log" - lru "github.com/hashicorp/golang-lru" -) - -type ( - NewNetFetcherFunc func(ctx context.Context, addr Address, peers *sync.Map) NetFetcher -) - -type NetFetcher interface { - Request(hopCount uint8) - Offer(source *enode.ID) -} - -// NetStore is an extension of local storage -// it implements the ChunkStore interface -// on request it initiates remote cloud retrieval using a fetcher -// fetchers are unique to a chunk and are stored in fetchers LRU memory cache -// fetchFuncFactory is a factory object to create a fetch function for a specific chunk address -type NetStore struct { - mu sync.Mutex - store SyncChunkStore - fetchers *lru.Cache - NewNetFetcherFunc NewNetFetcherFunc - closeC chan struct{} -} - -var fetcherTimeout = 2 * time.Minute // timeout to cancel the fetcher even if requests are coming in - -// NewNetStore creates a new NetStore object using the given local store. newFetchFunc is a -// constructor function that can create a fetch function for a specific chunk address. -func NewNetStore(store SyncChunkStore, nnf NewNetFetcherFunc) (*NetStore, error) { - fetchers, err := lru.New(defaultChunkRequestsCacheCapacity) - if err != nil { - return nil, err - } - return &NetStore{ - store: store, - fetchers: fetchers, - NewNetFetcherFunc: nnf, - closeC: make(chan struct{}), - }, nil -} - -// Put stores a chunk in localstore, and delivers to all requestor peers using the fetcher stored in -// the fetchers cache -func (n *NetStore) Put(ctx context.Context, ch Chunk) error { - n.mu.Lock() - defer n.mu.Unlock() - - // put to the chunk to the store, there should be no error - err := n.store.Put(ctx, ch) - if err != nil { - return err - } - - // if chunk is now put in the store, check if there was an active fetcher and call deliver on it - // (this delivers the chunk to requestors via the fetcher) - if f := n.getFetcher(ch.Address()); f != nil { - f.deliver(ctx, ch) - } - return nil -} - -// Get retrieves the chunk from the NetStore DPA synchronously. -// It calls NetStore.get, and if the chunk is not in local Storage -// it calls fetch with the request, which blocks until the chunk -// arrived or context is done -func (n *NetStore) Get(rctx context.Context, ref Address) (Chunk, error) { - chunk, fetch, err := n.get(rctx, ref) - if err != nil { - return nil, err - } - if chunk != nil { - return chunk, nil - } - return fetch(rctx) -} - -func (n *NetStore) BinIndex(po uint8) uint64 { - return n.store.BinIndex(po) -} - -func (n *NetStore) Iterator(from uint64, to uint64, po uint8, f func(Address, uint64) bool) error { - return n.store.Iterator(from, to, po, f) -} - -// FetchFunc returns nil if the store contains the given address. Otherwise it returns a wait function, -// which returns after the chunk is available or the context is done -func (n *NetStore) FetchFunc(ctx context.Context, ref Address) func(context.Context) error { - chunk, fetch, _ := n.get(ctx, ref) - if chunk != nil { - return nil - } - return func(ctx context.Context) error { - _, err := fetch(ctx) - return err - } -} - -// Close chunk store -func (n *NetStore) Close() { - close(n.closeC) - n.store.Close() - - wg := sync.WaitGroup{} - for _, key := range n.fetchers.Keys() { - if f, ok := n.fetchers.Get(key); ok { - if fetch, ok := f.(*fetcher); ok { - wg.Add(1) - go func(fetch *fetcher) { - defer wg.Done() - fetch.cancel() - - select { - case <-fetch.deliveredC: - case <-fetch.cancelledC: - } - }(fetch) - } - } - } - wg.Wait() -} - -// get attempts at retrieving the chunk from LocalStore -// If it is not found then using getOrCreateFetcher: -// 1. Either there is already a fetcher to retrieve it -// 2. A new fetcher is created and saved in the fetchers cache -// From here on, all Get will hit on this fetcher until the chunk is delivered -// or all fetcher contexts are done. -// It returns a chunk, a fetcher function and an error -// If chunk is nil, the returned fetch function needs to be called with a context to return the chunk. -func (n *NetStore) get(ctx context.Context, ref Address) (Chunk, func(context.Context) (Chunk, error), error) { - n.mu.Lock() - defer n.mu.Unlock() - - chunk, err := n.store.Get(ctx, ref) - if err != nil { - if err != ErrChunkNotFound { - log.Debug("Received error from LocalStore other than ErrNotFound", "err", err) - } - // The chunk is not available in the LocalStore, let's get the fetcher for it, or create a new one - // if it doesn't exist yet - f := n.getOrCreateFetcher(ctx, ref) - // If the caller needs the chunk, it has to use the returned fetch function to get it - return nil, f.Fetch, nil - } - - return chunk, nil, nil -} - -// Has is the storage layer entry point to query the underlying -// database to return if it has a chunk or not. -// Called from the DebugAPI -func (n *NetStore) Has(ctx context.Context, ref Address) bool { - return n.store.Has(ctx, ref) -} - -// getOrCreateFetcher attempts at retrieving an existing fetchers -// if none exists, creates one and saves it in the fetchers cache -// caller must hold the lock -func (n *NetStore) getOrCreateFetcher(ctx context.Context, ref Address) *fetcher { - if f := n.getFetcher(ref); f != nil { - return f - } - - // no fetcher for the given address, we have to create a new one - key := hex.EncodeToString(ref) - // create the context during which fetching is kept alive - cctx, cancel := context.WithTimeout(ctx, fetcherTimeout) - // destroy is called when all requests finish - destroy := func() { - // remove fetcher from fetchers - n.fetchers.Remove(key) - // stop fetcher by cancelling context called when - // all requests cancelled/timedout or chunk is delivered - cancel() - } - // peers always stores all the peers which have an active request for the chunk. It is shared - // between fetcher and the NewFetchFunc function. It is needed by the NewFetchFunc because - // the peers which requested the chunk should not be requested to deliver it. - peers := &sync.Map{} - - fetcher := newFetcher(ref, n.NewNetFetcherFunc(cctx, ref, peers), destroy, peers, n.closeC) - n.fetchers.Add(key, fetcher) - - return fetcher -} - -// getFetcher retrieves the fetcher for the given address from the fetchers cache if it exists, -// otherwise it returns nil -func (n *NetStore) getFetcher(ref Address) *fetcher { - key := hex.EncodeToString(ref) - f, ok := n.fetchers.Get(key) - if ok { - return f.(*fetcher) - } - return nil -} - -// RequestsCacheLen returns the current number of outgoing requests stored in the cache -func (n *NetStore) RequestsCacheLen() int { - return n.fetchers.Len() -} - -// One fetcher object is responsible to fetch one chunk for one address, and keep track of all the -// peers who have requested it and did not receive it yet. -type fetcher struct { - addr Address // address of chunk - chunk Chunk // fetcher can set the chunk on the fetcher - deliveredC chan struct{} // chan signalling chunk delivery to requests - cancelledC chan struct{} // chan signalling the fetcher has been cancelled (removed from fetchers in NetStore) - netFetcher NetFetcher // remote fetch function to be called with a request source taken from the context - cancel func() // cleanup function for the remote fetcher to call when all upstream contexts are called - peers *sync.Map // the peers which asked for the chunk - requestCnt int32 // number of requests on this chunk. If all the requests are done (delivered or context is done) the cancel function is called - deliverOnce *sync.Once // guarantees that we only close deliveredC once -} - -// newFetcher creates a new fetcher object for the fiven addr. fetch is the function which actually -// does the retrieval (in non-test cases this is coming from the network package). cancel function is -// called either -// 1. when the chunk has been fetched all peers have been either notified or their context has been done -// 2. the chunk has not been fetched but all context from all the requests has been done -// The peers map stores all the peers which have requested chunk. -func newFetcher(addr Address, nf NetFetcher, cancel func(), peers *sync.Map, closeC chan struct{}) *fetcher { - cancelOnce := &sync.Once{} // cancel should only be called once - return &fetcher{ - addr: addr, - deliveredC: make(chan struct{}), - deliverOnce: &sync.Once{}, - cancelledC: closeC, - netFetcher: nf, - cancel: func() { - cancelOnce.Do(func() { - cancel() - }) - }, - peers: peers, - } -} - -// Fetch fetches the chunk synchronously, it is called by NetStore.Get is the chunk is not available -// locally. -func (f *fetcher) Fetch(rctx context.Context) (Chunk, error) { - atomic.AddInt32(&f.requestCnt, 1) - defer func() { - // if all the requests are done the fetcher can be cancelled - if atomic.AddInt32(&f.requestCnt, -1) == 0 { - f.cancel() - } - }() - - // The peer asking for the chunk. Store in the shared peers map, but delete after the request - // has been delivered - peer := rctx.Value("peer") - if peer != nil { - f.peers.Store(peer, time.Now()) - defer f.peers.Delete(peer) - } - - // If there is a source in the context then it is an offer, otherwise a request - sourceIF := rctx.Value("source") - - hopCount, _ := rctx.Value("hopcount").(uint8) - - if sourceIF != nil { - var source enode.ID - if err := source.UnmarshalText([]byte(sourceIF.(string))); err != nil { - return nil, err - } - f.netFetcher.Offer(&source) - } else { - f.netFetcher.Request(hopCount) - } - - // wait until either the chunk is delivered or the context is done - select { - case <-rctx.Done(): - return nil, rctx.Err() - case <-f.deliveredC: - return f.chunk, nil - case <-f.cancelledC: - return nil, fmt.Errorf("fetcher cancelled") - } -} - -// deliver is called by NetStore.Put to notify all pending requests -func (f *fetcher) deliver(ctx context.Context, ch Chunk) { - f.deliverOnce.Do(func() { - f.chunk = ch - // closing the deliveredC channel will terminate ongoing requests - close(f.deliveredC) - }) -} diff --git a/swarm/storage/netstore_test.go b/swarm/storage/netstore_test.go deleted file mode 100644 index 347d1b6f0c82..000000000000 --- a/swarm/storage/netstore_test.go +++ /dev/null @@ -1,692 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package storage - -import ( - "bytes" - "context" - "crypto/rand" - "errors" - "fmt" - "io/ioutil" - "sync" - "testing" - "time" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/p2p/enode" - ch "github.com/ubiq/go-ubiq/swarm/chunk" -) - -var sourcePeerID = enode.HexID("99d8594b52298567d2ca3f4c441a5ba0140ee9245e26460d01102a52773c73b9") - -type mockNetFetcher struct { - peers *sync.Map - sources []*enode.ID - peersPerRequest [][]Address - requestCalled bool - offerCalled bool - quit <-chan struct{} - ctx context.Context - hopCounts []uint8 - mu sync.Mutex -} - -func (m *mockNetFetcher) Offer(source *enode.ID) { - m.offerCalled = true - m.sources = append(m.sources, source) -} - -func (m *mockNetFetcher) Request(hopCount uint8) { - m.mu.Lock() - defer m.mu.Unlock() - - m.requestCalled = true - var peers []Address - m.peers.Range(func(key interface{}, _ interface{}) bool { - peers = append(peers, common.FromHex(key.(string))) - return true - }) - m.peersPerRequest = append(m.peersPerRequest, peers) - m.hopCounts = append(m.hopCounts, hopCount) -} - -type mockNetFetchFuncFactory struct { - fetcher *mockNetFetcher -} - -func (m *mockNetFetchFuncFactory) newMockNetFetcher(ctx context.Context, _ Address, peers *sync.Map) NetFetcher { - m.fetcher.peers = peers - m.fetcher.quit = ctx.Done() - m.fetcher.ctx = ctx - return m.fetcher -} - -func mustNewNetStore(t *testing.T) *NetStore { - netStore, _ := mustNewNetStoreWithFetcher(t) - return netStore -} - -func mustNewNetStoreWithFetcher(t *testing.T) (*NetStore, *mockNetFetcher) { - t.Helper() - - datadir, err := ioutil.TempDir("", "netstore") - if err != nil { - t.Fatal(err) - } - naddr := make([]byte, 32) - params := NewDefaultLocalStoreParams() - params.Init(datadir) - params.BaseKey = naddr - localStore, err := NewTestLocalStoreForAddr(params) - if err != nil { - t.Fatal(err) - } - - fetcher := &mockNetFetcher{} - mockNetFetchFuncFactory := &mockNetFetchFuncFactory{ - fetcher: fetcher, - } - netStore, err := NewNetStore(localStore, mockNetFetchFuncFactory.newMockNetFetcher) - if err != nil { - t.Fatal(err) - } - return netStore, fetcher -} - -// TestNetStoreGetAndPut tests calling NetStore.Get which is blocked until the same chunk is Put. -// After the Put there should no active fetchers, and the context created for the fetcher should -// be cancelled. -func TestNetStoreGetAndPut(t *testing.T) { - netStore, fetcher := mustNewNetStoreWithFetcher(t) - - chunk := GenerateRandomChunk(ch.DefaultSize) - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - c := make(chan struct{}) // this channel ensures that the gouroutine with the Put does not run earlier than the Get - putErrC := make(chan error) - go func() { - <-c // wait for the Get to be called - time.Sleep(200 * time.Millisecond) // and a little more so it is surely called - - // check if netStore created a fetcher in the Get call for the unavailable chunk - if netStore.fetchers.Len() != 1 || netStore.getFetcher(chunk.Address()) == nil { - putErrC <- errors.New("Expected netStore to use a fetcher for the Get call") - return - } - - err := netStore.Put(ctx, chunk) - if err != nil { - putErrC <- fmt.Errorf("Expected no err got %v", err) - return - } - - putErrC <- nil - }() - - close(c) - recChunk, err := netStore.Get(ctx, chunk.Address()) // this is blocked until the Put above is done - if err != nil { - t.Fatalf("Expected no err got %v", err) - } - - if err := <-putErrC; err != nil { - t.Fatal(err) - } - // the retrieved chunk should be the same as what we Put - if !bytes.Equal(recChunk.Address(), chunk.Address()) || !bytes.Equal(recChunk.Data(), chunk.Data()) { - t.Fatalf("Different chunk received than what was put") - } - // the chunk is already available locally, so there should be no active fetchers waiting for it - if netStore.fetchers.Len() != 0 { - t.Fatal("Expected netStore to remove the fetcher after delivery") - } - - // A fetcher was created when the Get was called (and the chunk was not available). The chunk - // was delivered with the Put call, so the fetcher should be cancelled now. - select { - case <-fetcher.ctx.Done(): - default: - t.Fatal("Expected fetcher context to be cancelled") - } - -} - -// TestNetStoreGetAndPut tests calling NetStore.Put and then NetStore.Get. -// After the Put the chunk is available locally, so the Get can just retrieve it from LocalStore, -// there is no need to create fetchers. -func TestNetStoreGetAfterPut(t *testing.T) { - netStore, fetcher := mustNewNetStoreWithFetcher(t) - - chunk := GenerateRandomChunk(ch.DefaultSize) - - ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) - defer cancel() - - // First we Put the chunk, so the chunk will be available locally - err := netStore.Put(ctx, chunk) - if err != nil { - t.Fatalf("Expected no err got %v", err) - } - - // Get should retrieve the chunk from LocalStore, without creating fetcher - recChunk, err := netStore.Get(ctx, chunk.Address()) - if err != nil { - t.Fatalf("Expected no err got %v", err) - } - // the retrieved chunk should be the same as what we Put - if !bytes.Equal(recChunk.Address(), chunk.Address()) || !bytes.Equal(recChunk.Data(), chunk.Data()) { - t.Fatalf("Different chunk received than what was put") - } - // no fetcher offer or request should be created for a locally available chunk - if fetcher.offerCalled || fetcher.requestCalled { - t.Fatal("NetFetcher.offerCalled or requestCalled not expected to be called") - } - // no fetchers should be created for a locally available chunk - if netStore.fetchers.Len() != 0 { - t.Fatal("Expected netStore to not have fetcher") - } - -} - -// TestNetStoreGetTimeout tests a Get call for an unavailable chunk and waits for timeout -func TestNetStoreGetTimeout(t *testing.T) { - netStore, fetcher := mustNewNetStoreWithFetcher(t) - - chunk := GenerateRandomChunk(ch.DefaultSize) - - ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) - defer cancel() - - c := make(chan struct{}) // this channel ensures that the gouroutine does not run earlier than the Get - fetcherErrC := make(chan error) - go func() { - <-c // wait for the Get to be called - time.Sleep(200 * time.Millisecond) // and a little more so it is surely called - - // check if netStore created a fetcher in the Get call for the unavailable chunk - if netStore.fetchers.Len() != 1 || netStore.getFetcher(chunk.Address()) == nil { - fetcherErrC <- errors.New("Expected netStore to use a fetcher for the Get call") - return - } - - fetcherErrC <- nil - }() - - close(c) - // We call Get on this chunk, which is not in LocalStore. We don't Put it at all, so there will - // be a timeout - _, err := netStore.Get(ctx, chunk.Address()) - - // Check if the timeout happened - if err != context.DeadlineExceeded { - t.Fatalf("Expected context.DeadLineExceeded err got %v", err) - } - - if err := <-fetcherErrC; err != nil { - t.Fatal(err) - } - - // A fetcher was created, check if it has been removed after timeout - if netStore.fetchers.Len() != 0 { - t.Fatal("Expected netStore to remove the fetcher after timeout") - } - - // Check if the fetcher context has been cancelled after the timeout - select { - case <-fetcher.ctx.Done(): - default: - t.Fatal("Expected fetcher context to be cancelled") - } -} - -// TestNetStoreGetCancel tests a Get call for an unavailable chunk, then cancels the context and checks -// the errors -func TestNetStoreGetCancel(t *testing.T) { - netStore, fetcher := mustNewNetStoreWithFetcher(t) - - chunk := GenerateRandomChunk(ch.DefaultSize) - - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - - c := make(chan struct{}) // this channel ensures that the gouroutine with the cancel does not run earlier than the Get - fetcherErrC := make(chan error, 1) - go func() { - <-c // wait for the Get to be called - time.Sleep(200 * time.Millisecond) // and a little more so it is surely called - // check if netStore created a fetcher in the Get call for the unavailable chunk - if netStore.fetchers.Len() != 1 || netStore.getFetcher(chunk.Address()) == nil { - fetcherErrC <- errors.New("Expected netStore to use a fetcher for the Get call") - return - } - - fetcherErrC <- nil - cancel() - }() - - close(c) - - // We call Get with an unavailable chunk, so it will create a fetcher and wait for delivery - _, err := netStore.Get(ctx, chunk.Address()) - - if err := <-fetcherErrC; err != nil { - t.Fatal(err) - } - - // After the context is cancelled above Get should return with an error - if err != context.Canceled { - t.Fatalf("Expected context.Canceled err got %v", err) - } - - // A fetcher was created, check if it has been removed after cancel - if netStore.fetchers.Len() != 0 { - t.Fatal("Expected netStore to remove the fetcher after cancel") - } - - // Check if the fetcher context has been cancelled after the request context cancel - select { - case <-fetcher.ctx.Done(): - default: - t.Fatal("Expected fetcher context to be cancelled") - } -} - -// TestNetStoreMultipleGetAndPut tests four Get calls for the same unavailable chunk. The chunk is -// delivered with a Put, we have to make sure all Get calls return, and they use a single fetcher -// for the chunk retrieval -func TestNetStoreMultipleGetAndPut(t *testing.T) { - netStore, fetcher := mustNewNetStoreWithFetcher(t) - - chunk := GenerateRandomChunk(ch.DefaultSize) - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - putErrC := make(chan error) - go func() { - // sleep to make sure Put is called after all the Get - time.Sleep(500 * time.Millisecond) - // check if netStore created exactly one fetcher for all Get calls - if netStore.fetchers.Len() != 1 { - putErrC <- errors.New("Expected netStore to use one fetcher for all Get calls") - return - } - err := netStore.Put(ctx, chunk) - if err != nil { - putErrC <- fmt.Errorf("Expected no err got %v", err) - return - } - putErrC <- nil - }() - - count := 4 - // call Get 4 times for the same unavailable chunk. The calls will be blocked until the Put above. - errC := make(chan error) - for i := 0; i < count; i++ { - go func() { - recChunk, err := netStore.Get(ctx, chunk.Address()) - if err != nil { - errC <- fmt.Errorf("Expected no err got %v", err) - } - if !bytes.Equal(recChunk.Address(), chunk.Address()) || !bytes.Equal(recChunk.Data(), chunk.Data()) { - errC <- errors.New("Different chunk received than what was put") - } - errC <- nil - }() - } - - if err := <-putErrC; err != nil { - t.Fatal(err) - } - - timeout := time.After(1 * time.Second) - - // The Get calls should return after Put, so no timeout expected - for i := 0; i < count; i++ { - select { - case err := <-errC: - if err != nil { - t.Fatal(err) - } - case <-timeout: - t.Fatalf("Timeout waiting for Get calls to return") - } - } - - // A fetcher was created, check if it has been removed after cancel - if netStore.fetchers.Len() != 0 { - t.Fatal("Expected netStore to remove the fetcher after delivery") - } - - // A fetcher was created, check if it has been removed after delivery - select { - case <-fetcher.ctx.Done(): - default: - t.Fatal("Expected fetcher context to be cancelled") - } - -} - -// TestNetStoreFetchFuncTimeout tests a FetchFunc call for an unavailable chunk and waits for timeout -func TestNetStoreFetchFuncTimeout(t *testing.T) { - netStore, fetcher := mustNewNetStoreWithFetcher(t) - - chunk := GenerateRandomChunk(ch.DefaultSize) - - ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) - defer cancel() - - // FetchFunc is called for an unavaible chunk, so the returned wait function should not be nil - wait := netStore.FetchFunc(ctx, chunk.Address()) - if wait == nil { - t.Fatal("Expected wait function to be not nil") - } - - // There should an active fetcher for the chunk after the FetchFunc call - if netStore.fetchers.Len() != 1 || netStore.getFetcher(chunk.Address()) == nil { - t.Fatalf("Expected netStore to have one fetcher for the requested chunk") - } - - // wait function should timeout because we don't deliver the chunk with a Put - err := wait(ctx) - if err != context.DeadlineExceeded { - t.Fatalf("Expected context.DeadLineExceeded err got %v", err) - } - - // the fetcher should be removed after timeout - if netStore.fetchers.Len() != 0 { - t.Fatal("Expected netStore to remove the fetcher after timeout") - } - - // the fetcher context should be cancelled after timeout - select { - case <-fetcher.ctx.Done(): - default: - t.Fatal("Expected fetcher context to be cancelled") - } -} - -// TestNetStoreFetchFuncAfterPut tests that the FetchFunc should return nil for a locally available chunk -func TestNetStoreFetchFuncAfterPut(t *testing.T) { - netStore := mustNewNetStore(t) - - chunk := GenerateRandomChunk(ch.DefaultSize) - - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - - // We deliver the created the chunk with a Put - err := netStore.Put(ctx, chunk) - if err != nil { - t.Fatalf("Expected no err got %v", err) - } - - // FetchFunc should return nil, because the chunk is available locally, no need to fetch it - wait := netStore.FetchFunc(ctx, chunk.Address()) - if wait != nil { - t.Fatal("Expected wait to be nil") - } - - // No fetchers should be created at all - if netStore.fetchers.Len() != 0 { - t.Fatal("Expected netStore to not have fetcher") - } -} - -// TestNetStoreGetCallsRequest tests if Get created a request on the NetFetcher for an unavailable chunk -func TestNetStoreGetCallsRequest(t *testing.T) { - netStore, fetcher := mustNewNetStoreWithFetcher(t) - - chunk := GenerateRandomChunk(ch.DefaultSize) - - ctx := context.WithValue(context.Background(), "hopcount", uint8(5)) - ctx, cancel := context.WithTimeout(ctx, 200*time.Millisecond) - defer cancel() - - // We call get for a not available chunk, it will timeout because the chunk is not delivered - _, err := netStore.Get(ctx, chunk.Address()) - - if err != context.DeadlineExceeded { - t.Fatalf("Expected context.DeadlineExceeded err got %v", err) - } - - // NetStore should call NetFetcher.Request and wait for the chunk - if !fetcher.requestCalled { - t.Fatal("Expected NetFetcher.Request to be called") - } - - if fetcher.hopCounts[0] != 5 { - t.Fatalf("Expected NetFetcher.Request be called with hopCount 5, got %v", fetcher.hopCounts[0]) - } -} - -// TestNetStoreGetCallsOffer tests if Get created a request on the NetFetcher for an unavailable chunk -// in case of a source peer provided in the context. -func TestNetStoreGetCallsOffer(t *testing.T) { - netStore, fetcher := mustNewNetStoreWithFetcher(t) - - chunk := GenerateRandomChunk(ch.DefaultSize) - - // If a source peer is added to the context, NetStore will handle it as an offer - ctx := context.WithValue(context.Background(), "source", sourcePeerID.String()) - ctx, cancel := context.WithTimeout(ctx, 200*time.Millisecond) - defer cancel() - - // We call get for a not available chunk, it will timeout because the chunk is not delivered - _, err := netStore.Get(ctx, chunk.Address()) - - if err != context.DeadlineExceeded { - t.Fatalf("Expect error %v got %v", context.DeadlineExceeded, err) - } - - // NetStore should call NetFetcher.Offer with the source peer - if !fetcher.offerCalled { - t.Fatal("Expected NetFetcher.Request to be called") - } - - if len(fetcher.sources) != 1 { - t.Fatalf("Expected fetcher sources length 1 got %v", len(fetcher.sources)) - } - - if fetcher.sources[0].String() != sourcePeerID.String() { - t.Fatalf("Expected fetcher source %v got %v", sourcePeerID, fetcher.sources[0]) - } - -} - -// TestNetStoreFetcherCountPeers tests multiple NetStore.Get calls with peer in the context. -// There is no Put call, so the Get calls timeout -func TestNetStoreFetcherCountPeers(t *testing.T) { - - netStore, fetcher := mustNewNetStoreWithFetcher(t) - - addr := randomAddr() - peers := []string{randomAddr().Hex(), randomAddr().Hex(), randomAddr().Hex()} - - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - errC := make(chan error) - nrGets := 3 - - // Call Get 3 times with a peer in context - for i := 0; i < nrGets; i++ { - peer := peers[i] - go func() { - ctx := context.WithValue(ctx, "peer", peer) - _, err := netStore.Get(ctx, addr) - errC <- err - }() - } - - // All 3 Get calls should timeout - for i := 0; i < nrGets; i++ { - err := <-errC - if err != context.DeadlineExceeded { - t.Fatalf("Expected \"%v\" error got \"%v\"", context.DeadlineExceeded, err) - } - } - - // fetcher should be closed after timeout - select { - case <-fetcher.quit: - case <-time.After(3 * time.Second): - t.Fatalf("mockNetFetcher not closed after timeout") - } - - // All 3 peers should be given to NetFetcher after the 3 Get calls - if len(fetcher.peersPerRequest) != nrGets { - t.Fatalf("Expected 3 got %v", len(fetcher.peersPerRequest)) - } - - for i, peers := range fetcher.peersPerRequest { - if len(peers) < i+1 { - t.Fatalf("Expected at least %v got %v", i+1, len(peers)) - } - } -} - -// TestNetStoreFetchFuncCalledMultipleTimes calls the wait function given by FetchFunc three times, -// and checks there is still exactly one fetcher for one chunk. Afthe chunk is delivered, it checks -// if the fetcher is closed. -func TestNetStoreFetchFuncCalledMultipleTimes(t *testing.T) { - netStore, fetcher := mustNewNetStoreWithFetcher(t) - - chunk := GenerateRandomChunk(ch.DefaultSize) - - ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond) - defer cancel() - - // FetchFunc should return a non-nil wait function, because the chunk is not available - wait := netStore.FetchFunc(ctx, chunk.Address()) - if wait == nil { - t.Fatal("Expected wait function to be not nil") - } - - // There should be exactly one fetcher for the chunk - if netStore.fetchers.Len() != 1 || netStore.getFetcher(chunk.Address()) == nil { - t.Fatalf("Expected netStore to have one fetcher for the requested chunk") - } - - // Call wait three times in parallel - count := 3 - errC := make(chan error) - for i := 0; i < count; i++ { - go func() { - errC <- wait(ctx) - }() - } - - // sleep a little so the wait functions are called above - time.Sleep(100 * time.Millisecond) - - // there should be still only one fetcher, because all wait calls are for the same chunk - if netStore.fetchers.Len() != 1 || netStore.getFetcher(chunk.Address()) == nil { - t.Fatal("Expected netStore to have one fetcher for the requested chunk") - } - - // Deliver the chunk with a Put - err := netStore.Put(ctx, chunk) - if err != nil { - t.Fatalf("Expected no err got %v", err) - } - - // wait until all wait calls return (because the chunk is delivered) - for i := 0; i < count; i++ { - err := <-errC - if err != nil { - t.Fatal(err) - } - } - - // There should be no more fetchers for the delivered chunk - if netStore.fetchers.Len() != 0 { - t.Fatal("Expected netStore to remove the fetcher after delivery") - } - - // The context for the fetcher should be cancelled after delivery - select { - case <-fetcher.ctx.Done(): - default: - t.Fatal("Expected fetcher context to be cancelled") - } -} - -// TestNetStoreFetcherLifeCycleWithTimeout is similar to TestNetStoreFetchFuncCalledMultipleTimes, -// the only difference is that we don't deilver the chunk, just wait for timeout -func TestNetStoreFetcherLifeCycleWithTimeout(t *testing.T) { - netStore, fetcher := mustNewNetStoreWithFetcher(t) - - chunk := GenerateRandomChunk(ch.DefaultSize) - - ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second) - defer cancel() - - // FetchFunc should return a non-nil wait function, because the chunk is not available - wait := netStore.FetchFunc(ctx, chunk.Address()) - if wait == nil { - t.Fatal("Expected wait function to be not nil") - } - - // There should be exactly one fetcher for the chunk - if netStore.fetchers.Len() != 1 || netStore.getFetcher(chunk.Address()) == nil { - t.Fatalf("Expected netStore to have one fetcher for the requested chunk") - } - - // Call wait three times in parallel - count := 3 - errC := make(chan error) - for i := 0; i < count; i++ { - go func() { - rctx, rcancel := context.WithTimeout(context.Background(), 100*time.Millisecond) - defer rcancel() - err := wait(rctx) - if err != context.DeadlineExceeded { - errC <- fmt.Errorf("Expected err %v got %v", context.DeadlineExceeded, err) - return - } - errC <- nil - }() - } - - // wait until all wait calls timeout - for i := 0; i < count; i++ { - err := <-errC - if err != nil { - t.Fatal(err) - } - } - - // There should be no more fetchers after timeout - if netStore.fetchers.Len() != 0 { - t.Fatal("Expected netStore to remove the fetcher after delivery") - } - - // The context for the fetcher should be cancelled after timeout - select { - case <-fetcher.ctx.Done(): - default: - t.Fatal("Expected fetcher context to be cancelled") - } -} - -func randomAddr() Address { - addr := make([]byte, 32) - rand.Read(addr) - return Address(addr) -} diff --git a/swarm/storage/pyramid.go b/swarm/storage/pyramid.go deleted file mode 100644 index 0a6490757d26..000000000000 --- a/swarm/storage/pyramid.go +++ /dev/null @@ -1,694 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package storage - -import ( - "context" - "encoding/binary" - "errors" - "io" - "io/ioutil" - "sync" - "time" - - ch "github.com/ubiq/go-ubiq/swarm/chunk" - "github.com/ubiq/go-ubiq/swarm/log" -) - -/* - The main idea of a pyramid chunker is to process the input data without knowing the entire size apriori. - For this to be achieved, the chunker tree is built from the ground up until the data is exhausted. - This opens up new aveneus such as easy append and other sort of modifications to the tree thereby avoiding - duplication of data chunks. - - - Below is an example of a two level chunks tree. The leaf chunks are called data chunks and all the above - chunks are called tree chunks. The tree chunk above data chunks is level 0 and so on until it reaches - the root tree chunk. - - - - T10 <- Tree chunk lvl1 - | - __________________________|_____________________________ - / | | \ - / | \ \ - __T00__ ___T01__ ___T02__ ___T03__ <- Tree chunks lvl 0 - / / \ / / \ / / \ / / \ - / / \ / / \ / / \ / / \ - D1 D2 ... D128 D1 D2 ... D128 D1 D2 ... D128 D1 D2 ... D128 <- Data Chunks - - - The split function continuously read the data and creates data chunks and send them to storage. - When certain no of data chunks are created (defaultBranches), a signal is sent to create a tree - entry. When the level 0 tree entries reaches certain threshold (defaultBranches), another signal - is sent to a tree entry one level up.. and so on... until only the data is exhausted AND only one - tree entry is present in certain level. The key of tree entry is given out as the rootAddress of the file. - -*/ - -var ( - errLoadingTreeRootChunk = errors.New("LoadTree Error: Could not load root chunk") - errLoadingTreeChunk = errors.New("LoadTree Error: Could not load chunk") -) - -const ( - ChunkProcessors = 8 - splitTimeout = time.Minute * 5 -) - -type PyramidSplitterParams struct { - SplitterParams - getter Getter -} - -func NewPyramidSplitterParams(addr Address, reader io.Reader, putter Putter, getter Getter, chunkSize int64) *PyramidSplitterParams { - hashSize := putter.RefSize() - return &PyramidSplitterParams{ - SplitterParams: SplitterParams{ - ChunkerParams: ChunkerParams{ - chunkSize: chunkSize, - hashSize: hashSize, - }, - reader: reader, - putter: putter, - addr: addr, - }, - getter: getter, - } -} - -/* - When splitting, data is given as a SectionReader, and the key is a hashSize long byte slice (Address), the root hash of the entire content will fill this once processing finishes. - New chunks to store are store using the putter which the caller provides. -*/ -func PyramidSplit(ctx context.Context, reader io.Reader, putter Putter, getter Getter) (Address, func(context.Context) error, error) { - return NewPyramidSplitter(NewPyramidSplitterParams(nil, reader, putter, getter, ch.DefaultSize)).Split(ctx) -} - -func PyramidAppend(ctx context.Context, addr Address, reader io.Reader, putter Putter, getter Getter) (Address, func(context.Context) error, error) { - return NewPyramidSplitter(NewPyramidSplitterParams(addr, reader, putter, getter, ch.DefaultSize)).Append(ctx) -} - -// Entry to create a tree node -type TreeEntry struct { - level int - branchCount int64 - subtreeSize uint64 - chunk []byte - key []byte - index int // used in append to indicate the index of existing tree entry - updatePending bool // indicates if the entry is loaded from existing tree -} - -func NewTreeEntry(pyramid *PyramidChunker) *TreeEntry { - return &TreeEntry{ - level: 0, - branchCount: 0, - subtreeSize: 0, - chunk: make([]byte, pyramid.chunkSize+8), - key: make([]byte, pyramid.hashSize), - index: 0, - updatePending: false, - } -} - -// Used by the hash processor to create a data/tree chunk and send to storage -type chunkJob struct { - key Address - chunk []byte - parentWg *sync.WaitGroup -} - -type PyramidChunker struct { - chunkSize int64 - hashSize int64 - branches int64 - reader io.Reader - putter Putter - getter Getter - key Address - workerCount int64 - workerLock sync.RWMutex - jobC chan *chunkJob - wg *sync.WaitGroup - errC chan error - quitC chan bool - rootAddress []byte - chunkLevel [][]*TreeEntry -} - -func NewPyramidSplitter(params *PyramidSplitterParams) (pc *PyramidChunker) { - pc = &PyramidChunker{} - pc.reader = params.reader - pc.hashSize = params.hashSize - pc.branches = params.chunkSize / pc.hashSize - pc.chunkSize = pc.hashSize * pc.branches - pc.putter = params.putter - pc.getter = params.getter - pc.key = params.addr - pc.workerCount = 0 - pc.jobC = make(chan *chunkJob, 2*ChunkProcessors) - pc.wg = &sync.WaitGroup{} - pc.errC = make(chan error) - pc.quitC = make(chan bool) - pc.rootAddress = make([]byte, pc.hashSize) - pc.chunkLevel = make([][]*TreeEntry, pc.branches) - return -} - -func (pc *PyramidChunker) Join(addr Address, getter Getter, depth int) LazySectionReader { - return &LazyChunkReader{ - addr: addr, - depth: depth, - chunkSize: pc.chunkSize, - branches: pc.branches, - hashSize: pc.hashSize, - getter: getter, - } -} - -func (pc *PyramidChunker) incrementWorkerCount() { - pc.workerLock.Lock() - defer pc.workerLock.Unlock() - pc.workerCount += 1 -} - -func (pc *PyramidChunker) getWorkerCount() int64 { - pc.workerLock.Lock() - defer pc.workerLock.Unlock() - return pc.workerCount -} - -func (pc *PyramidChunker) decrementWorkerCount() { - pc.workerLock.Lock() - defer pc.workerLock.Unlock() - pc.workerCount -= 1 -} - -func (pc *PyramidChunker) Split(ctx context.Context) (k Address, wait func(context.Context) error, err error) { - pc.wg.Add(1) - pc.prepareChunks(ctx, false) - - // closes internal error channel if all subprocesses in the workgroup finished - go func() { - - // waiting for all chunks to finish - pc.wg.Wait() - - //We close errC here because this is passed down to 8 parallel routines underneath. - // if a error happens in one of them.. that particular routine raises error... - // once they all complete successfully, the control comes back and we can safely close this here. - close(pc.errC) - }() - - defer close(pc.quitC) - defer pc.putter.Close() - - select { - case err := <-pc.errC: - if err != nil { - return nil, nil, err - } - case <-ctx.Done(): - _ = pc.putter.Wait(ctx) //??? - return nil, nil, ctx.Err() - } - return pc.rootAddress, pc.putter.Wait, nil - -} - -func (pc *PyramidChunker) Append(ctx context.Context) (k Address, wait func(context.Context) error, err error) { - // Load the right most unfinished tree chunks in every level - pc.loadTree(ctx) - - pc.wg.Add(1) - pc.prepareChunks(ctx, true) - - // closes internal error channel if all subprocesses in the workgroup finished - go func() { - - // waiting for all chunks to finish - pc.wg.Wait() - - close(pc.errC) - }() - - defer close(pc.quitC) - defer pc.putter.Close() - - select { - case err := <-pc.errC: - if err != nil { - return nil, nil, err - } - case <-time.NewTimer(splitTimeout).C: - } - - return pc.rootAddress, pc.putter.Wait, nil - -} - -func (pc *PyramidChunker) processor(ctx context.Context, id int64) { - defer pc.decrementWorkerCount() - for { - select { - - case job, ok := <-pc.jobC: - if !ok { - return - } - pc.processChunk(ctx, id, job) - case <-pc.quitC: - return - } - } -} - -func (pc *PyramidChunker) processChunk(ctx context.Context, id int64, job *chunkJob) { - ref, err := pc.putter.Put(ctx, job.chunk) - if err != nil { - select { - case pc.errC <- err: - case <-pc.quitC: - } - } - - // report hash of this chunk one level up (keys corresponds to the proper subslice of the parent chunk) - copy(job.key, ref) - - // send off new chunk to storage - job.parentWg.Done() -} - -func (pc *PyramidChunker) loadTree(ctx context.Context) error { - // Get the root chunk to get the total size - chunkData, err := pc.getter.Get(ctx, Reference(pc.key)) - if err != nil { - return errLoadingTreeRootChunk - } - chunkSize := int64(chunkData.Size()) - log.Trace("pyramid.chunker: root chunk", "chunk.Size", chunkSize, "pc.chunkSize", pc.chunkSize) - - //if data size is less than a chunk... add a parent with update as pending - if chunkSize <= pc.chunkSize { - newEntry := &TreeEntry{ - level: 0, - branchCount: 1, - subtreeSize: uint64(chunkSize), - chunk: make([]byte, pc.chunkSize+8), - key: make([]byte, pc.hashSize), - index: 0, - updatePending: true, - } - copy(newEntry.chunk[8:], pc.key) - pc.chunkLevel[0] = append(pc.chunkLevel[0], newEntry) - return nil - } - - var treeSize int64 - var depth int - treeSize = pc.chunkSize - for ; treeSize < chunkSize; treeSize *= pc.branches { - depth++ - } - log.Trace("pyramid.chunker", "depth", depth) - - // Add the root chunk entry - branchCount := int64(len(chunkData)-8) / pc.hashSize - newEntry := &TreeEntry{ - level: depth - 1, - branchCount: branchCount, - subtreeSize: uint64(chunkSize), - chunk: chunkData, - key: pc.key, - index: 0, - updatePending: true, - } - pc.chunkLevel[depth-1] = append(pc.chunkLevel[depth-1], newEntry) - - // Add the rest of the tree - for lvl := depth - 1; lvl >= 1; lvl-- { - - //TODO(jmozah): instead of loading finished branches and then trim in the end, - //avoid loading them in the first place - for _, ent := range pc.chunkLevel[lvl] { - branchCount = int64(len(ent.chunk)-8) / pc.hashSize - for i := int64(0); i < branchCount; i++ { - key := ent.chunk[8+(i*pc.hashSize) : 8+((i+1)*pc.hashSize)] - newChunkData, err := pc.getter.Get(ctx, Reference(key)) - if err != nil { - return errLoadingTreeChunk - } - newChunkSize := newChunkData.Size() - bewBranchCount := int64(len(newChunkData)-8) / pc.hashSize - newEntry := &TreeEntry{ - level: lvl - 1, - branchCount: bewBranchCount, - subtreeSize: newChunkSize, - chunk: newChunkData, - key: key, - index: 0, - updatePending: true, - } - pc.chunkLevel[lvl-1] = append(pc.chunkLevel[lvl-1], newEntry) - - } - - // We need to get only the right most unfinished branch.. so trim all finished branches - if int64(len(pc.chunkLevel[lvl-1])) >= pc.branches { - pc.chunkLevel[lvl-1] = nil - } - } - } - - return nil -} - -func (pc *PyramidChunker) prepareChunks(ctx context.Context, isAppend bool) { - defer pc.wg.Done() - - chunkWG := &sync.WaitGroup{} - - pc.incrementWorkerCount() - - go pc.processor(ctx, pc.workerCount) - - parent := NewTreeEntry(pc) - var unfinishedChunkData ChunkData - var unfinishedChunkSize uint64 - - if isAppend && len(pc.chunkLevel[0]) != 0 { - lastIndex := len(pc.chunkLevel[0]) - 1 - ent := pc.chunkLevel[0][lastIndex] - - if ent.branchCount < pc.branches { - parent = &TreeEntry{ - level: 0, - branchCount: ent.branchCount, - subtreeSize: ent.subtreeSize, - chunk: ent.chunk, - key: ent.key, - index: lastIndex, - updatePending: true, - } - - lastBranch := parent.branchCount - 1 - lastAddress := parent.chunk[8+lastBranch*pc.hashSize : 8+(lastBranch+1)*pc.hashSize] - - var err error - unfinishedChunkData, err = pc.getter.Get(ctx, lastAddress) - if err != nil { - pc.errC <- err - } - unfinishedChunkSize = unfinishedChunkData.Size() - if unfinishedChunkSize < uint64(pc.chunkSize) { - parent.subtreeSize = parent.subtreeSize - unfinishedChunkSize - parent.branchCount = parent.branchCount - 1 - } else { - unfinishedChunkData = nil - } - } - } - - for index := 0; ; index++ { - var err error - chunkData := make([]byte, pc.chunkSize+8) - - var readBytes int - - if unfinishedChunkData != nil { - copy(chunkData, unfinishedChunkData) - readBytes += int(unfinishedChunkSize) - unfinishedChunkData = nil - log.Trace("pyramid.chunker: found unfinished chunk", "readBytes", readBytes) - } - - var res []byte - res, err = ioutil.ReadAll(io.LimitReader(pc.reader, int64(len(chunkData)-(8+readBytes)))) - - // hack for ioutil.ReadAll: - // a successful call to ioutil.ReadAll returns err == nil, not err == EOF, whereas we - // want to propagate the io.EOF error - if len(res) == 0 && err == nil { - err = io.EOF - } - copy(chunkData[8+readBytes:], res) - - readBytes += len(res) - log.Trace("pyramid.chunker: copied all data", "readBytes", readBytes) - - if err != nil { - if err == io.EOF || err == io.ErrUnexpectedEOF { - - pc.cleanChunkLevels() - - // Check if we are appending or the chunk is the only one. - if parent.branchCount == 1 && (pc.depth() == 0 || isAppend) { - // Data is exactly one chunk.. pick the last chunk key as root - chunkWG.Wait() - lastChunksAddress := parent.chunk[8 : 8+pc.hashSize] - copy(pc.rootAddress, lastChunksAddress) - break - } - } else { - close(pc.quitC) - break - } - } - - // Data ended in chunk boundary.. just signal to start bulding tree - if readBytes == 0 { - pc.buildTree(isAppend, parent, chunkWG, true, nil) - break - } else { - pkey := pc.enqueueDataChunk(chunkData, uint64(readBytes), parent, chunkWG) - - // update tree related parent data structures - parent.subtreeSize += uint64(readBytes) - parent.branchCount++ - - // Data got exhausted... signal to send any parent tree related chunks - if int64(readBytes) < pc.chunkSize { - - pc.cleanChunkLevels() - - // only one data chunk .. so dont add any parent chunk - if parent.branchCount <= 1 { - chunkWG.Wait() - - if isAppend || pc.depth() == 0 { - // No need to build the tree if the depth is 0 - // or we are appending. - // Just use the last key. - copy(pc.rootAddress, pkey) - } else { - // We need to build the tree and and provide the lonely - // chunk key to replace the last tree chunk key. - pc.buildTree(isAppend, parent, chunkWG, true, pkey) - } - break - } - - pc.buildTree(isAppend, parent, chunkWG, true, nil) - break - } - - if parent.branchCount == pc.branches { - pc.buildTree(isAppend, parent, chunkWG, false, nil) - parent = NewTreeEntry(pc) - } - - } - - workers := pc.getWorkerCount() - if int64(len(pc.jobC)) > workers && workers < ChunkProcessors { - pc.incrementWorkerCount() - go pc.processor(ctx, pc.workerCount) - } - - } - -} - -func (pc *PyramidChunker) buildTree(isAppend bool, ent *TreeEntry, chunkWG *sync.WaitGroup, last bool, lonelyChunkKey []byte) { - chunkWG.Wait() - pc.enqueueTreeChunk(ent, chunkWG, last) - - compress := false - endLvl := pc.branches - for lvl := int64(0); lvl < pc.branches; lvl++ { - lvlCount := int64(len(pc.chunkLevel[lvl])) - if lvlCount >= pc.branches { - endLvl = lvl + 1 - compress = true - break - } - } - - if !compress && !last { - return - } - - // Wait for all the keys to be processed before compressing the tree - chunkWG.Wait() - - for lvl := int64(ent.level); lvl < endLvl; lvl++ { - - lvlCount := int64(len(pc.chunkLevel[lvl])) - if lvlCount == 1 && last { - copy(pc.rootAddress, pc.chunkLevel[lvl][0].key) - return - } - - for startCount := int64(0); startCount < lvlCount; startCount += pc.branches { - - endCount := startCount + pc.branches - if endCount > lvlCount { - endCount = lvlCount - } - - var nextLvlCount int64 - var tempEntry *TreeEntry - if len(pc.chunkLevel[lvl+1]) > 0 { - nextLvlCount = int64(len(pc.chunkLevel[lvl+1]) - 1) - tempEntry = pc.chunkLevel[lvl+1][nextLvlCount] - } - if isAppend && tempEntry != nil && tempEntry.updatePending { - updateEntry := &TreeEntry{ - level: int(lvl + 1), - branchCount: 0, - subtreeSize: 0, - chunk: make([]byte, pc.chunkSize+8), - key: make([]byte, pc.hashSize), - index: int(nextLvlCount), - updatePending: true, - } - for index := int64(0); index < lvlCount; index++ { - updateEntry.branchCount++ - updateEntry.subtreeSize += pc.chunkLevel[lvl][index].subtreeSize - copy(updateEntry.chunk[8+(index*pc.hashSize):8+((index+1)*pc.hashSize)], pc.chunkLevel[lvl][index].key[:pc.hashSize]) - } - - pc.enqueueTreeChunk(updateEntry, chunkWG, last) - - } else { - - noOfBranches := endCount - startCount - newEntry := &TreeEntry{ - level: int(lvl + 1), - branchCount: noOfBranches, - subtreeSize: 0, - chunk: make([]byte, (noOfBranches*pc.hashSize)+8), - key: make([]byte, pc.hashSize), - index: int(nextLvlCount), - updatePending: false, - } - - index := int64(0) - for i := startCount; i < endCount; i++ { - entry := pc.chunkLevel[lvl][i] - newEntry.subtreeSize += entry.subtreeSize - copy(newEntry.chunk[8+(index*pc.hashSize):8+((index+1)*pc.hashSize)], entry.key[:pc.hashSize]) - index++ - } - // Lonely chunk key is the key of the last chunk that is only one on the last branch. - // In this case, ignore the its tree chunk key and replace it with the lonely chunk key. - if lonelyChunkKey != nil { - // Overwrite the last tree chunk key with the lonely data chunk key. - copy(newEntry.chunk[int64(len(newEntry.chunk))-pc.hashSize:], lonelyChunkKey[:pc.hashSize]) - } - - pc.enqueueTreeChunk(newEntry, chunkWG, last) - - } - - } - - if !isAppend { - chunkWG.Wait() - if compress { - pc.chunkLevel[lvl] = nil - } - } - } - -} - -func (pc *PyramidChunker) enqueueTreeChunk(ent *TreeEntry, chunkWG *sync.WaitGroup, last bool) { - if ent != nil && ent.branchCount > 0 { - - // wait for data chunks to get over before processing the tree chunk - if last { - chunkWG.Wait() - } - - binary.LittleEndian.PutUint64(ent.chunk[:8], ent.subtreeSize) - ent.key = make([]byte, pc.hashSize) - chunkWG.Add(1) - select { - case pc.jobC <- &chunkJob{ent.key, ent.chunk[:ent.branchCount*pc.hashSize+8], chunkWG}: - case <-pc.quitC: - } - - // Update or append based on weather it is a new entry or being reused - if ent.updatePending { - chunkWG.Wait() - pc.chunkLevel[ent.level][ent.index] = ent - } else { - pc.chunkLevel[ent.level] = append(pc.chunkLevel[ent.level], ent) - } - - } -} - -func (pc *PyramidChunker) enqueueDataChunk(chunkData []byte, size uint64, parent *TreeEntry, chunkWG *sync.WaitGroup) Address { - binary.LittleEndian.PutUint64(chunkData[:8], size) - pkey := parent.chunk[8+parent.branchCount*pc.hashSize : 8+(parent.branchCount+1)*pc.hashSize] - - chunkWG.Add(1) - select { - case pc.jobC <- &chunkJob{pkey, chunkData[:size+8], chunkWG}: - case <-pc.quitC: - } - - return pkey - -} - -// depth returns the number of chunk levels. -// It is used to detect if there is only one data chunk -// left for the last branch. -func (pc *PyramidChunker) depth() (d int) { - for _, l := range pc.chunkLevel { - if l == nil { - return - } - d++ - } - return -} - -// cleanChunkLevels removes gaps (nil levels) between chunk levels -// that are not nil. -func (pc *PyramidChunker) cleanChunkLevels() { - for i, l := range pc.chunkLevel { - if l == nil { - pc.chunkLevel = append(pc.chunkLevel[:i], append(pc.chunkLevel[i+1:], nil)...) - } - } -} diff --git a/swarm/storage/schema.go b/swarm/storage/schema.go deleted file mode 100644 index 91847ca0f9a4..000000000000 --- a/swarm/storage/schema.go +++ /dev/null @@ -1,17 +0,0 @@ -package storage - -// The DB schema we want to use. The actual/current DB schema might differ -// until migrations are run. -const CurrentDbSchema = DbSchemaHalloween - -// There was a time when we had no schema at all. -const DbSchemaNone = "" - -// "purity" is the first formal schema of LevelDB we release together with Swarm 0.3.5 -const DbSchemaPurity = "purity" - -// "halloween" is here because we had a screw in the garbage collector index. -// Because of that we had to rebuild the GC index to get rid of erroneous -// entries and that takes a long time. This schema is used for bookkeeping, -// so rebuild index will run just once. -const DbSchemaHalloween = "halloween" diff --git a/swarm/storage/swarmhasher.go b/swarm/storage/swarmhasher.go deleted file mode 100644 index fae03f0c72fe..000000000000 --- a/swarm/storage/swarmhasher.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package storage - -import ( - "hash" -) - -const ( - BMTHash = "BMT" - SHA3Hash = "SHA3" // http://golang.org/pkg/hash/#Hash - DefaultHash = BMTHash -) - -type SwarmHash interface { - hash.Hash - ResetWithLength([]byte) -} - -type HashWithLength struct { - hash.Hash -} - -func (h *HashWithLength) ResetWithLength(length []byte) { - h.Reset() - h.Write(length) -} diff --git a/swarm/storage/types.go b/swarm/storage/types.go deleted file mode 100644 index 28c10a47976c..000000000000 --- a/swarm/storage/types.go +++ /dev/null @@ -1,330 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package storage - -import ( - "bytes" - "context" - "crypto" - "crypto/rand" - "encoding/binary" - "fmt" - "io" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/swarm/bmt" - ch "github.com/ubiq/go-ubiq/swarm/chunk" - "golang.org/x/crypto/sha3" -) - -const MaxPO = 16 -const AddressLength = 32 - -type SwarmHasher func() SwarmHash - -type Address []byte - -// Proximity(x, y) returns the proximity order of the MSB distance between x and y -// -// The distance metric MSB(x, y) of two equal length byte sequences x an y is the -// value of the binary integer cast of the x^y, ie., x and y bitwise xor-ed. -// the binary cast is big endian: most significant bit first (=MSB). -// -// Proximity(x, y) is a discrete logarithmic scaling of the MSB distance. -// It is defined as the reverse rank of the integer part of the base 2 -// logarithm of the distance. -// It is calculated by counting the number of common leading zeros in the (MSB) -// binary representation of the x^y. -// -// (0 farthest, 255 closest, 256 self) -func Proximity(one, other []byte) (ret int) { - b := (MaxPO-1)/8 + 1 - if b > len(one) { - b = len(one) - } - m := 8 - for i := 0; i < b; i++ { - oxo := one[i] ^ other[i] - for j := 0; j < m; j++ { - if (oxo>>uint8(7-j))&0x01 != 0 { - return i*8 + j - } - } - } - return MaxPO -} - -var ZeroAddr = Address(common.Hash{}.Bytes()) - -func MakeHashFunc(hash string) SwarmHasher { - switch hash { - case "SHA256": - return func() SwarmHash { return &HashWithLength{crypto.SHA256.New()} } - case "SHA3": - return func() SwarmHash { return &HashWithLength{sha3.NewLegacyKeccak256()} } - case "BMT": - return func() SwarmHash { - hasher := sha3.NewLegacyKeccak256 - hasherSize := hasher().Size() - segmentCount := ch.DefaultSize / hasherSize - pool := bmt.NewTreePool(hasher, segmentCount, bmt.PoolSize) - return bmt.New(pool) - } - } - return nil -} - -func (a Address) Hex() string { - return fmt.Sprintf("%064x", []byte(a[:])) -} - -func (a Address) Log() string { - if len(a[:]) < 8 { - return fmt.Sprintf("%x", []byte(a[:])) - } - return fmt.Sprintf("%016x", []byte(a[:8])) -} - -func (a Address) String() string { - return fmt.Sprintf("%064x", []byte(a)) -} - -func (a Address) MarshalJSON() (out []byte, err error) { - return []byte(`"` + a.String() + `"`), nil -} - -func (a *Address) UnmarshalJSON(value []byte) error { - s := string(value) - *a = make([]byte, 32) - h := common.Hex2Bytes(s[1 : len(s)-1]) - copy(*a, h) - return nil -} - -type AddressCollection []Address - -func NewAddressCollection(l int) AddressCollection { - return make(AddressCollection, l) -} - -func (c AddressCollection) Len() int { - return len(c) -} - -func (c AddressCollection) Less(i, j int) bool { - return bytes.Compare(c[i], c[j]) == -1 -} - -func (c AddressCollection) Swap(i, j int) { - c[i], c[j] = c[j], c[i] -} - -// Chunk interface implemented by context.Contexts and data chunks -type Chunk interface { - Address() Address - Data() []byte -} - -type chunk struct { - addr Address - sdata []byte - span int64 -} - -func NewChunk(addr Address, data []byte) *chunk { - return &chunk{ - addr: addr, - sdata: data, - span: -1, - } -} - -func (c *chunk) Address() Address { - return c.addr -} - -func (c *chunk) Data() []byte { - return c.sdata -} - -// String() for pretty printing -func (self *chunk) String() string { - return fmt.Sprintf("Address: %v TreeSize: %v Chunksize: %v", self.addr.Log(), self.span, len(self.sdata)) -} - -func GenerateRandomChunk(dataSize int64) Chunk { - hasher := MakeHashFunc(DefaultHash)() - sdata := make([]byte, dataSize+8) - rand.Read(sdata[8:]) - binary.LittleEndian.PutUint64(sdata[:8], uint64(dataSize)) - hasher.ResetWithLength(sdata[:8]) - hasher.Write(sdata[8:]) - return NewChunk(hasher.Sum(nil), sdata) -} - -func GenerateRandomChunks(dataSize int64, count int) (chunks []Chunk) { - for i := 0; i < count; i++ { - ch := GenerateRandomChunk(dataSize) - chunks = append(chunks, ch) - } - return chunks -} - -// Size, Seek, Read, ReadAt -type LazySectionReader interface { - Context() context.Context - Size(context.Context, chan bool) (int64, error) - io.Seeker - io.Reader - io.ReaderAt -} - -type LazyTestSectionReader struct { - *io.SectionReader -} - -func (r *LazyTestSectionReader) Size(context.Context, chan bool) (int64, error) { - return r.SectionReader.Size(), nil -} - -func (r *LazyTestSectionReader) Context() context.Context { - return context.TODO() -} - -type StoreParams struct { - Hash SwarmHasher `toml:"-"` - DbCapacity uint64 - CacheCapacity uint - BaseKey []byte -} - -func NewDefaultStoreParams() *StoreParams { - return NewStoreParams(defaultLDBCapacity, defaultCacheCapacity, nil, nil) -} - -func NewStoreParams(ldbCap uint64, cacheCap uint, hash SwarmHasher, basekey []byte) *StoreParams { - if basekey == nil { - basekey = make([]byte, 32) - } - if hash == nil { - hash = MakeHashFunc(DefaultHash) - } - return &StoreParams{ - Hash: hash, - DbCapacity: ldbCap, - CacheCapacity: cacheCap, - BaseKey: basekey, - } -} - -type ChunkData []byte - -type Reference []byte - -// Putter is responsible to store data and create a reference for it -type Putter interface { - Put(context.Context, ChunkData) (Reference, error) - // RefSize returns the length of the Reference created by this Putter - RefSize() int64 - // Close is to indicate that no more chunk data will be Put on this Putter - Close() - // Wait returns if all data has been store and the Close() was called. - Wait(context.Context) error -} - -// Getter is an interface to retrieve a chunk's data by its reference -type Getter interface { - Get(context.Context, Reference) (ChunkData, error) -} - -// NOTE: this returns invalid data if chunk is encrypted -func (c ChunkData) Size() uint64 { - return binary.LittleEndian.Uint64(c[:8]) -} - -type ChunkValidator interface { - Validate(chunk Chunk) bool -} - -// Provides method for validation of content address in chunks -// Holds the corresponding hasher to create the address -type ContentAddressValidator struct { - Hasher SwarmHasher -} - -// Constructor -func NewContentAddressValidator(hasher SwarmHasher) *ContentAddressValidator { - return &ContentAddressValidator{ - Hasher: hasher, - } -} - -// Validate that the given key is a valid content address for the given data -func (v *ContentAddressValidator) Validate(chunk Chunk) bool { - data := chunk.Data() - if l := len(data); l < 9 || l > ch.DefaultSize+8 { - // log.Error("invalid chunk size", "chunk", addr.Hex(), "size", l) - return false - } - - hasher := v.Hasher() - hasher.ResetWithLength(data[:8]) - hasher.Write(data[8:]) - hash := hasher.Sum(nil) - - return bytes.Equal(hash, chunk.Address()) -} - -type ChunkStore interface { - Put(ctx context.Context, ch Chunk) (err error) - Get(rctx context.Context, ref Address) (ch Chunk, err error) - Has(rctx context.Context, ref Address) bool - Close() -} - -// SyncChunkStore is a ChunkStore which supports syncing -type SyncChunkStore interface { - ChunkStore - BinIndex(po uint8) uint64 - Iterator(from uint64, to uint64, po uint8, f func(Address, uint64) bool) error - FetchFunc(ctx context.Context, ref Address) func(context.Context) error -} - -// FakeChunkStore doesn't store anything, just implements the ChunkStore interface -// It can be used to inject into a hasherStore if you don't want to actually store data just do the -// hashing -type FakeChunkStore struct { -} - -// Put doesn't store anything it is just here to implement ChunkStore -func (f *FakeChunkStore) Put(_ context.Context, ch Chunk) error { - return nil -} - -// Has doesn't do anything it is just here to implement ChunkStore -func (f *FakeChunkStore) Has(_ context.Context, ref Address) bool { - panic("FakeChunkStore doesn't support HasChunk") -} - -// Get doesn't store anything it is just here to implement ChunkStore -func (f *FakeChunkStore) Get(_ context.Context, ref Address) (Chunk, error) { - panic("FakeChunkStore doesn't support Get") -} - -// Close doesn't store anything it is just here to implement ChunkStore -func (f *FakeChunkStore) Close() { -} diff --git a/swarm/storage/types_test.go b/swarm/storage/types_test.go deleted file mode 100644 index 32907bbf4903..000000000000 --- a/swarm/storage/types_test.go +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package storage - -import ( - "strconv" - "testing" -) - -// TestProximity validates Proximity function with explicit -// values in a table-driven test. It is highly dependant on -// MaxPO constant and it validates cases up to MaxPO=32. -func TestProximity(t *testing.T) { - // integer from base2 encoded string - bx := func(s string) uint8 { - i, err := strconv.ParseUint(s, 2, 8) - if err != nil { - t.Fatal(err) - } - return uint8(i) - } - // adjust expected bins in respect to MaxPO - limitPO := func(po uint8) uint8 { - if po > MaxPO { - return MaxPO - } - return po - } - base := []byte{bx("00000000"), bx("00000000"), bx("00000000"), bx("00000000")} - for _, tc := range []struct { - addr []byte - po uint8 - }{ - { - addr: base, - po: MaxPO, - }, - { - addr: []byte{bx("10000000"), bx("00000000"), bx("00000000"), bx("00000000")}, - po: limitPO(0), - }, - { - addr: []byte{bx("01000000"), bx("00000000"), bx("00000000"), bx("00000000")}, - po: limitPO(1), - }, - { - addr: []byte{bx("00100000"), bx("00000000"), bx("00000000"), bx("00000000")}, - po: limitPO(2), - }, - { - addr: []byte{bx("00010000"), bx("00000000"), bx("00000000"), bx("00000000")}, - po: limitPO(3), - }, - { - addr: []byte{bx("00001000"), bx("00000000"), bx("00000000"), bx("00000000")}, - po: limitPO(4), - }, - { - addr: []byte{bx("00000100"), bx("00000000"), bx("00000000"), bx("00000000")}, - po: limitPO(5), - }, - { - addr: []byte{bx("00000010"), bx("00000000"), bx("00000000"), bx("00000000")}, - po: limitPO(6), - }, - { - addr: []byte{bx("00000001"), bx("00000000"), bx("00000000"), bx("00000000")}, - po: limitPO(7), - }, - { - addr: []byte{bx("00000000"), bx("10000000"), bx("00000000"), bx("00000000")}, - po: limitPO(8), - }, - { - addr: []byte{bx("00000000"), bx("01000000"), bx("00000000"), bx("00000000")}, - po: limitPO(9), - }, - { - addr: []byte{bx("00000000"), bx("00100000"), bx("00000000"), bx("00000000")}, - po: limitPO(10), - }, - { - addr: []byte{bx("00000000"), bx("00010000"), bx("00000000"), bx("00000000")}, - po: limitPO(11), - }, - { - addr: []byte{bx("00000000"), bx("00001000"), bx("00000000"), bx("00000000")}, - po: limitPO(12), - }, - { - addr: []byte{bx("00000000"), bx("00000100"), bx("00000000"), bx("00000000")}, - po: limitPO(13), - }, - { - addr: []byte{bx("00000000"), bx("00000010"), bx("00000000"), bx("00000000")}, - po: limitPO(14), - }, - { - addr: []byte{bx("00000000"), bx("00000001"), bx("00000000"), bx("00000000")}, - po: limitPO(15), - }, - { - addr: []byte{bx("00000000"), bx("00000000"), bx("10000000"), bx("00000000")}, - po: limitPO(16), - }, - { - addr: []byte{bx("00000000"), bx("00000000"), bx("01000000"), bx("00000000")}, - po: limitPO(17), - }, - { - addr: []byte{bx("00000000"), bx("00000000"), bx("00100000"), bx("00000000")}, - po: limitPO(18), - }, - { - addr: []byte{bx("00000000"), bx("00000000"), bx("00010000"), bx("00000000")}, - po: limitPO(19), - }, - { - addr: []byte{bx("00000000"), bx("00000000"), bx("00001000"), bx("00000000")}, - po: limitPO(20), - }, - { - addr: []byte{bx("00000000"), bx("00000000"), bx("00000100"), bx("00000000")}, - po: limitPO(21), - }, - { - addr: []byte{bx("00000000"), bx("00000000"), bx("00000010"), bx("00000000")}, - po: limitPO(22), - }, - { - addr: []byte{bx("00000000"), bx("00000000"), bx("00000001"), bx("00000000")}, - po: limitPO(23), - }, - { - addr: []byte{bx("00000000"), bx("00000000"), bx("00000000"), bx("10000000")}, - po: limitPO(24), - }, - { - addr: []byte{bx("00000000"), bx("00000000"), bx("00000000"), bx("01000000")}, - po: limitPO(25), - }, - { - addr: []byte{bx("00000000"), bx("00000000"), bx("00000000"), bx("00100000")}, - po: limitPO(26), - }, - { - addr: []byte{bx("00000000"), bx("00000000"), bx("00000000"), bx("00010000")}, - po: limitPO(27), - }, - { - addr: []byte{bx("00000000"), bx("00000000"), bx("00000000"), bx("00001000")}, - po: limitPO(28), - }, - { - addr: []byte{bx("00000000"), bx("00000000"), bx("00000000"), bx("00000100")}, - po: limitPO(29), - }, - { - addr: []byte{bx("00000000"), bx("00000000"), bx("00000000"), bx("00000010")}, - po: limitPO(30), - }, - { - addr: []byte{bx("00000000"), bx("00000000"), bx("00000000"), bx("00000001")}, - po: limitPO(31), - }, - } { - got := uint8(Proximity(base, tc.addr)) - if got != tc.po { - t.Errorf("got %v bin, want %v", got, tc.po) - } - } -} diff --git a/swarm/swap/swap.go b/swarm/swap/swap.go deleted file mode 100644 index 6a79a070b2d3..000000000000 --- a/swarm/swap/swap.go +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package swap - -import ( - "errors" - "fmt" - "strconv" - "sync" - - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/protocols" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/state" -) - -// SwAP Swarm Accounting Protocol -// a peer to peer micropayment system -// A node maintains an individual balance with every peer -// Only messages which have a price will be accounted for -type Swap struct { - stateStore state.Store //stateStore is needed in order to keep balances across sessions - lock sync.RWMutex //lock the balances - balances map[enode.ID]int64 //map of balances for each peer -} - -// New - swap constructor -func New(stateStore state.Store) (swap *Swap) { - swap = &Swap{ - stateStore: stateStore, - balances: make(map[enode.ID]int64), - } - return -} - -//Swap implements the protocols.Balance interface -//Add is the (sole) accounting function -func (s *Swap) Add(amount int64, peer *protocols.Peer) (err error) { - s.lock.Lock() - defer s.lock.Unlock() - - //load existing balances from the state store - err = s.loadState(peer) - if err != nil && err != state.ErrNotFound { - return - } - //adjust the balance - //if amount is negative, it will decrease, otherwise increase - s.balances[peer.ID()] += amount - //save the new balance to the state store - peerBalance := s.balances[peer.ID()] - err = s.stateStore.Put(peer.ID().String(), &peerBalance) - - log.Debug(fmt.Sprintf("balance for peer %s: %s", peer.ID().String(), strconv.FormatInt(peerBalance, 10))) - return err -} - -//GetPeerBalance returns the balance for a given peer -func (swap *Swap) GetPeerBalance(peer enode.ID) (int64, error) { - swap.lock.RLock() - defer swap.lock.RUnlock() - if p, ok := swap.balances[peer]; ok { - return p, nil - } - return 0, errors.New("Peer not found") -} - -//load balances from the state store (persisted) -func (s *Swap) loadState(peer *protocols.Peer) (err error) { - var peerBalance int64 - peerID := peer.ID() - //only load if the current instance doesn't already have this peer's - //balance in memory - if _, ok := s.balances[peerID]; !ok { - err = s.stateStore.Get(peerID.String(), &peerBalance) - s.balances[peerID] = peerBalance - } - return -} - -//Clean up Swap -func (swap *Swap) Close() { - swap.stateStore.Close() -} diff --git a/swarm/swap/swap_test.go b/swarm/swap/swap_test.go deleted file mode 100644 index fc94d6bc9489..000000000000 --- a/swarm/swap/swap_test.go +++ /dev/null @@ -1,184 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package swap - -import ( - "flag" - "fmt" - "io/ioutil" - mrand "math/rand" - "os" - "testing" - "time" - - "github.com/ubiq/go-ubiq/log" - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/p2p/protocols" - "github.com/ubiq/go-ubiq/p2p/simulations/adapters" - "github.com/ubiq/go-ubiq/swarm/state" - colorable "github.com/mattn/go-colorable" -) - -var ( - loglevel = flag.Int("loglevel", 2, "verbosity of logs") -) - -func init() { - flag.Parse() - mrand.Seed(time.Now().UnixNano()) - - log.PrintOrigins(true) - log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*loglevel), log.StreamHandler(colorable.NewColorableStderr(), log.TerminalFormat(true)))) -} - -//Test getting a peer's balance -func TestGetPeerBalance(t *testing.T) { - //create a test swap account - swap, testDir := createTestSwap(t) - defer os.RemoveAll(testDir) - - //test for correct value - testPeer := newDummyPeer() - swap.balances[testPeer.ID()] = 888 - b, err := swap.GetPeerBalance(testPeer.ID()) - if err != nil { - t.Fatal(err) - } - if b != 888 { - t.Fatalf("Expected peer's balance to be %d, but is %d", 888, b) - } - - //test for inexistent node - id := adapters.RandomNodeConfig().ID - _, err = swap.GetPeerBalance(id) - if err == nil { - t.Fatal("Expected call to fail, but it didn't!") - } - if err.Error() != "Peer not found" { - t.Fatalf("Expected test to fail with %s, but is %s", "Peer not found", err.Error()) - } -} - -//Test that repeated bookings do correct accounting -func TestRepeatedBookings(t *testing.T) { - //create a test swap account - swap, testDir := createTestSwap(t) - defer os.RemoveAll(testDir) - - testPeer := newDummyPeer() - amount := mrand.Intn(100) - cnt := 1 + mrand.Intn(10) - for i := 0; i < cnt; i++ { - swap.Add(int64(amount), testPeer.Peer) - } - expectedBalance := int64(cnt * amount) - realBalance := swap.balances[testPeer.ID()] - if expectedBalance != realBalance { - t.Fatal(fmt.Sprintf("After %d credits of %d, expected balance to be: %d, but is: %d", cnt, amount, expectedBalance, realBalance)) - } - - testPeer2 := newDummyPeer() - amount = mrand.Intn(100) - cnt = 1 + mrand.Intn(10) - for i := 0; i < cnt; i++ { - swap.Add(0-int64(amount), testPeer2.Peer) - } - expectedBalance = int64(0 - (cnt * amount)) - realBalance = swap.balances[testPeer2.ID()] - if expectedBalance != realBalance { - t.Fatal(fmt.Sprintf("After %d debits of %d, expected balance to be: %d, but is: %d", cnt, amount, expectedBalance, realBalance)) - } - - //mixed debits and credits - amount1 := mrand.Intn(100) - amount2 := mrand.Intn(55) - amount3 := mrand.Intn(999) - swap.Add(int64(amount1), testPeer2.Peer) - swap.Add(int64(0-amount2), testPeer2.Peer) - swap.Add(int64(0-amount3), testPeer2.Peer) - - expectedBalance = expectedBalance + int64(amount1-amount2-amount3) - realBalance = swap.balances[testPeer2.ID()] - - if expectedBalance != realBalance { - t.Fatal(fmt.Sprintf("After mixed debits and credits, expected balance to be: %d, but is: %d", expectedBalance, realBalance)) - } -} - -//try restoring a balance from state store -//this is simulated by creating a node, -//assigning it an arbitrary balance, -//then closing the state store. -//Then we re-open the state store and check that -//the balance is still the same -func TestRestoreBalanceFromStateStore(t *testing.T) { - //create a test swap account - swap, testDir := createTestSwap(t) - defer os.RemoveAll(testDir) - - testPeer := newDummyPeer() - swap.balances[testPeer.ID()] = -8888 - - tmpBalance := swap.balances[testPeer.ID()] - swap.stateStore.Put(testPeer.ID().String(), &tmpBalance) - - swap.stateStore.Close() - swap.stateStore = nil - - stateStore, err := state.NewDBStore(testDir) - if err != nil { - t.Fatal(err) - } - - var newBalance int64 - stateStore.Get(testPeer.ID().String(), &newBalance) - - //compare the balances - if tmpBalance != newBalance { - t.Fatal(fmt.Sprintf("Unexpected balance value after sending cheap message test. Expected balance: %d, balance is: %d", - tmpBalance, newBalance)) - } -} - -//create a test swap account -//creates a stateStore for persistence and a Swap account -func createTestSwap(t *testing.T) (*Swap, string) { - dir, err := ioutil.TempDir("", "swap_test_store") - if err != nil { - t.Fatal(err) - } - stateStore, err2 := state.NewDBStore(dir) - if err2 != nil { - t.Fatal(err2) - } - swap := New(stateStore) - return swap, dir -} - -type dummyPeer struct { - *protocols.Peer -} - -//creates a dummy protocols.Peer with dummy MsgReadWriter -func newDummyPeer() *dummyPeer { - id := adapters.RandomNodeConfig().ID - protoPeer := protocols.NewPeer(p2p.NewPeer(id, "testPeer", nil), nil, nil) - dummy := &dummyPeer{ - Peer: protoPeer, - } - return dummy -} diff --git a/swarm/swarm.go b/swarm/swarm.go deleted file mode 100644 index 7ca656afa015..000000000000 --- a/swarm/swarm.go +++ /dev/null @@ -1,554 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package swarm - -import ( - "bytes" - "context" - "crypto/ecdsa" - "fmt" - "io" - "math/big" - "net" - "path/filepath" - "strings" - "time" - "unicode" - - "github.com/ubiq/go-ubiq/accounts/abi/bind" - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/contracts/chequebook" - "github.com/ubiq/go-ubiq/contracts/ens" - "github.com/ubiq/go-ubiq/ethclient" - "github.com/ubiq/go-ubiq/metrics" - "github.com/ubiq/go-ubiq/p2p" - "github.com/ubiq/go-ubiq/p2p/enode" - "github.com/ubiq/go-ubiq/p2p/protocols" - "github.com/ubiq/go-ubiq/params" - "github.com/ubiq/go-ubiq/rpc" - "github.com/ubiq/go-ubiq/swarm/api" - httpapi "github.com/ubiq/go-ubiq/swarm/api/http" - "github.com/ubiq/go-ubiq/swarm/fuse" - "github.com/ubiq/go-ubiq/swarm/log" - "github.com/ubiq/go-ubiq/swarm/network" - "github.com/ubiq/go-ubiq/swarm/network/stream" - "github.com/ubiq/go-ubiq/swarm/pss" - "github.com/ubiq/go-ubiq/swarm/state" - "github.com/ubiq/go-ubiq/swarm/storage" - "github.com/ubiq/go-ubiq/swarm/storage/feed" - "github.com/ubiq/go-ubiq/swarm/storage/mock" - "github.com/ubiq/go-ubiq/swarm/swap" - "github.com/ubiq/go-ubiq/swarm/tracing" -) - -var ( - updateGaugesPeriod = 5 * time.Second - startCounter = metrics.NewRegisteredCounter("stack,start", nil) - stopCounter = metrics.NewRegisteredCounter("stack,stop", nil) - uptimeGauge = metrics.NewRegisteredGauge("stack.uptime", nil) - requestsCacheGauge = metrics.NewRegisteredGauge("storage.cache.requests.size", nil) -) - -// the swarm stack -type Swarm struct { - config *api.Config // swarm configuration - api *api.API // high level api layer (fs/manifest) - dns api.Resolver // DNS registrar - fileStore *storage.FileStore // distributed preimage archive, the local API to the storage with document level storage/retrieval support - streamer *stream.Registry - bzz *network.Bzz // the logistic manager - backend chequebook.Backend // simple blockchain Backend - privateKey *ecdsa.PrivateKey - netStore *storage.NetStore - sfs *fuse.SwarmFS // need this to cleanup all the active mounts on node exit - ps *pss.Pss - swap *swap.Swap - stateStore *state.DBStore - accountingMetrics *protocols.AccountingMetrics - cleanupFuncs []func() error - - tracerClose io.Closer -} - -// NewSwarm creates a new swarm service instance -// implements node.Service -// If mockStore is not nil, it will be used as the storage for chunk data. -// MockStore should be used only for testing. -func NewSwarm(config *api.Config, mockStore *mock.NodeStore) (self *Swarm, err error) { - if bytes.Equal(common.FromHex(config.PublicKey), storage.ZeroAddr) { - return nil, fmt.Errorf("empty public key") - } - if bytes.Equal(common.FromHex(config.BzzKey), storage.ZeroAddr) { - return nil, fmt.Errorf("empty bzz key") - } - - var backend chequebook.Backend - if config.SwapAPI != "" && config.SwapEnabled { - log.Info("connecting to SWAP API", "url", config.SwapAPI) - backend, err = ethclient.Dial(config.SwapAPI) - if err != nil { - return nil, fmt.Errorf("error connecting to SWAP API %s: %s", config.SwapAPI, err) - } - } - - self = &Swarm{ - config: config, - backend: backend, - privateKey: config.ShiftPrivateKey(), - cleanupFuncs: []func() error{}, - } - log.Debug("Setting up Swarm service components") - - config.HiveParams.Discovery = true - - bzzconfig := &network.BzzConfig{ - NetworkID: config.NetworkID, - OverlayAddr: common.FromHex(config.BzzKey), - HiveParams: config.HiveParams, - LightNode: config.LightNodeEnabled, - BootnodeMode: config.BootnodeMode, - } - - self.stateStore, err = state.NewDBStore(filepath.Join(config.Path, "state-store.db")) - if err != nil { - return - } - - // set up high level api - var resolver *api.MultiResolver - if len(config.EnsAPIs) > 0 { - opts := []api.MultiResolverOption{} - for _, c := range config.EnsAPIs { - tld, endpoint, addr := parseEnsAPIAddress(c) - r, err := newEnsClient(endpoint, addr, config, self.privateKey) - if err != nil { - return nil, err - } - opts = append(opts, api.MultiResolverOptionWithResolver(r, tld)) - - } - resolver = api.NewMultiResolver(opts...) - self.dns = resolver - } - - lstore, err := storage.NewLocalStore(config.LocalStoreParams, mockStore) - if err != nil { - return nil, err - } - - self.netStore, err = storage.NewNetStore(lstore, nil) - if err != nil { - return nil, err - } - - to := network.NewKademlia( - common.FromHex(config.BzzKey), - network.NewKadParams(), - ) - delivery := stream.NewDelivery(to, self.netStore) - self.netStore.NewNetFetcherFunc = network.NewFetcherFactory(delivery.RequestFromPeers, config.DeliverySkipCheck).New - - if config.SwapEnabled { - balancesStore, err := state.NewDBStore(filepath.Join(config.Path, "balances.db")) - if err != nil { - return nil, err - } - self.swap = swap.New(balancesStore) - self.accountingMetrics = protocols.SetupAccountingMetrics(10*time.Second, filepath.Join(config.Path, "metrics.db")) - } - - var nodeID enode.ID - if err := nodeID.UnmarshalText([]byte(config.NodeID)); err != nil { - return nil, err - } - - syncing := stream.SyncingAutoSubscribe - if !config.SyncEnabled || config.LightNodeEnabled { - syncing = stream.SyncingDisabled - } - - retrieval := stream.RetrievalEnabled - if config.LightNodeEnabled { - retrieval = stream.RetrievalClientOnly - } - - registryOptions := &stream.RegistryOptions{ - SkipCheck: config.DeliverySkipCheck, - Syncing: syncing, - Retrieval: retrieval, - SyncUpdateDelay: config.SyncUpdateDelay, - MaxPeerServers: config.MaxStreamPeerServers, - } - self.streamer = stream.NewRegistry(nodeID, delivery, self.netStore, self.stateStore, registryOptions, self.swap) - - // Swarm Hash Merklised Chunking for Arbitrary-length Document/File storage - self.fileStore = storage.NewFileStore(self.netStore, self.config.FileStoreParams) - - var feedsHandler *feed.Handler - fhParams := &feed.HandlerParams{} - - feedsHandler = feed.NewHandler(fhParams) - feedsHandler.SetStore(self.netStore) - - lstore.Validators = []storage.ChunkValidator{ - storage.NewContentAddressValidator(storage.MakeHashFunc(storage.DefaultHash)), - feedsHandler, - } - - err = lstore.Migrate() - if err != nil { - return nil, err - } - - log.Debug("Setup local storage") - - self.bzz = network.NewBzz(bzzconfig, to, self.stateStore, self.streamer.GetSpec(), self.streamer.Run) - - // Pss = postal service over swarm (devp2p over bzz) - self.ps, err = pss.NewPss(to, config.Pss) - if err != nil { - return nil, err - } - if pss.IsActiveHandshake { - pss.SetHandshakeController(self.ps, pss.NewHandshakeParams()) - } - - self.api = api.NewAPI(self.fileStore, self.dns, feedsHandler, self.privateKey) - - self.sfs = fuse.NewSwarmFS(self.api) - log.Debug("Initialized FUSE filesystem") - - return self, nil -} - -// parseEnsAPIAddress parses string according to format -// [tld:][contract-addr@]url and returns ENSClientConfig structure -// with endpoint, contract address and TLD. -func parseEnsAPIAddress(s string) (tld, endpoint string, addr common.Address) { - isAllLetterString := func(s string) bool { - for _, r := range s { - if !unicode.IsLetter(r) { - return false - } - } - return true - } - endpoint = s - if i := strings.Index(endpoint, ":"); i > 0 { - if isAllLetterString(endpoint[:i]) && len(endpoint) > i+2 && endpoint[i+1:i+3] != "//" { - tld = endpoint[:i] - endpoint = endpoint[i+1:] - } - } - if i := strings.Index(endpoint, "@"); i > 0 { - addr = common.HexToAddress(endpoint[:i]) - endpoint = endpoint[i+1:] - } - return -} - -// ensClient provides functionality for api.ResolveValidator -type ensClient struct { - *ens.ENS - *ethclient.Client -} - -// newEnsClient creates a new ENS client for that is a consumer of -// a ENS API on a specific endpoint. It is used as a helper function -// for creating multiple resolvers in NewSwarm function. -func newEnsClient(endpoint string, addr common.Address, config *api.Config, privkey *ecdsa.PrivateKey) (*ensClient, error) { - log.Info("connecting to ENS API", "url", endpoint) - client, err := rpc.Dial(endpoint) - if err != nil { - return nil, fmt.Errorf("error connecting to ENS API %s: %s", endpoint, err) - } - ethClient := ethclient.NewClient(client) - - ensRoot := config.EnsRoot - if addr != (common.Address{}) { - ensRoot = addr - } else { - a, err := detectEnsAddr(client) - if err == nil { - ensRoot = a - } else { - log.Warn(fmt.Sprintf("could not determine ENS contract address, using default %s", ensRoot), "err", err) - } - } - transactOpts := bind.NewKeyedTransactor(privkey) - dns, err := ens.NewENS(transactOpts, ensRoot, ethClient) - if err != nil { - return nil, err - } - log.Debug(fmt.Sprintf("-> Swarm Domain Name Registrar %v @ address %v", endpoint, ensRoot.Hex())) - return &ensClient{ - ENS: dns, - Client: ethClient, - }, err -} - -// detectEnsAddr determines the ENS contract address by getting both the -// version and genesis hash using the client and matching them to either -// mainnet or testnet addresses -func detectEnsAddr(client *rpc.Client) (common.Address, error) { - ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - - var version string - if err := client.CallContext(ctx, &version, "net_version"); err != nil { - return common.Address{}, err - } - - block, err := ethclient.NewClient(client).BlockByNumber(ctx, big.NewInt(0)) - if err != nil { - return common.Address{}, err - } - - switch { - - case version == "1" && block.Hash() == params.MainnetGenesisHash: - log.Info("using Mainnet ENS contract address", "addr", ens.MainNetAddress) - return ens.MainNetAddress, nil - - case version == "3" && block.Hash() == params.TestnetGenesisHash: - log.Info("using Testnet ENS contract address", "addr", ens.TestNetAddress) - return ens.TestNetAddress, nil - - default: - return common.Address{}, fmt.Errorf("unknown version and genesis hash: %s %s", version, block.Hash()) - } -} - -/* -Start is called when the stack is started -* starts the network kademlia hive peer management -* (starts netStore level 0 api) -* starts DPA level 1 api (chunking -> store/retrieve requests) -* (starts level 2 api) -* starts http proxy server -* registers url scheme handlers for bzz, etc -* TODO: start subservices like sword, swear, swarmdns -*/ -// implements the node.Service interface -func (s *Swarm) Start(srv *p2p.Server) error { - startTime := time.Now() - - s.tracerClose = tracing.Closer - - // update uaddr to correct enode - newaddr := s.bzz.UpdateLocalAddr([]byte(srv.Self().String())) - log.Info("Updated bzz local addr", "oaddr", fmt.Sprintf("%x", newaddr.OAddr), "uaddr", fmt.Sprintf("%s", newaddr.UAddr)) - // set chequebook - //TODO: Currently if swap is enabled and no chequebook (or inexistent) contract is provided, the node would crash. - //Once we integrate back the contracts, this check MUST be revisited - if s.config.SwapEnabled && s.config.SwapAPI != "" { - ctx := context.Background() // The initial setup has no deadline. - err := s.SetChequebook(ctx) - if err != nil { - return fmt.Errorf("Unable to set chequebook for SWAP: %v", err) - } - log.Debug(fmt.Sprintf("-> cheque book for SWAP: %v", s.config.Swap.Chequebook())) - } else { - log.Debug(fmt.Sprintf("SWAP disabled: no cheque book set")) - } - - log.Info("Starting bzz service") - - err := s.bzz.Start(srv) - if err != nil { - log.Error("bzz failed", "err", err) - return err - } - log.Info("Swarm network started", "bzzaddr", fmt.Sprintf("%x", s.bzz.Hive.BaseAddr())) - - if s.ps != nil { - s.ps.Start(srv) - } - - // start swarm http proxy server - if s.config.Port != "" { - addr := net.JoinHostPort(s.config.ListenAddr, s.config.Port) - server := httpapi.NewServer(s.api, s.config.Cors) - - if s.config.Cors != "" { - log.Debug("Swarm HTTP proxy CORS headers", "allowedOrigins", s.config.Cors) - } - - log.Debug("Starting Swarm HTTP proxy", "port", s.config.Port) - go func() { - err := server.ListenAndServe(addr) - if err != nil { - log.Error("Could not start Swarm HTTP proxy", "err", err.Error()) - } - }() - } - - doneC := make(chan struct{}) - - s.cleanupFuncs = append(s.cleanupFuncs, func() error { - close(doneC) - return nil - }) - - go func(time.Time) { - for { - select { - case <-time.After(updateGaugesPeriod): - uptimeGauge.Update(time.Since(startTime).Nanoseconds()) - requestsCacheGauge.Update(int64(s.netStore.RequestsCacheLen())) - case <-doneC: - return - } - } - }(startTime) - - startCounter.Inc(1) - s.streamer.Start(srv) - return nil -} - -// implements the node.Service interface -// stops all component services. -func (s *Swarm) Stop() error { - if s.tracerClose != nil { - err := s.tracerClose.Close() - if err != nil { - return err - } - } - - if s.ps != nil { - s.ps.Stop() - } - if ch := s.config.Swap.Chequebook(); ch != nil { - ch.Stop() - ch.Save() - } - if s.swap != nil { - s.swap.Close() - } - if s.accountingMetrics != nil { - s.accountingMetrics.Close() - } - if s.netStore != nil { - s.netStore.Close() - } - s.sfs.Stop() - stopCounter.Inc(1) - s.streamer.Stop() - - err := s.bzz.Stop() - if s.stateStore != nil { - s.stateStore.Close() - } - - for _, cleanF := range s.cleanupFuncs { - err = cleanF() - if err != nil { - log.Error("encountered an error while running cleanup function", "err", err) - break - } - } - return err -} - -// Protocols implements the node.Service interface -func (s *Swarm) Protocols() (protos []p2p.Protocol) { - if s.config.BootnodeMode { - protos = append(protos, s.bzz.Protocols()...) - } else { - protos = append(protos, s.bzz.Protocols()...) - - if s.ps != nil { - protos = append(protos, s.ps.Protocols()...) - } - } - return -} - -// implements node.Service -// APIs returns the RPC API descriptors the Swarm implementation offers -func (s *Swarm) APIs() []rpc.API { - - apis := []rpc.API{ - // public APIs - { - Namespace: "bzz", - Version: "3.0", - Service: &Info{s.config, chequebook.ContractParams}, - Public: true, - }, - // admin APIs - { - Namespace: "bzz", - Version: "3.0", - Service: api.NewInspector(s.api, s.bzz.Hive, s.netStore), - Public: false, - }, - { - Namespace: "chequebook", - Version: chequebook.Version, - Service: chequebook.NewAPI(s.config.Swap.Chequebook), - Public: false, - }, - { - Namespace: "swarmfs", - Version: fuse.Swarmfs_Version, - Service: s.sfs, - Public: false, - }, - { - Namespace: "accounting", - Version: protocols.AccountingVersion, - Service: protocols.NewAccountingApi(s.accountingMetrics), - Public: false, - }, - } - - apis = append(apis, s.bzz.APIs()...) - - if s.ps != nil { - apis = append(apis, s.ps.APIs()...) - } - - return apis -} - -// SetChequebook ensures that the local checquebook is set up on chain. -func (s *Swarm) SetChequebook(ctx context.Context) error { - err := s.config.Swap.SetChequebook(ctx, s.backend, s.config.Path) - if err != nil { - return err - } - log.Info(fmt.Sprintf("new chequebook set (%v): saving config file, resetting all connections in the hive", s.config.Swap.Contract.Hex())) - return nil -} - -// RegisterPssProtocol adds a devp2p protocol to the swarm node's Pss instance -func (s *Swarm) RegisterPssProtocol(topic *pss.Topic, spec *protocols.Spec, targetprotocol *p2p.Protocol, options *pss.ProtocolParams) (*pss.Protocol, error) { - return pss.RegisterProtocol(s.ps, topic, spec, targetprotocol, options) -} - -// serialisable info about swarm -type Info struct { - *api.Config - *chequebook.Params -} - -func (s *Info) Info() *Info { - return s -} diff --git a/swarm/swarm_test.go b/swarm/swarm_test.go deleted file mode 100644 index 4b53e84d1286..000000000000 --- a/swarm/swarm_test.go +++ /dev/null @@ -1,375 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package swarm - -import ( - "context" - "encoding/hex" - "io/ioutil" - "math/rand" - "os" - "path" - "runtime" - "strings" - "testing" - "time" - - "github.com/ubiq/go-ubiq/common" - "github.com/ubiq/go-ubiq/crypto" - "github.com/ubiq/go-ubiq/rpc" - "github.com/ubiq/go-ubiq/swarm/api" -) - -// TestNewSwarm validates Swarm fields in repsect to the provided configuration. -func TestNewSwarm(t *testing.T) { - dir, err := ioutil.TempDir("", "swarm") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - - // a simple rpc endpoint for testing dialing - ipcEndpoint := path.Join(dir, "TestSwarm.ipc") - - // windows namedpipes are not on filesystem but on NPFS - if runtime.GOOS == "windows" { - b := make([]byte, 8) - rand.Read(b) - ipcEndpoint = `\\.\pipe\TestSwarm-` + hex.EncodeToString(b) - } - - _, server, err := rpc.StartIPCEndpoint(ipcEndpoint, nil) - if err != nil { - t.Error(err) - } - defer server.Stop() - - for _, tc := range []struct { - name string - configure func(*api.Config) - check func(*testing.T, *Swarm, *api.Config) - }{ - { - name: "defaults", - configure: nil, - check: func(t *testing.T, s *Swarm, config *api.Config) { - if s.config != config { - t.Error("config is not the same object") - } - if s.backend != nil { - t.Error("backend is not nil") - } - if s.privateKey == nil { - t.Error("private key is not set") - } - if !s.config.HiveParams.Discovery { - t.Error("config.HiveParams.Discovery is false, must be true regardless the configuration") - } - if s.dns != nil { - t.Error("dns initialized, but it should not be") - } - if s.netStore == nil { - t.Error("netStore not initialized") - } - if s.streamer == nil { - t.Error("streamer not initialized") - } - if s.fileStore == nil { - t.Error("fileStore not initialized") - } - if s.bzz == nil { - t.Error("bzz not initialized") - } - if s.ps == nil { - t.Error("pss not initialized") - } - if s.api == nil { - t.Error("api not initialized") - } - if s.sfs == nil { - t.Error("swarm filesystem not initialized") - } - }, - }, - { - name: "with swap", - configure: func(config *api.Config) { - config.SwapAPI = ipcEndpoint - config.SwapEnabled = true - }, - check: func(t *testing.T, s *Swarm, _ *api.Config) { - if s.backend == nil { - t.Error("backend is nil") - } - }, - }, - { - name: "with swap disabled", - configure: func(config *api.Config) { - config.SwapAPI = ipcEndpoint - config.SwapEnabled = false - }, - check: func(t *testing.T, s *Swarm, _ *api.Config) { - if s.backend != nil { - t.Error("backend is not nil") - } - }, - }, - { - name: "with swap enabled and api endpoint blank", - configure: func(config *api.Config) { - config.SwapAPI = "" - config.SwapEnabled = true - }, - check: func(t *testing.T, s *Swarm, _ *api.Config) { - if s.backend != nil { - t.Error("backend is not nil") - } - }, - }, - { - name: "ens", - configure: func(config *api.Config) { - config.EnsAPIs = []string{ - "http://127.0.0.1:8888", - } - }, - check: func(t *testing.T, s *Swarm, _ *api.Config) { - if s.dns == nil { - t.Error("dns is not initialized") - } - }, - }, - } { - t.Run(tc.name, func(t *testing.T) { - config := api.NewConfig() - - dir, err := ioutil.TempDir("", "swarm") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - - config.Path = dir - - privkey, err := crypto.GenerateKey() - if err != nil { - t.Fatal(err) - } - - config.Init(privkey) - - if tc.configure != nil { - tc.configure(config) - } - - s, err := NewSwarm(config, nil) - if err != nil { - t.Fatal(err) - } - - if tc.check != nil { - tc.check(t, s, config) - } - }) - } -} - -func TestParseEnsAPIAddress(t *testing.T) { - for _, x := range []struct { - description string - value string - tld string - endpoint string - addr common.Address - }{ - { - description: "IPC endpoint", - value: "/data/testnet/gubiq.ipc", - endpoint: "/data/testnet/gubiq.ipc", - }, - { - description: "HTTP endpoint", - value: "http://127.0.0.1:1234", - endpoint: "http://127.0.0.1:1234", - }, - { - description: "WS endpoint", - value: "ws://127.0.0.1:1234", - endpoint: "ws://127.0.0.1:1234", - }, - { - description: "IPC Endpoint and TLD", - value: "test:/data/testnet/gubiq.ipc", - endpoint: "/data/testnet/gubiq.ipc", - tld: "test", - }, - { - description: "HTTP endpoint and TLD", - value: "test:http://127.0.0.1:1234", - endpoint: "http://127.0.0.1:1234", - tld: "test", - }, - { - description: "WS endpoint and TLD", - value: "test:ws://127.0.0.1:1234", - endpoint: "ws://127.0.0.1:1234", - tld: "test", - }, - { - description: "IPC Endpoint and contract address", - value: "314159265dD8dbb310642f98f50C066173C1259b@/data/testnet/gubiq.ipc", - endpoint: "/data/testnet/gubiq.ipc", - addr: common.HexToAddress("314159265dD8dbb310642f98f50C066173C1259b"), - }, - { - description: "HTTP endpoint and contract address", - value: "314159265dD8dbb310642f98f50C066173C1259b@http://127.0.0.1:1234", - endpoint: "http://127.0.0.1:1234", - addr: common.HexToAddress("314159265dD8dbb310642f98f50C066173C1259b"), - }, - { - description: "WS endpoint and contract address", - value: "314159265dD8dbb310642f98f50C066173C1259b@ws://127.0.0.1:1234", - endpoint: "ws://127.0.0.1:1234", - addr: common.HexToAddress("314159265dD8dbb310642f98f50C066173C1259b"), - }, - { - description: "IPC Endpoint, TLD and contract address", - value: "test:314159265dD8dbb310642f98f50C066173C1259b@/data/testnet/gubiq.ipc", - endpoint: "/data/testnet/gubiq.ipc", - addr: common.HexToAddress("314159265dD8dbb310642f98f50C066173C1259b"), - tld: "test", - }, - { - description: "HTTP endpoint, TLD and contract address", - value: "eth:314159265dD8dbb310642f98f50C066173C1259b@http://127.0.0.1:1234", - endpoint: "http://127.0.0.1:1234", - addr: common.HexToAddress("314159265dD8dbb310642f98f50C066173C1259b"), - tld: "eth", - }, - { - description: "WS endpoint, TLD and contract address", - value: "eth:314159265dD8dbb310642f98f50C066173C1259b@ws://127.0.0.1:1234", - endpoint: "ws://127.0.0.1:1234", - addr: common.HexToAddress("314159265dD8dbb310642f98f50C066173C1259b"), - tld: "eth", - }, - } { - t.Run(x.description, func(t *testing.T) { - tld, endpoint, addr := parseEnsAPIAddress(x.value) - if endpoint != x.endpoint { - t.Errorf("expected Endpoint %q, got %q", x.endpoint, endpoint) - } - if addr != x.addr { - t.Errorf("expected ContractAddress %q, got %q", x.addr.String(), addr.String()) - } - if tld != x.tld { - t.Errorf("expected TLD %q, got %q", x.tld, tld) - } - }) - } -} - -// TestLocalStoreAndRetrieve runs multiple tests where different size files are uploaded -// to a single Swarm instance using API Store and checked against the content returned -// by API Retrieve function. -// -// This test is intended to validate functionality of chunker store and join functions -// and their intergartion into Swarm, without comparing results with ones produced by -// another chunker implementation, as it is done in swarm/storage tests. -func TestLocalStoreAndRetrieve(t *testing.T) { - config := api.NewConfig() - - dir, err := ioutil.TempDir("", "node") - if err != nil { - t.Fatal(err) - } - defer os.RemoveAll(dir) - - config.Path = dir - - privkey, err := crypto.GenerateKey() - if err != nil { - t.Fatal(err) - } - - config.Init(privkey) - - swarm, err := NewSwarm(config, nil) - if err != nil { - t.Fatal(err) - } - - // by default, test only the lonely chunk cases - sizes := []int{1, 60, 4097, 524288 + 1, 7*524288 + 1} - - if *longrunning { - // test broader set of cases if -longruning flag is set - sizes = append(sizes, 83, 179, 253, 1024, 4095, 4096, 8191, 8192, 8193, 12287, 12288, 12289, 123456, 2345678, 67298391, 524288, 524288+4096, 524288+4097, 7*524288, 7*524288+4096, 7*524288+4097, 128*524288+1, 128*524288, 128*524288+4096, 128*524288+4097, 816778334) - } - for _, n := range sizes { - testLocalStoreAndRetrieve(t, swarm, n, true) - testLocalStoreAndRetrieve(t, swarm, n, false) - } -} - -// testLocalStoreAndRetrieve is using a single Swarm instance, to upload -// a file of length n with optional random data using API Store function, -// and checks the output of API Retrieve function on the same instance. -// This is a regression test for issue -// https://github.com/ethersphere/go-ethereum/issues/639 -// where pyramid chunker did not split correctly files with lengths that -// are edge cases for chunk and tree parameters, depending whether there -// is a tree chunk with only one data chunk and how the compress functionality -// changed the tree. -func testLocalStoreAndRetrieve(t *testing.T, swarm *Swarm, n int, randomData bool) { - slice := make([]byte, n) - if randomData { - rand.Seed(time.Now().UnixNano()) - rand.Read(slice) - } - dataPut := string(slice) - - ctx := context.TODO() - k, wait, err := swarm.api.Store(ctx, strings.NewReader(dataPut), int64(len(dataPut)), false) - if err != nil { - t.Fatal(err) - } - if wait != nil { - err = wait(ctx) - if err != nil { - t.Fatal(err) - } - } - - r, _ := swarm.api.Retrieve(context.TODO(), k) - - d, err := ioutil.ReadAll(r) - if err != nil { - t.Fatal(err) - } - dataGet := string(d) - - if len(dataPut) != len(dataGet) { - t.Fatalf("data not matched: length expected %v, got %v", len(dataPut), len(dataGet)) - } else { - if dataPut != dataGet { - t.Fatal("data not matched") - } - } -} diff --git a/swarm/testutil/file.go b/swarm/testutil/file.go deleted file mode 100644 index 70732aa92efe..000000000000 --- a/swarm/testutil/file.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright 2017 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package testutil - -import ( - "bytes" - "io" - "io/ioutil" - "math/rand" - "os" - "strings" - "testing" -) - -// TempFileWithContent is a helper function that creates a temp file that contains the following string content then closes the file handle -// it returns the complete file path -func TempFileWithContent(t *testing.T, content string) string { - tempFile, err := ioutil.TempFile("", "swarm-temp-file") - if err != nil { - t.Fatal(err) - } - - _, err = io.Copy(tempFile, strings.NewReader(content)) - if err != nil { - os.RemoveAll(tempFile.Name()) - t.Fatal(err) - } - if err = tempFile.Close(); err != nil { - t.Fatal(err) - } - return tempFile.Name() -} - -// RandomBytes returns pseudo-random deterministic result -// because test fails must be reproducible -func RandomBytes(seed, length int) []byte { - b := make([]byte, length) - reader := rand.New(rand.NewSource(int64(seed))) - for n := 0; n < length; { - read, err := reader.Read(b[n:]) - if err != nil { - panic(err) - } - n += read - } - return b -} - -func RandomReader(seed, length int) *bytes.Reader { - return bytes.NewReader(RandomBytes(seed, length)) -} diff --git a/swarm/version/version.go b/swarm/version/version.go deleted file mode 100644 index 6b536bded958..000000000000 --- a/swarm/version/version.go +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright 2018 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see . - -package version - -import ( - "fmt" -) - -const ( - VersionMajor = 0 // Major version component of the current release - VersionMinor = 3 // Minor version component of the current release - VersionPatch = 14 // Patch version component of the current release - VersionMeta = "stable" // Version metadata to append to the version string -) - -// Version holds the textual version string. -var Version = func() string { - return fmt.Sprintf("%d.%d.%d", VersionMajor, VersionMinor, VersionPatch) -}() - -// VersionWithMeta holds the textual version string including the metadata. -var VersionWithMeta = func() string { - v := Version - if VersionMeta != "" { - v += "-" + VersionMeta - } - return v -}() - -// Git SHA1 commit hash of the release, will be set by main.init() function -var GitCommit string - -// ArchiveVersion holds the textual version string used for Swarm archives. -// e.g. "0.3.0-dea1ce05" for stable releases, or -// "0.3.1-unstable-21c059b6" for unstable releases -func ArchiveVersion(gitCommit string) string { - vsn := Version - if VersionMeta != "stable" { - vsn += "-" + VersionMeta - } - if len(gitCommit) >= 8 { - vsn += "-" + gitCommit[:8] - } - return vsn -} - -func VersionWithCommit(gitCommit string) string { - vsn := Version - if len(gitCommit) >= 8 { - vsn += "-" + gitCommit[:8] - } - return vsn -} From 7d7bf861e4c3768d12dd2ed5073dff7b504075dc Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Tue, 26 Nov 2019 19:30:41 +0100 Subject: [PATCH 07/29] puppeth: fix ascii border spacing --- cmd/puppeth/wizard_intro.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/puppeth/wizard_intro.go b/cmd/puppeth/wizard_intro.go index b1b7deb6fc12..9140aa746bbc 100644 --- a/cmd/puppeth/wizard_intro.go +++ b/cmd/puppeth/wizard_intro.go @@ -46,9 +46,9 @@ func makeWizard(network string) *wizard { // setting up a new or managing an existing Ubiq private network. func (w *wizard) run() { fmt.Println("+-----------------------------------------------------------+") - fmt.Println("| Welcome to puppeth, your Ubiq private network manager |") + fmt.Println("| Welcome to puppeth, your Ubiq private network manager |") fmt.Println("| |") - fmt.Println("| This tool lets you create a new Ubiq network down to |") + fmt.Println("| This tool lets you create a new Ubiq network down to |") fmt.Println("| the genesis block, bootnodes, miners and ethstats servers |") fmt.Println("| without the hassle that it would normally entail. |") fmt.Println("| |") From fc7596a30d34aed0db94aede590e21b4202641b0 Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Sat, 30 Nov 2019 22:07:22 +0100 Subject: [PATCH 08/29] swarm: update readme --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 559629deee4b..442d02c2cfd9 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Binary archives are published at [releases](https://github.com/ubiq/go-ubiq/rele ## Building the source -For prerequisites and detailed build instructions please read the [Ubiq's Installation Instructions](https://github.com/ubiq/go-ubiq/wiki/Building-Ubiq) on their wiki. +For prerequisites and detailed build instructions please read the [Ubiq's Installation Instructions](https://github.com/ubiq/go-ubiq/wiki/Building-Ubiq) on their wiki. *Note*: Keep in mind that Ubiq aims to be 100% compatible with Ethereum, so mostly all the documentation you can find on Ethereum wiki, will apply for sure to Ubiq. @@ -35,12 +35,11 @@ The go-ubiq project comes with several wrappers/executables found in the `cmd` d | `evm` | Developer utility version of the EVM (Ethereum Virtual Machine) that is capable of running bytecode snippets within a configurable environment and execution mode. Its purpose is to allow isolated, fine-grained debugging of EVM opcodes (e.g. `evm --code 60ff60ff --debug`). | | `gubiqrpctest` | Developer utility tool to support our [ethereum/rpc-test](https://github.com/ethereum/rpc-tests) test suite which validates baseline conformity to the [Ethereum JSON RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) specs. Please see the [test suite's readme](https://github.com/ethereum/rpc-tests/blob/master/README.md) for details. | | `rlpdump` | Developer utility tool to convert binary RLP ([Recursive Length Prefix](https://github.com/ethereum/wiki/wiki/RLP)) dumps (data encoding used by the Ubiq protocol both network as well as consensus wise) to user friendlier hierarchical representation (e.g. `rlpdump --hex CE0183FFFFFFC4C304050583616263`). | -| `swarm` | Swarm daemon and tools. This is the entrypoint for the Swarm network. `swarm --help` for command line options and subcommands. See [Swarm README](https://github.com/ubiq/go-ubiq/tree/master/swarm) for more information. | | `puppeth` | a CLI wizard that aids in creating a new Ubiq network. | ## Running gubiq -Going through all the possible command line flags is out of scope here (please consult +Going through all the possible command line flags is out of scope here (please consult [Ubiq CLI Wiki page](https://github.com/ubiq/go-ubiq/wiki/Command-Line-Options)), but we've enumerated a few common parameter combos to get you up to speed quickly on how you can run your own Gubiq instance. @@ -90,7 +89,7 @@ Specifying the `--testnet` flag however will reconfigure your Gubiq instance a b `gubiq attach /testnet/gubiq.ipc`. Windows users are not affected by this. * Instead of connecting the main Ubiq network, the client will connect to the test network, which uses different P2P bootnodes, different network IDs and genesis states. - + *Note: Although there are some internal protective measures to prevent transactions from crossing over between the main network and test network, you should make sure to always use separate accounts for play-money and real-money. Unless you manually move accounts, Gubiq will by default correctly From da34923c5a5bf7bf8b836692d65bb356e40662de Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Sat, 30 Nov 2019 22:08:43 +0100 Subject: [PATCH 09/29] params: bump version --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index 24505a671322..477c49cd427f 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 2 // Major version component of the current release - VersionMinor = 3 // Minor version component of the current release - VersionPatch = 0 // Patch version component of the current release - VersionMeta = "fleek" // Version metadata to append to the version string + VersionMajor = 3 // Major version component of the current release + VersionMinor = 0 // Minor version component of the current release + VersionPatch = 0 // Patch version component of the current release + VersionMeta = "testing" // Version metadata to append to the version string ) // Version holds the textual version string. From b82d77c399bf26a0933e796741ffb1eb180b5e2d Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Sat, 30 Nov 2019 22:23:14 +0100 Subject: [PATCH 10/29] tests(accounts/hd): fix Hexadecimal absolute derivation path tests --- accounts/hd_test.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/accounts/hd_test.go b/accounts/hd_test.go index 05a8efc7f421..48edb2ce2136 100644 --- a/accounts/hd_test.go +++ b/accounts/hd_test.go @@ -44,10 +44,10 @@ func TestHDPathParsing(t *testing.T) { {"2147483648", DerivationPath{0x80000000 + 44, 0x80000000 + 108, 0x80000000 + 0, 0, 0x80000000 + 0}}, // Hexadecimal absolute derivation paths - {"m/0x2C'/0x3c'/0x00'/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 108, 0x80000000 + 0, 0}}, - {"m/0x2C'/0x3c'/0x00'/0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 108, 0x80000000 + 0, 128}}, - {"m/0x2C'/0x3c'/0x00'/0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 108, 0x80000000 + 0, 0x80000000 + 0}}, - {"m/0x2C'/0x3c'/0x00'/0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 108, 0x80000000 + 0, 0x80000000 + 128}}, + {"m/0x2C'/0x6c'/0x00'/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 108, 0x80000000 + 0, 0}}, + {"m/0x2C'/0x6c'/0x00'/0x80", DerivationPath{0x80000000 + 44, 0x80000000 + 108, 0x80000000 + 0, 128}}, + {"m/0x2C'/0x6c'/0x00'/0x00'", DerivationPath{0x80000000 + 44, 0x80000000 + 108, 0x80000000 + 0, 0x80000000 + 0}}, + {"m/0x2C'/0x6c'/0x00'/0x80'", DerivationPath{0x80000000 + 44, 0x80000000 + 108, 0x80000000 + 0, 0x80000000 + 128}}, {"m/0x8000002C/0x8000006c/0x80000000/0x00", DerivationPath{0x80000000 + 44, 0x80000000 + 108, 0x80000000 + 0, 0}}, {"m/0x8000002C/0x8000006c/0x80000000/0x80000000", DerivationPath{0x80000000 + 44, 0x80000000 + 108, 0x80000000 + 0, 0x80000000 + 0}}, @@ -62,12 +62,12 @@ func TestHDPathParsing(t *testing.T) { {" m / 44 '\n/\n 108 \n\n\t' /\n0 ' /\t\t 0", DerivationPath{0x80000000 + 44, 0x80000000 + 108, 0x80000000 + 0, 0}}, // Invaid derivation paths - {"", nil}, // Empty relative derivation path - {"m", nil}, // Empty absolute derivation path - {"m/", nil}, // Missing last derivation component + {"", nil}, // Empty relative derivation path + {"m", nil}, // Empty absolute derivation path + {"m/", nil}, // Missing last derivation component {"/44'/108'/0'/0", nil}, // Absolute path without m prefix, might be user error - {"m/2147483648'", nil}, // Overflows 32 bit integer - {"m/-1'", nil}, // Cannot contain negative number + {"m/2147483648'", nil}, // Overflows 32 bit integer + {"m/-1'", nil}, // Cannot contain negative number } for i, tt := range tests { if path, err := ParseDerivationPath(tt.input); !reflect.DeepEqual(path, tt.output) { From 29ee8e58badb53658022c704ea70514c88d2f62d Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Wed, 18 Dec 2019 14:16:18 +0100 Subject: [PATCH 11/29] tests(consensus/ubqhash): fix TestSizeCalculations and TestConcurrentDiskCacheGeneration --- consensus/ubqhash/algorithm.go | 4 +--- consensus/ubqhash/algorithm_test.go | 26 +++++++++++++------------- consensus/ubqhash/api.go | 4 ++-- consensus/ubqhash/consensus_test.go | 20 +++++++++++++++++++- 4 files changed, 35 insertions(+), 19 deletions(-) diff --git a/consensus/ubqhash/algorithm.go b/consensus/ubqhash/algorithm.go index 5ac001763643..97c901c6b03d 100644 --- a/consensus/ubqhash/algorithm.go +++ b/consensus/ubqhash/algorithm.go @@ -132,8 +132,6 @@ func blakeHasher(h hash.Hash) hasher { } } - - // seedHash is the seed to use for generating a verification cache and the mining // dataset. func seedHash(block uint64) []byte { @@ -1062,7 +1060,7 @@ var cacheSizes = [maxEpoch]uint64{ 200014016, 200146624, 200276672, 200408128, 200540096, 200671168, 200801984, 200933312, 201062464, 201194944, 201326144, 201457472, 201588544, 201719744, 201850816, 201981632, 202111552, 202244032, - 202374464, 202505152, 202636352, 202767808, 202898368, 203038836, + 202374464, 202505152, 202636352, 202767808, 202898368, 203030336, 203159872, 203292608, 203423296, 203553472, 203685824, 203816896, 203947712, 204078272, 204208192, 204341056, 204472256, 204603328, 204733888, 204864448, 204996544, 205125568, 205258304, 205388864, diff --git a/consensus/ubqhash/algorithm_test.go b/consensus/ubqhash/algorithm_test.go index ad6a6fceff1c..c753b8164518 100644 --- a/consensus/ubqhash/algorithm_test.go +++ b/consensus/ubqhash/algorithm_test.go @@ -706,20 +706,20 @@ func TestConcurrentDiskCacheGeneration(t *testing.T) { // Define a heavy enough block, one from mainnet should do block := types.NewBlockWithHeader(&types.Header{ - Number: big.NewInt(3311058), - ParentHash: common.HexToHash("0xd783efa4d392943503f28438ad5830b2d5964696ffc285f338585e9fe0a37a05"), + Number: big.NewInt(1047270), + ParentHash: common.HexToHash("0x50162f5bb86b04d36ad3c0972942f3ad12012681364a9ecfd851163893973b1e"), UncleHash: common.HexToHash("0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"), - Coinbase: common.HexToAddress("0xc0ea08a2d404d3172d2add29a45be56da40e2949"), - Root: common.HexToHash("0x77d14e10470b5850332524f8cd6f69ad21f070ce92dca33ab2858300242ef2f1"), - TxHash: common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"), - ReceiptHash: common.HexToHash("0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"), - Difficulty: big.NewInt(167925187834220), - GasLimit: 4015682, - GasUsed: 0, - Time: 1488928920, - Extra: []byte("www.bw.com"), - MixDigest: common.HexToHash("0x3e140b0784516af5e5ec6730f2fb20cca22f32be399b9e4ad77d32541f798cd0"), - Nonce: types.EncodeNonce(0xf400cd0006070c49), + Coinbase: common.HexToAddress("0xaeb7897adf9b1309d7ef2dca6f3f6afb65358abd"), + Root: common.HexToHash("0x48450163a72087d7f7c7f8e479ca5f9b46ba2dea65af31374869330438f092bc"), + TxHash: common.HexToHash("0xf3d988c1ba77b327ffe99f8f24d183f1dcca0a858dea397da90d19511f3e5243"), + ReceiptHash: common.HexToHash("0x51f3538babf4e6315ac5843cbb6585b57024cfda57a3fbe51413549944496dcc"), + Difficulty: big.NewInt(4399410101192), + GasLimit: 8000000, + GasUsed: 21000, + Time: 1576671205, + Extra: []byte("ubiqpool.maxhash.org"), + MixDigest: common.HexToHash("0x8ce33f23cc18c394542ab28d35065706293eaf9c101007beb800f7e273266f82"), + Nonce: types.EncodeNonce(0x2b00b2d234c5134e), }) // Simulate multiple processes sharing the same datadir var pend sync.WaitGroup diff --git a/consensus/ubqhash/api.go b/consensus/ubqhash/api.go index 615bf88761fc..2aa7141c26c9 100644 --- a/consensus/ubqhash/api.go +++ b/consensus/ubqhash/api.go @@ -24,7 +24,7 @@ import ( "github.com/ubiq/go-ubiq/core/types" ) -var errEthashStopped = errors.New("ubqhash stopped") +var errUbqhashStopped = errors.New("ubqhash stopped") // API exposes ubqhash related methods for the RPC interface. type API struct { @@ -51,7 +51,7 @@ func (api *API) GetWork() ([4]string, error) { select { case api.ubqhash.fetchWorkCh <- &sealWork{errc: errc, res: workCh}: case <-api.ubqhash.exitCh: - return [4]string{}, errEthashStopped + return [4]string{}, errUbqhashStopped } select { diff --git a/consensus/ubqhash/consensus_test.go b/consensus/ubqhash/consensus_test.go index 4d8e5881c743..5b3953c2f9d4 100644 --- a/consensus/ubqhash/consensus_test.go +++ b/consensus/ubqhash/consensus_test.go @@ -24,7 +24,10 @@ import ( "testing" "github.com/ubiq/go-ubiq/common/math" + "github.com/ubiq/go-ubiq/core" "github.com/ubiq/go-ubiq/core/types" + "github.com/ubiq/go-ubiq/core/vm" + "github.com/ubiq/go-ubiq/ethdb" "github.com/ubiq/go-ubiq/params" ) @@ -72,9 +75,24 @@ func TestCalcDifficulty(t *testing.T) { config := ¶ms.ChainConfig{HomesteadBlock: big.NewInt(1150000)} + var ( + testdb = ethdb.NewMemDatabase() + gspec = &core.Genesis{Config: config} + genesis = gspec.MustCommit(testdb) + blocks, _ = core.GenerateChain(config, genesis, NewFaker(), testdb, 88, nil) + ) + + headers := make([]*types.Header, len(blocks)) + for i, block := range blocks { + headers[i] = block.Header() + } + // Run the header checker for blocks one-by-one, checking for both valid and invalid nonces + chain, _ := core.NewBlockChain(testdb, nil, config, NewFaker(), vm.Config{}, nil) + defer chain.Stop() + for name, test := range tests { number := new(big.Int).Sub(test.CurrentBlocknumber, big.NewInt(1)) - diff := CalcDifficulty(config, test.CurrentTimestamp, &types.Header{ + diff := CalcDifficulty(chain, test.CurrentTimestamp, &types.Header{ Number: number, Time: test.ParentTimestamp, Difficulty: test.ParentDifficulty, From 8b1cff54b920635ff43551883d4bd9645a34dce0 Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Wed, 18 Dec 2019 21:49:51 +0100 Subject: [PATCH 12/29] consensus/ubqhash: add CalcBaseBlockReward & CalcUncleBlockReward (with tests), refactor accumulateRewards --- consensus/ubqhash/consensus.go | 77 ++++++++++--------- consensus/ubqhash/consensus_test.go | 114 ++++++++++++++++++++++++++++ 2 files changed, 157 insertions(+), 34 deletions(-) diff --git a/consensus/ubqhash/consensus.go b/consensus/ubqhash/consensus.go index 8c3eac4532b0..73e68f9c2245 100644 --- a/consensus/ubqhash/consensus.go +++ b/consensus/ubqhash/consensus.go @@ -703,62 +703,71 @@ func (ubqhash *Ubqhash) SealHash(header *types.Header) (hash common.Hash) { return hash } -// AccumulateRewards credits the coinbase of the given block with the mining -// reward. The total reward consists of the static block reward and rewards for -// included uncles. The coinbase of each uncle block is also rewarded. -func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) { +// Calculates the base block reward as per the ubiq monetary policy +func CalcBaseBlockReward(height *big.Int) *big.Int { reward := new(big.Int).Set(blockReward) - if header.Number.Cmp(big.NewInt(358363)) > 0 { + if height.Cmp(big.NewInt(358363)) > 0 { reward = big.NewInt(7e+18) } - if header.Number.Cmp(big.NewInt(716727)) > 0 { + if height.Cmp(big.NewInt(716727)) > 0 { reward = big.NewInt(6e+18) } - if header.Number.Cmp(big.NewInt(1075090)) > 0 { + if height.Cmp(big.NewInt(1075090)) > 0 { reward = big.NewInt(5e+18) } - if header.Number.Cmp(big.NewInt(1433454)) > 0 { + if height.Cmp(big.NewInt(1433454)) > 0 { reward = big.NewInt(4e+18) } - if header.Number.Cmp(big.NewInt(1791818)) > 0 { + if height.Cmp(big.NewInt(1791818)) > 0 { reward = big.NewInt(3e+18) } - if header.Number.Cmp(big.NewInt(2150181)) > 0 { + if height.Cmp(big.NewInt(2150181)) > 0 { reward = big.NewInt(2e+18) } - if header.Number.Cmp(big.NewInt(2508545)) > 0 { + if height.Cmp(big.NewInt(2508545)) > 0 { reward = big.NewInt(1e+18) } + return reward +} + +func CalcUncleBlockReward(config *params.ChainConfig, blockHeight *big.Int, uncleHeight *big.Int, blockReward *big.Int) *big.Int { + reward := new(big.Int) + // calculate reward based on depth + reward.Add(uncleHeight, big2) + reward.Sub(reward, blockHeight) + reward.Mul(reward, blockReward) + reward.Div(reward, big2) + + // negative uncle reward fix. (activates along-side EIP158) + if config.IsEIP158(blockHeight) && reward.Cmp(big.NewInt(0)) < 0 { + reward = big.NewInt(0) + } + return reward +} + +// AccumulateRewards credits the coinbase of the given block with the mining +// reward. The total reward consists of the static block reward and rewards for +// included uncles. The coinbase of each uncle block is also rewarded. +func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) { + // block reward (miner) + reward := CalcBaseBlockReward(header.Number) - // Uncle reward step down fix. + // Uncle reward step down fix. (activates along-side byzantium) ufixReward := new(big.Int).Set(blockReward) if config.IsByzantium(header.Number) { ufixReward = reward } - r := new(big.Int) for _, uncle := range uncles { - r.Add(uncle.Number, big2) - r.Sub(r, header.Number) - r.Mul(r, ufixReward) - r.Div(r, big2) - - if header.Number.Cmp(big.NewInt(10)) < 0 { - state.AddBalance(uncle.Coinbase, r) - r.Div(ufixReward, big32) - if r.Cmp(big.NewInt(0)) < 0 { - r = big.NewInt(0) - } - } else { - if r.Cmp(big.NewInt(0)) < 0 { - r = big.NewInt(0) - } - state.AddBalance(uncle.Coinbase, r) - r.Div(ufixReward, big32) - } - - reward.Add(reward, r) - } + // uncle block miner reward (depth === 1 ? baseBlockReward * 0.5 : 0) + uncleReward := CalcUncleBlockReward(config, header.Number, uncle.Number, ufixReward) + // update uncle miner balance + state.AddBalance(uncle.Coinbase, uncleReward) + // include uncle bonus reward (baseBlockReward/32) + uncleReward.Div(ufixReward, big32) + reward.Add(reward, uncleReward) + } + // update block miner balance state.AddBalance(header.Coinbase, reward) } diff --git a/consensus/ubqhash/consensus_test.go b/consensus/ubqhash/consensus_test.go index 5b3953c2f9d4..04646c5959d0 100644 --- a/consensus/ubqhash/consensus_test.go +++ b/consensus/ubqhash/consensus_test.go @@ -102,3 +102,117 @@ func TestCalcDifficulty(t *testing.T) { } } } + +func TestCalcBaseBlockReward(t *testing.T) { + reward := CalcBaseBlockReward(big.NewInt(1)) + if reward.Cmp(big.NewInt(8e+18)) != 0 { + t.Error("TestCalcBaseBlockReward 8 (start)", "failed. Expected", big.NewInt(8e+18), "and calculated", reward) + } + reward = CalcBaseBlockReward(big.NewInt(358363)) + if reward.Cmp(big.NewInt(8e+18)) != 0 { + t.Error("TestCalcBaseBlockReward 8 (end)", "failed. Expected", big.NewInt(8e+18), "and calculated", reward) + } + + reward = CalcBaseBlockReward(big.NewInt(358364)) + if reward.Cmp(big.NewInt(7e+18)) != 0 { + t.Error("TestCalcBaseBlockReward 7 (start)", "failed. Expected", big.NewInt(7e+18), "and calculated", reward) + } + reward = CalcBaseBlockReward(big.NewInt(716727)) + if reward.Cmp(big.NewInt(7e+18)) != 0 { + t.Error("TestCalcBaseBlockReward 7 (end)", "failed. Expected", big.NewInt(7e+18), "and calculated", reward) + } + + reward = CalcBaseBlockReward(big.NewInt(716728)) + if reward.Cmp(big.NewInt(6e+18)) != 0 { + t.Error("TestCalcBaseBlockReward 6 (start)", "failed. Expected", big.NewInt(6e+18), "and calculated", reward) + } + reward = CalcBaseBlockReward(big.NewInt(1075090)) + if reward.Cmp(big.NewInt(6e+18)) != 0 { + t.Error("TestCalcBaseBlockReward 6 (end)", "failed. Expected", big.NewInt(6e+18), "and calculated", reward) + } + + reward = CalcBaseBlockReward(big.NewInt(1075091)) + if reward.Cmp(big.NewInt(5e+18)) != 0 { + t.Error("TestCalcBaseBlockReward 5 (start)", "failed. Expected", big.NewInt(5e+18), "and calculated", reward) + } + reward = CalcBaseBlockReward(big.NewInt(1433454)) + if reward.Cmp(big.NewInt(5e+18)) != 0 { + t.Error("TestCalcBaseBlockReward 5 (end)", "failed. Expected", big.NewInt(5e+18), "and calculated", reward) + } + + reward = CalcBaseBlockReward(big.NewInt(1433455)) + if reward.Cmp(big.NewInt(4e+18)) != 0 { + t.Error("TestCalcBaseBlockReward 4 (start)", "failed. Expected", big.NewInt(4e+18), "and calculated", reward) + } + reward = CalcBaseBlockReward(big.NewInt(1791818)) + if reward.Cmp(big.NewInt(4e+18)) != 0 { + t.Error("TestCalcBaseBlockReward 4 (end)", "failed. Expected", big.NewInt(4e+18), "and calculated", reward) + } + + reward = CalcBaseBlockReward(big.NewInt(1791819)) + if reward.Cmp(big.NewInt(3e+18)) != 0 { + t.Error("TestCalcBaseBlockReward 3 (start)", "failed. Expected", big.NewInt(3e+18), "and calculated", reward) + } + reward = CalcBaseBlockReward(big.NewInt(2150181)) + if reward.Cmp(big.NewInt(3e+18)) != 0 { + t.Error("TestCalcBaseBlockReward 3 (end)", "failed. Expected", big.NewInt(3e+18), "and calculated", reward) + } + + reward = CalcBaseBlockReward(big.NewInt(2150182)) + if reward.Cmp(big.NewInt(2e+18)) != 0 { + t.Error("TestCalcBaseBlockReward 2 (start)", "failed. Expected", big.NewInt(2e+18), "and calculated", reward) + } + reward = CalcBaseBlockReward(big.NewInt(2508545)) + if reward.Cmp(big.NewInt(2e+18)) != 0 { + t.Error("TestCalcBaseBlockReward 2 (end)", "failed. Expected", big.NewInt(2e+18), "and calculated", reward) + } + + reward = CalcBaseBlockReward(big.NewInt(2508546)) + if reward.Cmp(big.NewInt(1e+18)) != 0 { + t.Error("TestCalcBaseBlockReward 1 (start)", "failed. Expected", big.NewInt(1e+18), "and calculated", reward) + } +} + +func TestCalcUncleBlockReward(t *testing.T) { + config := ¶ms.ChainConfig{HomesteadBlock: big.NewInt(0), EIP158Block: big.NewInt(10)} + reward := big.NewInt(8e+18) + // depth 1 + u := CalcUncleBlockReward(config, big.NewInt(5), big.NewInt(4), reward) + if u.Cmp(big.NewInt(4e+18)) != 0 { + t.Error("TestCalcUncleBlockReward 8", "failed. Expected", big.NewInt(4e+18), "and calculated", u) + } + + // depth 2 + u = CalcUncleBlockReward(config, big.NewInt(8), big.NewInt(6), reward) + if u.Cmp(big.NewInt(0)) != 0 { + t.Error("TestCalcUncleBlockReward 8", "failed. Expected", big.NewInt(0), "and calculated", u) + } + + // depth 3 (before negative fix) + u = CalcUncleBlockReward(config, big.NewInt(8), big.NewInt(5), reward) + if u.Cmp(big.NewInt(-4e+18)) != 0 { + t.Error("TestCalcUncleBlockReward 8", "failed. Expected", big.NewInt(-4e+18), "and calculated", u) + } + + // depth 3 (after negative fix) + u = CalcUncleBlockReward(config, big.NewInt(10), big.NewInt(7), reward) + if u.Cmp(big.NewInt(0)) != 0 { + t.Error("TestCalcUncleBlockReward 8", "failed. Expected", big.NewInt(0), "and calculated", u) + } + + reward = big.NewInt(7e+18) + expected := big.NewInt(35e+17) + // depth 1 (after stepdown) + u = CalcUncleBlockReward(config, big.NewInt(8), big.NewInt(7), reward) + if u.Cmp(expected) != 0 { + t.Error("TestCalcUncleBlockReward 7", "failed. Expected", expected, "and calculated", u) + } + + reward = big.NewInt(5e+18) + expected = big.NewInt(25e+17) + // depth 1 (after stepdown) + u = CalcUncleBlockReward(config, big.NewInt(8), big.NewInt(7), reward) + if u.Cmp(expected) != 0 { + t.Error("TestCalcUncleBlockReward 5", "failed. Expected", expected, "and calculated", u) + } +} From 88b56df9eb04940e98c81afa14ca327a19701d51 Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Fri, 20 Dec 2019 22:21:36 +0100 Subject: [PATCH 13/29] consensus/ubqhash: move diff algo changeover blocks to ChainConfig.Ubqhash --- consensus/ubqhash/consensus.go | 9 +++++---- params/config.go | 17 +++++++++++++---- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/consensus/ubqhash/consensus.go b/consensus/ubqhash/consensus.go index 73e68f9c2245..4beefb7952c2 100644 --- a/consensus/ubqhash/consensus.go +++ b/consensus/ubqhash/consensus.go @@ -51,13 +51,11 @@ var ( nPowMaxAdjustDown = big.NewInt(16) // 16% adjustment down nPowMaxAdjustUp = big.NewInt(8) // 8% adjustment up - diffChangeBlock = big.NewInt(4088) nPowAveragingWindow88 = big.NewInt(88) nPowMaxAdjustDown2 = big.NewInt(3) // 3% adjustment down nPowMaxAdjustUp2 = big.NewInt(2) // 2% adjustment up // Flux - fluxChangeBlock = big.NewInt(8000) nPowMaxAdjustDownFlux = big.NewInt(5) // 0.5% adjustment down nPowMaxAdjustUpFlux = big.NewInt(3) // 0.3% adjustment up nPowDampFlux = big.NewInt(1) // 0.1% @@ -394,10 +392,13 @@ func CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Head parentNumber := parent.Number parentDiff := parent.Difficulty - if parentNumber.Cmp(diffChangeBlock) < 0 { + config := chain.Config() + ubqhashConfig := config.Ubqhash + + if parentNumber.Cmp(ubqhashConfig.DigishieldModBlock) < 0 { return calcDifficultyOrig(chain, parentNumber, parentDiff, parent) } - if parentNumber.Cmp(fluxChangeBlock) < 0 { + if parentNumber.Cmp(ubqhashConfig.FluxBlock) < 0 { // (chain consensus.ChainReader, parentNumber, parentDiff *big.Int, parent *types.Header) return calcDifficulty2(chain, parentNumber, parentDiff, parent) } else { diff --git a/params/config.go b/params/config.go index db078a16b70e..bad6c1aaa27a 100644 --- a/params/config.go +++ b/params/config.go @@ -42,7 +42,10 @@ var ( ByzantiumBlock: big.NewInt(math.MaxInt64), ConstantinopleBlock: big.NewInt(math.MaxInt64), PetersburgBlock: big.NewInt(math.MaxInt64), - Ubqhash: new(UbqhashConfig), + Ubqhash: &UbqhashConfig{ + DigishieldModBlock: big.NewInt(4088), + FluxBlock: big.NewInt(8000), + }, } // MainnetTrustedCheckpoint contains the light client trusted checkpoint for the main network. @@ -65,7 +68,10 @@ var ( ByzantiumBlock: big.NewInt(math.MaxInt64), ConstantinopleBlock: big.NewInt(math.MaxInt64), PetersburgBlock: big.NewInt(math.MaxInt64), - Ubqhash: new(UbqhashConfig), + Ubqhash: &UbqhashConfig{ + DigishieldModBlock: big.NewInt(4088), + FluxBlock: big.NewInt(8000), + }, } // TestnetTrustedCheckpoint contains the light client trusted checkpoint for the Ropsten test network. @@ -82,7 +88,7 @@ var ( // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllUbqhashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(UbqhashConfig), nil} + AllUbqhashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, &UbqhashConfig{DigishieldModBlock: big.NewInt(0), FluxBlock: big.NewInt(0)}, nil} // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ubiq core developers into the Clique consensus. @@ -135,7 +141,10 @@ type ChainConfig struct { } // UbqhashConfig is the consensus engine configs for proof-of-work based sealing. -type UbqhashConfig struct{} +type UbqhashConfig struct { + DigishieldModBlock *big.Int `json:"digishieldModBlock"` // Block to activate the DigiShield V3 mod + FluxBlock *big.Int `json:"fluxBlock"` // Block to activate the Flux difficulty algorithm (must be > DigiShieldModBlock) +} // String implements the stringer interface, returning the consensus engine details. func (c *UbqhashConfig) String() string { From b17e25f2db39f805ff0198ab8d7b8c07db5479b1 Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Sat, 21 Dec 2019 00:32:59 +0100 Subject: [PATCH 14/29] consensus/ubqhash: Redeclare diff algo constants as diffConfig structs --- consensus/ubqhash/consensus.go | 72 ++++++++++++++++++++-------------- 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/consensus/ubqhash/consensus.go b/consensus/ubqhash/consensus.go index 4beefb7952c2..ff33a429e8f5 100644 --- a/consensus/ubqhash/consensus.go +++ b/consensus/ubqhash/consensus.go @@ -45,22 +45,36 @@ var ( // Diff algo constants. var ( - big88 = big.NewInt(88) - bigMinus99 = big.NewInt(-99) - nPowAveragingWindow = big.NewInt(21) - nPowMaxAdjustDown = big.NewInt(16) // 16% adjustment down - nPowMaxAdjustUp = big.NewInt(8) // 8% adjustment up - - nPowAveragingWindow88 = big.NewInt(88) - nPowMaxAdjustDown2 = big.NewInt(3) // 3% adjustment down - nPowMaxAdjustUp2 = big.NewInt(2) // 2% adjustment up - - // Flux - nPowMaxAdjustDownFlux = big.NewInt(5) // 0.5% adjustment down - nPowMaxAdjustUpFlux = big.NewInt(3) // 0.3% adjustment up - nPowDampFlux = big.NewInt(1) // 0.1% + big88 = big.NewInt(88) + bigMinus99 = big.NewInt(-99) + + digishieldV3Config = &diffConfig{ + AveragingWindow: big.NewInt(21), + MaxAdjustDown: big.NewInt(16), // 16% + MaxAdjustUp: big.NewInt(8), // 8% + } + + digishieldV3ModConfig = &diffConfig{ + AveragingWindow: big.NewInt(88), + MaxAdjustDown: big.NewInt(3), // 3% + MaxAdjustUp: big.NewInt(2), // 2% + } + + fluxConfig = &diffConfig{ + AveragingWindow: big.NewInt(88), + MaxAdjustDown: big.NewInt(5), // 0.5% + MaxAdjustUp: big.NewInt(3), // 0.3% + Dampen: big.NewInt(1), // 0.1% + } ) +type diffConfig struct { + AveragingWindow *big.Int `json:"averagingWindow"` + MaxAdjustDown *big.Int `json:"maxAdjustDown"` + MaxAdjustUp *big.Int `json:"maxAdjustUp"` + Dampen *big.Int `json:"dampen,omitempty"` +} + // Various error messages to mark blocks invalid. These should be private to // prevent engine specific errors from being referenced in the remainder of the // codebase, inherently breaking if the engine is swapped out. Please put common @@ -300,14 +314,14 @@ func (ubqhash *Ubqhash) verifyHeader(chain consensus.ChainReader, header, parent // Difficulty timespans func averagingWindowTimespan() *big.Int { x := new(big.Int) - return x.Mul(nPowAveragingWindow, big88) + return x.Mul(digishieldV3Config.AveragingWindow, big88) } func minActualTimespan() *big.Int { x := new(big.Int) y := new(big.Int) z := new(big.Int) - x.Sub(big.NewInt(100), nPowMaxAdjustUp) + x.Sub(big.NewInt(100), digishieldV3Config.MaxAdjustUp) y.Mul(averagingWindowTimespan(), x) z.Div(y, big.NewInt(100)) return z @@ -317,7 +331,7 @@ func maxActualTimespan() *big.Int { x := new(big.Int) y := new(big.Int) z := new(big.Int) - x.Add(big.NewInt(100), nPowMaxAdjustDown) + x.Add(big.NewInt(100), digishieldV3Config.MaxAdjustDown) y.Mul(averagingWindowTimespan(), x) z.Div(y, big.NewInt(100)) return z @@ -325,14 +339,14 @@ func maxActualTimespan() *big.Int { func averagingWindowTimespan88() *big.Int { x := new(big.Int) - return x.Mul(nPowAveragingWindow88, big88) + return x.Mul(digishieldV3ModConfig.AveragingWindow, big88) } func minActualTimespan2() *big.Int { x := new(big.Int) y := new(big.Int) z := new(big.Int) - x.Sub(big.NewInt(100), nPowMaxAdjustUp2) + x.Sub(big.NewInt(100), digishieldV3ModConfig.MaxAdjustUp) y.Mul(averagingWindowTimespan88(), x) z.Div(y, big.NewInt(100)) return z @@ -342,7 +356,7 @@ func maxActualTimespan2() *big.Int { x := new(big.Int) y := new(big.Int) z := new(big.Int) - x.Add(big.NewInt(100), nPowMaxAdjustDown2) + x.Add(big.NewInt(100), digishieldV3ModConfig.MaxAdjustDown) y.Mul(averagingWindowTimespan88(), x) z.Div(y, big.NewInt(100)) return z @@ -353,11 +367,11 @@ func minActualTimespanFlux(dampen bool) *big.Int { y := new(big.Int) z := new(big.Int) if dampen { - x.Sub(big.NewInt(1000), nPowDampFlux) + x.Sub(big.NewInt(1000), fluxConfig.Dampen) y.Mul(averagingWindowTimespan88(), x) z.Div(y, big.NewInt(1000)) } else { - x.Sub(big.NewInt(1000), nPowMaxAdjustUpFlux) + x.Sub(big.NewInt(1000), fluxConfig.MaxAdjustUp) y.Mul(averagingWindowTimespan88(), x) z.Div(y, big.NewInt(1000)) } @@ -369,11 +383,11 @@ func maxActualTimespanFlux(dampen bool) *big.Int { y := new(big.Int) z := new(big.Int) if dampen { - x.Add(big.NewInt(1000), nPowDampFlux) + x.Add(big.NewInt(1000), fluxConfig.Dampen) y.Mul(averagingWindowTimespan88(), x) z.Div(y, big.NewInt(1000)) } else { - x.Add(big.NewInt(1000), nPowMaxAdjustDownFlux) + x.Add(big.NewInt(1000), fluxConfig.MaxAdjustDown) y.Mul(averagingWindowTimespan88(), x) z.Div(y, big.NewInt(1000)) } @@ -454,13 +468,13 @@ func calcDifficultyOrig(chain consensus.ChainReader, parentNumber, parentDiff *b // holds intermediate values to make the algo easier to read & audit x := new(big.Int) nFirstBlock := new(big.Int) - nFirstBlock.Sub(parentNumber, nPowAveragingWindow) + nFirstBlock.Sub(parentNumber, digishieldV3Config.AveragingWindow) log.Debug(fmt.Sprintf("CalcDifficulty parentNumber: %v parentDiff: %v", parentNumber, parentDiff)) // Check we have enough blocks - if parentNumber.Cmp(nPowAveragingWindow) < 1 { - log.Debug(fmt.Sprintf("CalcDifficulty: parentNumber(%+x) < nPowAveragingWindow(%+x)", parentNumber, nPowAveragingWindow)) + if parentNumber.Cmp(digishieldV3Config.AveragingWindow) < 1 { + log.Debug(fmt.Sprintf("CalcDifficulty: parentNumber(%+x) < digishieldV3Config.AveragingWindow(%+x)", parentNumber, digishieldV3Config.AveragingWindow)) x.Set(parentDiff) return x } @@ -504,7 +518,7 @@ func calcDifficultyOrig(chain consensus.ChainReader, parentNumber, parentDiff *b func calcDifficulty2(chain consensus.ChainReader, parentNumber, parentDiff *big.Int, parent *types.Header) *big.Int { x := new(big.Int) nFirstBlock := new(big.Int) - nFirstBlock.Sub(parentNumber, nPowAveragingWindow88) + nFirstBlock.Sub(parentNumber, digishieldV3ModConfig.AveragingWindow) nLastBlockTime := chain.CalcPastMedianTime(parentNumber.Uint64(), parent) nFirstBlockTime := chain.CalcPastMedianTime(nFirstBlock.Uint64(), parent) @@ -536,7 +550,7 @@ func calcDifficulty2(chain consensus.ChainReader, parentNumber, parentDiff *big. func fluxDifficulty(chain consensus.ChainReader, time, parentTime, parentNumber, parentDiff *big.Int, parent *types.Header) *big.Int { x := new(big.Int) nFirstBlock := new(big.Int) - nFirstBlock.Sub(parentNumber, nPowAveragingWindow88) + nFirstBlock.Sub(parentNumber, fluxConfig.AveragingWindow) diffTime := new(big.Int) diffTime.Sub(time, parentTime) From 461310e401e3802f7b11ed84759365415c980e7b Mon Sep 17 00:00:00 2001 From: Julian Yap Date: Fri, 20 Dec 2019 22:47:51 -1000 Subject: [PATCH 15/29] Fix golint reported issue --- consensus/ubqhash/consensus.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/consensus/ubqhash/consensus.go b/consensus/ubqhash/consensus.go index 4beefb7952c2..1207ae34587c 100644 --- a/consensus/ubqhash/consensus.go +++ b/consensus/ubqhash/consensus.go @@ -401,10 +401,9 @@ func CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Head if parentNumber.Cmp(ubqhashConfig.FluxBlock) < 0 { // (chain consensus.ChainReader, parentNumber, parentDiff *big.Int, parent *types.Header) return calcDifficulty2(chain, parentNumber, parentDiff, parent) - } else { - // (chain consensus.ChainReader, time, parentTime, parentNumber, parentDiff *big.Int, parent *types.Header) - return fluxDifficulty(chain, big.NewInt(int64(time)), big.NewInt(int64(parentTime)), parentNumber, parentDiff, parent) } + // (chain consensus.ChainReader, time, parentTime, parentNumber, parentDiff *big.Int, parent *types.Header) + return fluxDifficulty(chain, big.NewInt(int64(time)), big.NewInt(int64(parentTime)), parentNumber, parentDiff, parent) } // Some weird constants to avoid constant memory allocs for them. From 401b1c86a461053e58e78fc42e0f7a4a6c051085 Mon Sep 17 00:00:00 2001 From: Julian Yap Date: Fri, 20 Dec 2019 22:53:37 -1000 Subject: [PATCH 16/29] Fix golint reported issue --- consensus/ubqhash/ubqhash_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/consensus/ubqhash/ubqhash_test.go b/consensus/ubqhash/ubqhash_test.go index ab5baee32196..a77e953321b3 100644 --- a/consensus/ubqhash/ubqhash_test.go +++ b/consensus/ubqhash/ubqhash_test.go @@ -142,7 +142,7 @@ func TestHashRate(t *testing.T) { } api := &API{ubqhash} - for i := 0; i < len(hashrate); i += 1 { + for i := 0; i < len(hashrate); i++ { if res := api.SubmitHashRate(hashrate[i], ids[i]); !res { t.Error("remote miner submit hashrate failed") } From 8b2745314d46e0d1672df826b6407b93b07d4c5c Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Sat, 21 Dec 2019 12:06:59 +0100 Subject: [PATCH 17/29] consensus/ubqhash: merge calcDifficultyOrig and calcDifficulty2 into calcDifficultyDigishieldV3 --- consensus/ubqhash/consensus.go | 123 ++++++++++----------------------- 1 file changed, 36 insertions(+), 87 deletions(-) diff --git a/consensus/ubqhash/consensus.go b/consensus/ubqhash/consensus.go index ff33a429e8f5..1e8f9a3905c8 100644 --- a/consensus/ubqhash/consensus.go +++ b/consensus/ubqhash/consensus.go @@ -312,52 +312,27 @@ func (ubqhash *Ubqhash) verifyHeader(chain consensus.ChainReader, header, parent } // Difficulty timespans -func averagingWindowTimespan() *big.Int { +func averagingWindowTimespan(config *diffConfig) *big.Int { x := new(big.Int) - return x.Mul(digishieldV3Config.AveragingWindow, big88) + return x.Mul(config.AveragingWindow, big88) } -func minActualTimespan() *big.Int { +func minActualTimespan(config *diffConfig) *big.Int { x := new(big.Int) y := new(big.Int) z := new(big.Int) - x.Sub(big.NewInt(100), digishieldV3Config.MaxAdjustUp) - y.Mul(averagingWindowTimespan(), x) + x.Sub(big.NewInt(100), config.MaxAdjustUp) + y.Mul(averagingWindowTimespan(config), x) z.Div(y, big.NewInt(100)) return z } -func maxActualTimespan() *big.Int { +func maxActualTimespan(config *diffConfig) *big.Int { x := new(big.Int) y := new(big.Int) z := new(big.Int) - x.Add(big.NewInt(100), digishieldV3Config.MaxAdjustDown) - y.Mul(averagingWindowTimespan(), x) - z.Div(y, big.NewInt(100)) - return z -} - -func averagingWindowTimespan88() *big.Int { - x := new(big.Int) - return x.Mul(digishieldV3ModConfig.AveragingWindow, big88) -} - -func minActualTimespan2() *big.Int { - x := new(big.Int) - y := new(big.Int) - z := new(big.Int) - x.Sub(big.NewInt(100), digishieldV3ModConfig.MaxAdjustUp) - y.Mul(averagingWindowTimespan88(), x) - z.Div(y, big.NewInt(100)) - return z -} - -func maxActualTimespan2() *big.Int { - x := new(big.Int) - y := new(big.Int) - z := new(big.Int) - x.Add(big.NewInt(100), digishieldV3ModConfig.MaxAdjustDown) - y.Mul(averagingWindowTimespan88(), x) + x.Add(big.NewInt(100), config.MaxAdjustDown) + y.Mul(averagingWindowTimespan(config), x) z.Div(y, big.NewInt(100)) return z } @@ -368,11 +343,11 @@ func minActualTimespanFlux(dampen bool) *big.Int { z := new(big.Int) if dampen { x.Sub(big.NewInt(1000), fluxConfig.Dampen) - y.Mul(averagingWindowTimespan88(), x) + y.Mul(averagingWindowTimespan(fluxConfig), x) z.Div(y, big.NewInt(1000)) } else { x.Sub(big.NewInt(1000), fluxConfig.MaxAdjustUp) - y.Mul(averagingWindowTimespan88(), x) + y.Mul(averagingWindowTimespan(fluxConfig), x) z.Div(y, big.NewInt(1000)) } return z @@ -384,11 +359,11 @@ func maxActualTimespanFlux(dampen bool) *big.Int { z := new(big.Int) if dampen { x.Add(big.NewInt(1000), fluxConfig.Dampen) - y.Mul(averagingWindowTimespan88(), x) + y.Mul(averagingWindowTimespan(fluxConfig), x) z.Div(y, big.NewInt(1000)) } else { x.Add(big.NewInt(1000), fluxConfig.MaxAdjustDown) - y.Mul(averagingWindowTimespan88(), x) + y.Mul(averagingWindowTimespan(fluxConfig), x) z.Div(y, big.NewInt(1000)) } return z @@ -409,15 +384,17 @@ func CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Head config := chain.Config() ubqhashConfig := config.Ubqhash - if parentNumber.Cmp(ubqhashConfig.DigishieldModBlock) < 0 { - return calcDifficultyOrig(chain, parentNumber, parentDiff, parent) - } if parentNumber.Cmp(ubqhashConfig.FluxBlock) < 0 { - // (chain consensus.ChainReader, parentNumber, parentDiff *big.Int, parent *types.Header) - return calcDifficulty2(chain, parentNumber, parentDiff, parent) + if parentNumber.Cmp(ubqhashConfig.DigishieldModBlock) < 0 { + // Original DigishieldV3 + return calcDifficultyDigishieldV3(chain, parentNumber, parentDiff, parent, digishieldV3Config) + } else { + // Modified DigishieldV3 + return calcDifficultyDigishieldV3(chain, parentNumber, parentDiff, parent, digishieldV3ModConfig) + } } else { - // (chain consensus.ChainReader, time, parentTime, parentNumber, parentDiff *big.Int, parent *types.Header) - return fluxDifficulty(chain, big.NewInt(int64(time)), big.NewInt(int64(parentTime)), parentNumber, parentDiff, parent) + // Flux + return calcDifficultyFlux(chain, big.NewInt(int64(time)), big.NewInt(int64(parentTime)), parentNumber, parentDiff, parent) } } @@ -464,17 +441,17 @@ func CalcDifficultyLegacy(time, parentTime uint64, parentNumber, parentDiff *big // the difficulty that a new block should have when created at time // given the parent block's time and difficulty. // Rewritten to be based on Digibyte's Digishield v3 retargeting -func calcDifficultyOrig(chain consensus.ChainReader, parentNumber, parentDiff *big.Int, parent *types.Header) *big.Int { +func calcDifficultyDigishieldV3(chain consensus.ChainReader, parentNumber, parentDiff *big.Int, parent *types.Header, digishield *diffConfig) *big.Int { // holds intermediate values to make the algo easier to read & audit x := new(big.Int) nFirstBlock := new(big.Int) - nFirstBlock.Sub(parentNumber, digishieldV3Config.AveragingWindow) + nFirstBlock.Sub(parentNumber, digishield.AveragingWindow) log.Debug(fmt.Sprintf("CalcDifficulty parentNumber: %v parentDiff: %v", parentNumber, parentDiff)) // Check we have enough blocks - if parentNumber.Cmp(digishieldV3Config.AveragingWindow) < 1 { - log.Debug(fmt.Sprintf("CalcDifficulty: parentNumber(%+x) < digishieldV3Config.AveragingWindow(%+x)", parentNumber, digishieldV3Config.AveragingWindow)) + if parentNumber.Cmp(digishield.AveragingWindow) < 1 { + log.Debug(fmt.Sprintf("CalcDifficulty: parentNumber(%+x) < digishieldV3Config.AveragingWindow(%+x)", parentNumber, digishield.AveragingWindow)) x.Set(parentDiff) return x } @@ -490,56 +467,28 @@ func calcDifficultyOrig(chain consensus.ChainReader, parentNumber, parentDiff *b // nActualTimespan = AveragingWindowTimespan() + (nActualTimespan-AveragingWindowTimespan())/4 y := new(big.Int) - y.Sub(nActualTimespan, averagingWindowTimespan()) + y.Sub(nActualTimespan, averagingWindowTimespan(digishield)) y.Div(y, big.NewInt(4)) - nActualTimespan.Add(y, averagingWindowTimespan()) + nActualTimespan.Add(y, averagingWindowTimespan(digishield)) log.Debug(fmt.Sprintf("CalcDifficulty nActualTimespan = %v before bounds", nActualTimespan)) - if nActualTimespan.Cmp(minActualTimespan()) < 0 { - nActualTimespan.Set(minActualTimespan()) + if nActualTimespan.Cmp(minActualTimespan(digishield)) < 0 { + nActualTimespan.Set(minActualTimespan(digishield)) log.Debug("CalcDifficulty Minimum Timespan set") - } else if nActualTimespan.Cmp(maxActualTimespan()) > 0 { - nActualTimespan.Set(maxActualTimespan()) + } else if nActualTimespan.Cmp(maxActualTimespan(digishield)) > 0 { + nActualTimespan.Set(maxActualTimespan(digishield)) log.Debug("CalcDifficulty Maximum Timespan set") } log.Debug(fmt.Sprintf("CalcDifficulty nActualTimespan = %v final\n", nActualTimespan)) // Retarget - x.Mul(parentDiff, averagingWindowTimespan()) + x.Mul(parentDiff, averagingWindowTimespan(digishield)) log.Debug(fmt.Sprintf("CalcDifficulty parentDiff * AveragingWindowTimespan: %v", x)) x.Div(x, nActualTimespan) log.Debug(fmt.Sprintf("CalcDifficulty x / nActualTimespan: %v", x)) - return x -} - -func calcDifficulty2(chain consensus.ChainReader, parentNumber, parentDiff *big.Int, parent *types.Header) *big.Int { - x := new(big.Int) - nFirstBlock := new(big.Int) - nFirstBlock.Sub(parentNumber, digishieldV3ModConfig.AveragingWindow) - - nLastBlockTime := chain.CalcPastMedianTime(parentNumber.Uint64(), parent) - nFirstBlockTime := chain.CalcPastMedianTime(nFirstBlock.Uint64(), parent) - - nActualTimespan := new(big.Int) - nActualTimespan.Sub(nLastBlockTime, nFirstBlockTime) - - y := new(big.Int) - y.Sub(nActualTimespan, averagingWindowTimespan88()) - y.Div(y, big.NewInt(4)) - nActualTimespan.Add(y, averagingWindowTimespan88()) - - if nActualTimespan.Cmp(minActualTimespan2()) < 0 { - nActualTimespan.Set(minActualTimespan2()) - } else if nActualTimespan.Cmp(maxActualTimespan2()) > 0 { - nActualTimespan.Set(maxActualTimespan2()) - } - - x.Mul(parentDiff, averagingWindowTimespan88()) - x.Div(x, nActualTimespan) - if x.Cmp(params.MinimumDifficulty) < 0 { x.Set(params.MinimumDifficulty) } @@ -547,7 +496,7 @@ func calcDifficulty2(chain consensus.ChainReader, parentNumber, parentDiff *big. return x } -func fluxDifficulty(chain consensus.ChainReader, time, parentTime, parentNumber, parentDiff *big.Int, parent *types.Header) *big.Int { +func calcDifficultyFlux(chain consensus.ChainReader, time, parentTime, parentNumber, parentDiff *big.Int, parent *types.Header) *big.Int { x := new(big.Int) nFirstBlock := new(big.Int) nFirstBlock.Sub(parentNumber, fluxConfig.AveragingWindow) @@ -561,9 +510,9 @@ func fluxDifficulty(chain consensus.ChainReader, time, parentTime, parentNumber, nActualTimespan.Sub(nLastBlockTime, nFirstBlockTime) y := new(big.Int) - y.Sub(nActualTimespan, averagingWindowTimespan88()) + y.Sub(nActualTimespan, averagingWindowTimespan(fluxConfig)) y.Div(y, big.NewInt(4)) - nActualTimespan.Add(y, averagingWindowTimespan88()) + nActualTimespan.Add(y, averagingWindowTimespan(fluxConfig)) if nActualTimespan.Cmp(minActualTimespanFlux(false)) < 0 { doubleBig88 := new(big.Int) @@ -583,7 +532,7 @@ func fluxDifficulty(chain consensus.ChainReader, time, parentTime, parentNumber, } } - x.Mul(parentDiff, averagingWindowTimespan88()) + x.Mul(parentDiff, averagingWindowTimespan(fluxConfig)) x.Div(x, nActualTimespan) if x.Cmp(params.MinimumDifficulty) < 0 { From a46055e8cedc7f4b99977da5bab6b286117c3622 Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Sat, 21 Dec 2019 14:24:34 +0100 Subject: [PATCH 18/29] consensus/ubqhash: merge [min/max]ActualTimespan() and [min/max]ActualTimespanFlux() --- consensus/ubqhash/consensus.go | 73 ++++++++++++++-------------------- 1 file changed, 29 insertions(+), 44 deletions(-) diff --git a/consensus/ubqhash/consensus.go b/consensus/ubqhash/consensus.go index f575952fb35a..89c28ad71e32 100644 --- a/consensus/ubqhash/consensus.go +++ b/consensus/ubqhash/consensus.go @@ -52,12 +52,14 @@ var ( AveragingWindow: big.NewInt(21), MaxAdjustDown: big.NewInt(16), // 16% MaxAdjustUp: big.NewInt(8), // 8% + Factor: big.NewInt(100), } digishieldV3ModConfig = &diffConfig{ AveragingWindow: big.NewInt(88), MaxAdjustDown: big.NewInt(3), // 3% MaxAdjustUp: big.NewInt(2), // 2% + Factor: big.NewInt(100), } fluxConfig = &diffConfig{ @@ -65,6 +67,7 @@ var ( MaxAdjustDown: big.NewInt(5), // 0.5% MaxAdjustUp: big.NewInt(3), // 0.3% Dampen: big.NewInt(1), // 0.1% + Factor: big.NewInt(1000), } ) @@ -73,6 +76,7 @@ type diffConfig struct { MaxAdjustDown *big.Int `json:"maxAdjustDown"` MaxAdjustUp *big.Int `json:"maxAdjustUp"` Dampen *big.Int `json:"dampen,omitempty"` + Factor *big.Int `json:"factor"` } // Various error messages to mark blocks invalid. These should be private to @@ -317,54 +321,34 @@ func averagingWindowTimespan(config *diffConfig) *big.Int { return x.Mul(config.AveragingWindow, big88) } -func minActualTimespan(config *diffConfig) *big.Int { - x := new(big.Int) - y := new(big.Int) - z := new(big.Int) - x.Sub(big.NewInt(100), config.MaxAdjustUp) - y.Mul(averagingWindowTimespan(config), x) - z.Div(y, big.NewInt(100)) - return z -} - -func maxActualTimespan(config *diffConfig) *big.Int { - x := new(big.Int) - y := new(big.Int) - z := new(big.Int) - x.Add(big.NewInt(100), config.MaxAdjustDown) - y.Mul(averagingWindowTimespan(config), x) - z.Div(y, big.NewInt(100)) - return z -} - -func minActualTimespanFlux(dampen bool) *big.Int { +func minActualTimespan(config *diffConfig, dampen bool) *big.Int { x := new(big.Int) y := new(big.Int) z := new(big.Int) if dampen { - x.Sub(big.NewInt(1000), fluxConfig.Dampen) - y.Mul(averagingWindowTimespan(fluxConfig), x) - z.Div(y, big.NewInt(1000)) + x.Sub(config.Factor, config.Dampen) + y.Mul(averagingWindowTimespan(config), x) + z.Div(y, config.Factor) } else { - x.Sub(big.NewInt(1000), fluxConfig.MaxAdjustUp) - y.Mul(averagingWindowTimespan(fluxConfig), x) - z.Div(y, big.NewInt(1000)) + x.Sub(config.Factor, config.MaxAdjustUp) + y.Mul(averagingWindowTimespan(config), x) + z.Div(y, config.Factor) } return z } -func maxActualTimespanFlux(dampen bool) *big.Int { +func maxActualTimespan(config *diffConfig, dampen bool) *big.Int { x := new(big.Int) y := new(big.Int) z := new(big.Int) if dampen { - x.Add(big.NewInt(1000), fluxConfig.Dampen) - y.Mul(averagingWindowTimespan(fluxConfig), x) - z.Div(y, big.NewInt(1000)) + x.Add(config.Factor, config.Dampen) + y.Mul(averagingWindowTimespan(config), x) + z.Div(y, config.Factor) } else { - x.Add(big.NewInt(1000), fluxConfig.MaxAdjustDown) - y.Mul(averagingWindowTimespan(fluxConfig), x) - z.Div(y, big.NewInt(1000)) + x.Add(config.Factor, config.MaxAdjustDown) + y.Mul(averagingWindowTimespan(config), x) + z.Div(y, config.Factor) } return z } @@ -376,6 +360,7 @@ func (ubqhash *Ubqhash) CalcDifficulty(chain consensus.ChainReader, time uint64, return CalcDifficulty(chain, time, parent) } +// CalcDifficulty determines which difficulty algorithm to use for calculating a new block func CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Header) *big.Int { parentTime := parent.Time parentNumber := parent.Number @@ -470,11 +455,11 @@ func calcDifficultyDigishieldV3(chain consensus.ChainReader, parentNumber, paren nActualTimespan.Add(y, averagingWindowTimespan(digishield)) log.Debug(fmt.Sprintf("CalcDifficulty nActualTimespan = %v before bounds", nActualTimespan)) - if nActualTimespan.Cmp(minActualTimespan(digishield)) < 0 { - nActualTimespan.Set(minActualTimespan(digishield)) + if nActualTimespan.Cmp(minActualTimespan(digishield, false)) < 0 { + nActualTimespan.Set(minActualTimespan(digishield, false)) log.Debug("CalcDifficulty Minimum Timespan set") - } else if nActualTimespan.Cmp(maxActualTimespan(digishield)) > 0 { - nActualTimespan.Set(maxActualTimespan(digishield)) + } else if nActualTimespan.Cmp(maxActualTimespan(digishield, false)) > 0 { + nActualTimespan.Set(maxActualTimespan(digishield, false)) log.Debug("CalcDifficulty Maximum Timespan set") } @@ -512,21 +497,21 @@ func calcDifficultyFlux(chain consensus.ChainReader, time, parentTime, parentNum y.Div(y, big.NewInt(4)) nActualTimespan.Add(y, averagingWindowTimespan(fluxConfig)) - if nActualTimespan.Cmp(minActualTimespanFlux(false)) < 0 { + if nActualTimespan.Cmp(minActualTimespan(fluxConfig, false)) < 0 { doubleBig88 := new(big.Int) doubleBig88.Mul(big88, big.NewInt(2)) if diffTime.Cmp(doubleBig88) > 0 { - nActualTimespan.Set(minActualTimespanFlux(true)) + nActualTimespan.Set(minActualTimespan(fluxConfig, true)) } else { - nActualTimespan.Set(minActualTimespanFlux(false)) + nActualTimespan.Set(minActualTimespan(fluxConfig, false)) } - } else if nActualTimespan.Cmp(maxActualTimespanFlux(false)) > 0 { + } else if nActualTimespan.Cmp(maxActualTimespan(fluxConfig, false)) > 0 { halfBig88 := new(big.Int) halfBig88.Div(big88, big.NewInt(2)) if diffTime.Cmp(halfBig88) < 0 { - nActualTimespan.Set(maxActualTimespanFlux(true)) + nActualTimespan.Set(maxActualTimespan(fluxConfig, true)) } else { - nActualTimespan.Set(maxActualTimespanFlux(false)) + nActualTimespan.Set(maxActualTimespan(fluxConfig, false)) } } From fbc1784ff7e7d3cbfd576e0af4a12506bb87ed8b Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Sat, 21 Dec 2019 16:48:56 +0100 Subject: [PATCH 19/29] consensus/ubqhash: refactor CalcBaseBlockReward; Move block reward stepdown block & reward definitions to params.ChainConfig.Ubqhash.MonetaryPolicy (definable via custom genesis) --- consensus/ubqhash/consensus.go | 45 +++++++---------- consensus/ubqhash/consensus_test.go | 51 ++++++++++--------- params/config.go | 78 +++++++++++++++++++++++++++-- 3 files changed, 118 insertions(+), 56 deletions(-) diff --git a/consensus/ubqhash/consensus.go b/consensus/ubqhash/consensus.go index 89c28ad71e32..3d27815c1f1b 100644 --- a/consensus/ubqhash/consensus.go +++ b/consensus/ubqhash/consensus.go @@ -38,9 +38,9 @@ import ( // Ubqhash proof-of-work protocol constants. var ( - blockReward *big.Int = big.NewInt(8e+18) // Block reward in wei for successfully mining a block - maxUncles = 2 // Maximum number of uncles allowed in a single block - allowedFutureBlockTime = 15 * time.Second // Max time from current time allowed for blocks, before they're considered future blocks + // blockReward *big.Int = big.NewInt(8e+18) // Block reward in wei for successfully mining a block + maxUncles = 2 // Maximum number of uncles allowed in a single block + allowedFutureBlockTime = 15 * time.Second // Max time from current time allowed for blocks, before they're considered future blocks ) // Diff algo constants. @@ -650,34 +650,21 @@ func (ubqhash *Ubqhash) SealHash(header *types.Header) (hash common.Hash) { return hash } -// Calculates the base block reward as per the ubiq monetary policy -func CalcBaseBlockReward(height *big.Int) *big.Int { - reward := new(big.Int).Set(blockReward) +// CalcBaseBlockReward calculates the base block reward as per the ubiq monetary +// policy. +func CalcBaseBlockReward(config *params.UbqhashConfig, height *big.Int) *big.Int { + reward := new(big.Int).Set(config.BlockReward) - if height.Cmp(big.NewInt(358363)) > 0 { - reward = big.NewInt(7e+18) - } - if height.Cmp(big.NewInt(716727)) > 0 { - reward = big.NewInt(6e+18) - } - if height.Cmp(big.NewInt(1075090)) > 0 { - reward = big.NewInt(5e+18) - } - if height.Cmp(big.NewInt(1433454)) > 0 { - reward = big.NewInt(4e+18) - } - if height.Cmp(big.NewInt(1791818)) > 0 { - reward = big.NewInt(3e+18) - } - if height.Cmp(big.NewInt(2150181)) > 0 { - reward = big.NewInt(2e+18) - } - if height.Cmp(big.NewInt(2508545)) > 0 { - reward = big.NewInt(1e+18) + for _, step := range config.MonetaryPolicy { + if height.Cmp(step.Block) > 0 { + reward = step.Reward + } } + return reward } +// CalcUncleBlockReward calculates the uncle miner reward based on depth. func CalcUncleBlockReward(config *params.ChainConfig, blockHeight *big.Int, uncleHeight *big.Int, blockReward *big.Int) *big.Int { reward := new(big.Int) // calculate reward based on depth @@ -697,11 +684,13 @@ func CalcUncleBlockReward(config *params.ChainConfig, blockHeight *big.Int, uncl // reward. The total reward consists of the static block reward and rewards for // included uncles. The coinbase of each uncle block is also rewarded. func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) { + ubqhashConfig := config.Ubqhash + // block reward (miner) - reward := CalcBaseBlockReward(header.Number) + reward := CalcBaseBlockReward(ubqhashConfig, header.Number) // Uncle reward step down fix. (activates along-side byzantium) - ufixReward := new(big.Int).Set(blockReward) + ufixReward := new(big.Int).Set(ubqhashConfig.BlockReward) if config.IsByzantium(header.Number) { ufixReward = reward } diff --git a/consensus/ubqhash/consensus_test.go b/consensus/ubqhash/consensus_test.go index 04646c5959d0..ad7c595e147e 100644 --- a/consensus/ubqhash/consensus_test.go +++ b/consensus/ubqhash/consensus_test.go @@ -17,20 +17,22 @@ package ubqhash import ( - "encoding/json" + // "encoding/json" "math/big" - "os" - "path/filepath" + // "os" + // "path/filepath" "testing" - "github.com/ubiq/go-ubiq/common/math" - "github.com/ubiq/go-ubiq/core" - "github.com/ubiq/go-ubiq/core/types" - "github.com/ubiq/go-ubiq/core/vm" - "github.com/ubiq/go-ubiq/ethdb" + // "github.com/ubiq/go-ubiq/common/math" + // "github.com/ubiq/go-ubiq/core" + // "github.com/ubiq/go-ubiq/core/types" + // "github.com/ubiq/go-ubiq/core/vm" + // "github.com/ubiq/go-ubiq/ethdb" "github.com/ubiq/go-ubiq/params" ) +// TODO: write new difficulty tests +/* type diffTest struct { ParentTimestamp uint64 ParentDifficulty *big.Int @@ -101,73 +103,74 @@ func TestCalcDifficulty(t *testing.T) { t.Error(name, "failed. Expected", test.CurrentDifficulty, "and calculated", diff) } } -} +}*/ func TestCalcBaseBlockReward(t *testing.T) { - reward := CalcBaseBlockReward(big.NewInt(1)) + config := *params.MainnetChainConfig + reward := CalcBaseBlockReward(config.Ubqhash, big.NewInt(1)) if reward.Cmp(big.NewInt(8e+18)) != 0 { t.Error("TestCalcBaseBlockReward 8 (start)", "failed. Expected", big.NewInt(8e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(big.NewInt(358363)) + reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(358363)) if reward.Cmp(big.NewInt(8e+18)) != 0 { t.Error("TestCalcBaseBlockReward 8 (end)", "failed. Expected", big.NewInt(8e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(big.NewInt(358364)) + reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(358364)) if reward.Cmp(big.NewInt(7e+18)) != 0 { t.Error("TestCalcBaseBlockReward 7 (start)", "failed. Expected", big.NewInt(7e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(big.NewInt(716727)) + reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(716727)) if reward.Cmp(big.NewInt(7e+18)) != 0 { t.Error("TestCalcBaseBlockReward 7 (end)", "failed. Expected", big.NewInt(7e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(big.NewInt(716728)) + reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(716728)) if reward.Cmp(big.NewInt(6e+18)) != 0 { t.Error("TestCalcBaseBlockReward 6 (start)", "failed. Expected", big.NewInt(6e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(big.NewInt(1075090)) + reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(1075090)) if reward.Cmp(big.NewInt(6e+18)) != 0 { t.Error("TestCalcBaseBlockReward 6 (end)", "failed. Expected", big.NewInt(6e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(big.NewInt(1075091)) + reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(1075091)) if reward.Cmp(big.NewInt(5e+18)) != 0 { t.Error("TestCalcBaseBlockReward 5 (start)", "failed. Expected", big.NewInt(5e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(big.NewInt(1433454)) + reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(1433454)) if reward.Cmp(big.NewInt(5e+18)) != 0 { t.Error("TestCalcBaseBlockReward 5 (end)", "failed. Expected", big.NewInt(5e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(big.NewInt(1433455)) + reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(1433455)) if reward.Cmp(big.NewInt(4e+18)) != 0 { t.Error("TestCalcBaseBlockReward 4 (start)", "failed. Expected", big.NewInt(4e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(big.NewInt(1791818)) + reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(1791818)) if reward.Cmp(big.NewInt(4e+18)) != 0 { t.Error("TestCalcBaseBlockReward 4 (end)", "failed. Expected", big.NewInt(4e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(big.NewInt(1791819)) + reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(1791819)) if reward.Cmp(big.NewInt(3e+18)) != 0 { t.Error("TestCalcBaseBlockReward 3 (start)", "failed. Expected", big.NewInt(3e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(big.NewInt(2150181)) + reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(2150181)) if reward.Cmp(big.NewInt(3e+18)) != 0 { t.Error("TestCalcBaseBlockReward 3 (end)", "failed. Expected", big.NewInt(3e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(big.NewInt(2150182)) + reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(2150182)) if reward.Cmp(big.NewInt(2e+18)) != 0 { t.Error("TestCalcBaseBlockReward 2 (start)", "failed. Expected", big.NewInt(2e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(big.NewInt(2508545)) + reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(2508545)) if reward.Cmp(big.NewInt(2e+18)) != 0 { t.Error("TestCalcBaseBlockReward 2 (end)", "failed. Expected", big.NewInt(2e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(big.NewInt(2508546)) + reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(2508546)) if reward.Cmp(big.NewInt(1e+18)) != 0 { t.Error("TestCalcBaseBlockReward 1 (start)", "failed. Expected", big.NewInt(1e+18), "and calculated", reward) } diff --git a/params/config.go b/params/config.go index bad6c1aaa27a..241b463c1cb5 100644 --- a/params/config.go +++ b/params/config.go @@ -43,8 +43,39 @@ var ( ConstantinopleBlock: big.NewInt(math.MaxInt64), PetersburgBlock: big.NewInt(math.MaxInt64), Ubqhash: &UbqhashConfig{ + BlockReward: big.NewInt(8e+18), // 8 UBQ in wei DigishieldModBlock: big.NewInt(4088), FluxBlock: big.NewInt(8000), + MonetaryPolicy: []UbqhashMPStep{ + UbqhashMPStep{ + Block: big.NewInt(358363), + Reward: big.NewInt(7e+18), + }, + UbqhashMPStep{ + Block: big.NewInt(716727), + Reward: big.NewInt(6e+18), + }, + UbqhashMPStep{ + Block: big.NewInt(1075090), + Reward: big.NewInt(5e+18), + }, + UbqhashMPStep{ + Block: big.NewInt(1433454), + Reward: big.NewInt(4e+18), + }, + UbqhashMPStep{ + Block: big.NewInt(1791818), + Reward: big.NewInt(3e+18), + }, + UbqhashMPStep{ + Block: big.NewInt(2150181), + Reward: big.NewInt(2e+18), + }, + UbqhashMPStep{ + Block: big.NewInt(2508545), + Reward: big.NewInt(1e+18), + }, + }, }, } @@ -69,8 +100,39 @@ var ( ConstantinopleBlock: big.NewInt(math.MaxInt64), PetersburgBlock: big.NewInt(math.MaxInt64), Ubqhash: &UbqhashConfig{ + BlockReward: big.NewInt(8e+18), // 8 UBQ in wei DigishieldModBlock: big.NewInt(4088), FluxBlock: big.NewInt(8000), + MonetaryPolicy: []UbqhashMPStep{ + UbqhashMPStep{ + Block: big.NewInt(358363), + Reward: big.NewInt(7e+18), + }, + UbqhashMPStep{ + Block: big.NewInt(716727), + Reward: big.NewInt(6e+18), + }, + UbqhashMPStep{ + Block: big.NewInt(1075090), + Reward: big.NewInt(5e+18), + }, + UbqhashMPStep{ + Block: big.NewInt(1433454), + Reward: big.NewInt(4e+18), + }, + UbqhashMPStep{ + Block: big.NewInt(1791818), + Reward: big.NewInt(3e+18), + }, + UbqhashMPStep{ + Block: big.NewInt(2150181), + Reward: big.NewInt(2e+18), + }, + UbqhashMPStep{ + Block: big.NewInt(2508545), + Reward: big.NewInt(1e+18), + }, + }, }, } @@ -88,7 +150,7 @@ var ( // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllUbqhashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, &UbqhashConfig{DigishieldModBlock: big.NewInt(0), FluxBlock: big.NewInt(0)}, nil} + AllUbqhashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, &UbqhashConfig{big.NewInt(0), big.NewInt(0), big.NewInt(0), []UbqhashMPStep{}}, nil} // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ubiq core developers into the Clique consensus. @@ -97,7 +159,7 @@ var ( // adding flags to the config to also have to set these fields. AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} - TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, new(UbqhashConfig), nil} + TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, &UbqhashConfig{big.NewInt(0), big.NewInt(0), big.NewInt(0), []UbqhashMPStep{}}, nil} TestRules = TestChainConfig.Rules(new(big.Int)) ) @@ -140,10 +202,18 @@ type ChainConfig struct { Clique *CliqueConfig `json:"clique,omitempty"` } +// Ubqhash monetary policy reward step +type UbqhashMPStep struct { + Block *big.Int `json:"block"` + Reward *big.Int `json:"reward"` +} + // UbqhashConfig is the consensus engine configs for proof-of-work based sealing. type UbqhashConfig struct { - DigishieldModBlock *big.Int `json:"digishieldModBlock"` // Block to activate the DigiShield V3 mod - FluxBlock *big.Int `json:"fluxBlock"` // Block to activate the Flux difficulty algorithm (must be > DigiShieldModBlock) + BlockReward *big.Int `json:"blockReward"` // Initial block reward in wei for mining a block + DigishieldModBlock *big.Int `json:"digishieldModBlock,omitempty"` // Block to activate the DigiShield V3 mod + FluxBlock *big.Int `json:"fluxBlock"` // Block to activate the Flux difficulty algorithm + MonetaryPolicy []UbqhashMPStep `json:"monetaryPolicy"` // Blocks to step the block reward down } // String implements the stringer interface, returning the consensus engine details. From 213a9fe9470eef34b28d5888d563aa146537c580 Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Sat, 21 Dec 2019 20:19:27 +0100 Subject: [PATCH 20/29] consensus/ubqhash: Check we have enough blocks (supprt custom ubqhash net with flux activating before block 88) --- consensus/ubqhash/consensus.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/consensus/ubqhash/consensus.go b/consensus/ubqhash/consensus.go index 3d27815c1f1b..df24c4288ae2 100644 --- a/consensus/ubqhash/consensus.go +++ b/consensus/ubqhash/consensus.go @@ -441,14 +441,12 @@ func calcDifficultyDigishieldV3(chain consensus.ChainReader, parentNumber, paren // Limit adjustment step // Use medians to prevent time-warp attacks - // nActualTimespan := nLastBlockTime - nFirstBlockTime nLastBlockTime := chain.CalcPastMedianTime(parentNumber.Uint64(), parent) nFirstBlockTime := chain.CalcPastMedianTime(nFirstBlock.Uint64(), parent) nActualTimespan := new(big.Int) nActualTimespan.Sub(nLastBlockTime, nFirstBlockTime) log.Debug(fmt.Sprintf("CalcDifficulty nActualTimespan = %v before dampening", nActualTimespan)) - // nActualTimespan = AveragingWindowTimespan() + (nActualTimespan-AveragingWindowTimespan())/4 y := new(big.Int) y.Sub(nActualTimespan, averagingWindowTimespan(digishield)) y.Div(y, big.NewInt(4)) @@ -484,6 +482,13 @@ func calcDifficultyFlux(chain consensus.ChainReader, time, parentTime, parentNum nFirstBlock := new(big.Int) nFirstBlock.Sub(parentNumber, fluxConfig.AveragingWindow) + // Check we have enough blocks + if parentNumber.Cmp(fluxConfig.AveragingWindow) < 1 { + log.Debug(fmt.Sprintf("CalcDifficulty: parentNumber(%+x) < fluxConfig.AveragingWindow(%+x)", parentNumber, fluxConfig.AveragingWindow)) + x.Set(parentDiff) + return x + } + diffTime := new(big.Int) diffTime.Sub(time, parentTime) From f11f237f326242f77b139e27e97482f439e0527a Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Sat, 21 Dec 2019 22:03:35 +0100 Subject: [PATCH 21/29] consensus/ubqhash: remove calcDifficultyLegacy --- consensus/ubqhash/consensus.go | 33 --------------------------------- tests/difficulty_test_util.go | 7 ++++--- 2 files changed, 4 insertions(+), 36 deletions(-) diff --git a/consensus/ubqhash/consensus.go b/consensus/ubqhash/consensus.go index df24c4288ae2..776be9835bf4 100644 --- a/consensus/ubqhash/consensus.go +++ b/consensus/ubqhash/consensus.go @@ -387,39 +387,6 @@ var ( big10 = big.NewInt(10) ) -// CalcDifficultyLegacy is the difficulty adjustment algorithm. It returns -// the difficulty that a new block should have when created at time given the -// parent block's time and difficulty. The calculation uses the Legacy rules. -func CalcDifficultyLegacy(time, parentTime uint64, parentNumber, parentDiff *big.Int) *big.Int { - bigTime := new(big.Int).SetUint64(time) - bigParentTime := new(big.Int).SetUint64(parentTime) - - // holds intermediate values to make the algo easier to read & audit - x := new(big.Int) - y := new(big.Int) - - // 1 - (block_timestamp -parent_timestamp) // 10 - x.Sub(bigTime, bigParentTime) - x.Div(x, big88) - x.Sub(common.Big1, x) - - // max(1 - (block_timestamp - parent_timestamp) // 10, -99))) - if x.Cmp(bigMinus99) < 0 { - x.Set(bigMinus99) - } - // (parent_diff + parent_diff // 2048 * max(1 - (block_timestamp - parent_timestamp) // 10, -99)) - y.Div(parentDiff, params.DifficultyBoundDivisor) - x.Mul(y, x) - x.Add(parentDiff, x) - - // minimum difficulty can ever be (before exponential factor) - if x.Cmp(params.MinimumDifficulty) < 0 { - x.Set(params.MinimumDifficulty) - } - - return x -} - // calcDifficultyDigishieldV3 is the original difficulty adjustment algorithm. // It returns the difficulty that a new block should have when created at time // given the parent block's time and difficulty. diff --git a/tests/difficulty_test_util.go b/tests/difficulty_test_util.go index 920cd5d525bf..66a0529c5478 100644 --- a/tests/difficulty_test_util.go +++ b/tests/difficulty_test_util.go @@ -17,12 +17,12 @@ package tests import ( - "fmt" + // "fmt" "math/big" "github.com/ubiq/go-ubiq/common" "github.com/ubiq/go-ubiq/common/math" - "github.com/ubiq/go-ubiq/consensus/ubqhash" + // "github.com/ubiq/go-ubiq/consensus/ubqhash" //"github.com/ubiq/go-ubiq/core/types" "github.com/ubiq/go-ubiq/params" @@ -49,6 +49,7 @@ type difficultyTestMarshaling struct { } func (test *DifficultyTest) Run(config *params.ChainConfig) error { + /* TODO: write new difficulty tests parentNumber := big.NewInt(int64(test.CurrentBlockNumber - 1)) actual := ubqhash.CalcDifficultyLegacy(test.CurrentTimestamp, test.ParentTimestamp, parentNumber, test.ParentDifficulty) @@ -58,7 +59,7 @@ func (test *DifficultyTest) Run(config *params.ChainConfig) error { return fmt.Errorf("parent[time %v diff %v unclehash:%x] child[time %v number %v] diff %v != expected %v", test.ParentTimestamp, test.ParentDifficulty, test.UncleHash, test.CurrentTimestamp, test.CurrentBlockNumber, actual, exp) - } + }*/ return nil } From 43f49607e09ef1143f4dfcc71bff1496d2b74b28 Mon Sep 17 00:00:00 2001 From: Julian Yap Date: Tue, 24 Dec 2019 00:10:46 -1000 Subject: [PATCH 22/29] Remove unused variables --- consensus/ubqhash/consensus.go | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/consensus/ubqhash/consensus.go b/consensus/ubqhash/consensus.go index 776be9835bf4..c89029119004 100644 --- a/consensus/ubqhash/consensus.go +++ b/consensus/ubqhash/consensus.go @@ -45,8 +45,7 @@ var ( // Diff algo constants. var ( - big88 = big.NewInt(88) - bigMinus99 = big.NewInt(-99) + big88 = big.NewInt(88) digishieldV3Config = &diffConfig{ AveragingWindow: big.NewInt(21), @@ -381,12 +380,6 @@ func CalcDifficulty(chain consensus.ChainReader, time uint64, parent *types.Head return calcDifficultyFlux(chain, big.NewInt(int64(time)), big.NewInt(int64(parentTime)), parentNumber, parentDiff, parent) } -// Some weird constants to avoid constant memory allocs for them. -var ( - expDiffPeriod = big.NewInt(100000) - big10 = big.NewInt(10) -) - // calcDifficultyDigishieldV3 is the original difficulty adjustment algorithm. // It returns the difficulty that a new block should have when created at time // given the parent block's time and difficulty. @@ -595,7 +588,6 @@ func (ubqhash *Ubqhash) Finalize(chain consensus.ChainReader, header *types.Head // Some weird constants to avoid constant memory allocs for them. var ( big2 = big.NewInt(2) - big8 = big.NewInt(8) big32 = big.NewInt(32) ) From 2cf02cdcd2ccb742af049c711d6bdfaeacfa16ce Mon Sep 17 00:00:00 2001 From: Julian Yap Date: Tue, 24 Dec 2019 01:15:09 -1000 Subject: [PATCH 23/29] Sync with upstream and remove unused variables --- consensus/ubqhash/consensus.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/consensus/ubqhash/consensus.go b/consensus/ubqhash/consensus.go index c89029119004..e18c32af67fe 100644 --- a/consensus/ubqhash/consensus.go +++ b/consensus/ubqhash/consensus.go @@ -83,13 +83,11 @@ type diffConfig struct { // codebase, inherently breaking if the engine is swapped out. Please put common // error types into the consensus package. var ( - errLargeBlockTime = errors.New("timestamp too big") errZeroBlockTime = errors.New("timestamp equals parent's") errTooManyUncles = errors.New("too many uncles") errDuplicateUncle = errors.New("duplicate uncle") errUncleIsAncestor = errors.New("uncle is ancestor") errDanglingUncle = errors.New("uncle's parent is not ancestor") - errNonceOutOfRange = errors.New("nonce out of range") errInvalidDifficulty = errors.New("non-positive difficulty") errInvalidMixDigest = errors.New("invalid mix digest") errInvalidPoW = errors.New("invalid proof-of-work") From 39b316e6793626853c08a23799b5dbbee9c6070f Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Tue, 24 Dec 2019 17:58:47 +0100 Subject: [PATCH 24/29] consensus/ubqhash: be careful with pointers and block rewards --- consensus/ubqhash/consensus.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/consensus/ubqhash/consensus.go b/consensus/ubqhash/consensus.go index c89029119004..66b0b0e52338 100644 --- a/consensus/ubqhash/consensus.go +++ b/consensus/ubqhash/consensus.go @@ -621,7 +621,7 @@ func CalcBaseBlockReward(config *params.UbqhashConfig, height *big.Int) *big.Int for _, step := range config.MonetaryPolicy { if height.Cmp(step.Block) > 0 { - reward = step.Reward + reward = new(big.Int).Set(step.Reward) } } @@ -656,7 +656,7 @@ func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header // Uncle reward step down fix. (activates along-side byzantium) ufixReward := new(big.Int).Set(ubqhashConfig.BlockReward) if config.IsByzantium(header.Number) { - ufixReward = reward + ufixReward = new(big.Int).Set(reward) } for _, uncle := range uncles { From 49f69277cb32331779d331cf44bd29c7149c4237 Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Wed, 25 Dec 2019 22:34:14 +0100 Subject: [PATCH 25/29] consensus/ubqhash: cleanup and optimize after refactor --- consensus/ubqhash/consensus.go | 24 +++++++++++------------- params/config.go | 10 ++++++++-- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/consensus/ubqhash/consensus.go b/consensus/ubqhash/consensus.go index 2a28b10a509b..047476b42a93 100644 --- a/consensus/ubqhash/consensus.go +++ b/consensus/ubqhash/consensus.go @@ -38,7 +38,6 @@ import ( // Ubqhash proof-of-work protocol constants. var ( - // blockReward *big.Int = big.NewInt(8e+18) // Block reward in wei for successfully mining a block maxUncles = 2 // Maximum number of uncles allowed in a single block allowedFutureBlockTime = 15 * time.Second // Max time from current time allowed for blocks, before they're considered future blocks ) @@ -612,18 +611,19 @@ func (ubqhash *Ubqhash) SealHash(header *types.Header) (hash common.Hash) { return hash } -// CalcBaseBlockReward calculates the base block reward as per the ubiq monetary -// policy. -func CalcBaseBlockReward(config *params.UbqhashConfig, height *big.Int) *big.Int { - reward := new(big.Int).Set(config.BlockReward) +// CalcBaseBlockReward calculates the base block reward as per the ubiq monetary policy. +func CalcBaseBlockReward(config *params.UbqhashConfig, height *big.Int) (*big.Int, *big.Int) { + reward := new(big.Int) for _, step := range config.MonetaryPolicy { if height.Cmp(step.Block) > 0 { reward = new(big.Int).Set(step.Reward) + } else { + break } } - return reward + return new(big.Int).Set(config.MonetaryPolicy[0].Reward), reward } // CalcUncleBlockReward calculates the uncle miner reward based on depth. @@ -646,15 +646,13 @@ func CalcUncleBlockReward(config *params.ChainConfig, blockHeight *big.Int, uncl // reward. The total reward consists of the static block reward and rewards for // included uncles. The coinbase of each uncle block is also rewarded. func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) { - ubqhashConfig := config.Ubqhash - // block reward (miner) - reward := CalcBaseBlockReward(ubqhashConfig, header.Number) + initialReward, currentReward := CalcBaseBlockReward(config.Ubqhash, header.Number) // Uncle reward step down fix. (activates along-side byzantium) - ufixReward := new(big.Int).Set(ubqhashConfig.BlockReward) + ufixReward := initialReward if config.IsByzantium(header.Number) { - ufixReward = new(big.Int).Set(reward) + ufixReward = currentReward } for _, uncle := range uncles { @@ -664,8 +662,8 @@ func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header state.AddBalance(uncle.Coinbase, uncleReward) // include uncle bonus reward (baseBlockReward/32) uncleReward.Div(ufixReward, big32) - reward.Add(reward, uncleReward) + currentReward.Add(currentReward, uncleReward) } // update block miner balance - state.AddBalance(header.Coinbase, reward) + state.AddBalance(header.Coinbase, currentReward) } diff --git a/params/config.go b/params/config.go index 241b463c1cb5..8a413d67fb21 100644 --- a/params/config.go +++ b/params/config.go @@ -43,10 +43,13 @@ var ( ConstantinopleBlock: big.NewInt(math.MaxInt64), PetersburgBlock: big.NewInt(math.MaxInt64), Ubqhash: &UbqhashConfig{ - BlockReward: big.NewInt(8e+18), // 8 UBQ in wei DigishieldModBlock: big.NewInt(4088), FluxBlock: big.NewInt(8000), MonetaryPolicy: []UbqhashMPStep{ + UbqhashMPStep{ + Block: big.NewInt(0), + Reward: big.NewInt(8e+18), + }, UbqhashMPStep{ Block: big.NewInt(358363), Reward: big.NewInt(7e+18), @@ -100,10 +103,13 @@ var ( ConstantinopleBlock: big.NewInt(math.MaxInt64), PetersburgBlock: big.NewInt(math.MaxInt64), Ubqhash: &UbqhashConfig{ - BlockReward: big.NewInt(8e+18), // 8 UBQ in wei DigishieldModBlock: big.NewInt(4088), FluxBlock: big.NewInt(8000), MonetaryPolicy: []UbqhashMPStep{ + UbqhashMPStep{ + Block: big.NewInt(0), + Reward: big.NewInt(8e+18), + }, UbqhashMPStep{ Block: big.NewInt(358363), Reward: big.NewInt(7e+18), From 92d9775c32372b7a3df68d5df57f0970c6f9d7e5 Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Thu, 26 Dec 2019 00:31:06 +0100 Subject: [PATCH 26/29] consensus/ubqhash: update tests --- consensus/ubqhash/consensus_test.go | 37 +++++++++++++++-------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/consensus/ubqhash/consensus_test.go b/consensus/ubqhash/consensus_test.go index ad7c595e147e..2781c9bc2ef6 100644 --- a/consensus/ubqhash/consensus_test.go +++ b/consensus/ubqhash/consensus_test.go @@ -107,77 +107,77 @@ func TestCalcDifficulty(t *testing.T) { func TestCalcBaseBlockReward(t *testing.T) { config := *params.MainnetChainConfig - reward := CalcBaseBlockReward(config.Ubqhash, big.NewInt(1)) + _, reward := CalcBaseBlockReward(config.Ubqhash, big.NewInt(1)) if reward.Cmp(big.NewInt(8e+18)) != 0 { t.Error("TestCalcBaseBlockReward 8 (start)", "failed. Expected", big.NewInt(8e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(358363)) + _, reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(358363)) if reward.Cmp(big.NewInt(8e+18)) != 0 { t.Error("TestCalcBaseBlockReward 8 (end)", "failed. Expected", big.NewInt(8e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(358364)) + _, reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(358364)) if reward.Cmp(big.NewInt(7e+18)) != 0 { t.Error("TestCalcBaseBlockReward 7 (start)", "failed. Expected", big.NewInt(7e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(716727)) + _, reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(716727)) if reward.Cmp(big.NewInt(7e+18)) != 0 { t.Error("TestCalcBaseBlockReward 7 (end)", "failed. Expected", big.NewInt(7e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(716728)) + _, reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(716728)) if reward.Cmp(big.NewInt(6e+18)) != 0 { t.Error("TestCalcBaseBlockReward 6 (start)", "failed. Expected", big.NewInt(6e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(1075090)) + _, reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(1075090)) if reward.Cmp(big.NewInt(6e+18)) != 0 { t.Error("TestCalcBaseBlockReward 6 (end)", "failed. Expected", big.NewInt(6e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(1075091)) + _, reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(1075091)) if reward.Cmp(big.NewInt(5e+18)) != 0 { t.Error("TestCalcBaseBlockReward 5 (start)", "failed. Expected", big.NewInt(5e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(1433454)) + _, reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(1433454)) if reward.Cmp(big.NewInt(5e+18)) != 0 { t.Error("TestCalcBaseBlockReward 5 (end)", "failed. Expected", big.NewInt(5e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(1433455)) + _, reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(1433455)) if reward.Cmp(big.NewInt(4e+18)) != 0 { t.Error("TestCalcBaseBlockReward 4 (start)", "failed. Expected", big.NewInt(4e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(1791818)) + _, reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(1791818)) if reward.Cmp(big.NewInt(4e+18)) != 0 { t.Error("TestCalcBaseBlockReward 4 (end)", "failed. Expected", big.NewInt(4e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(1791819)) + _, reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(1791819)) if reward.Cmp(big.NewInt(3e+18)) != 0 { t.Error("TestCalcBaseBlockReward 3 (start)", "failed. Expected", big.NewInt(3e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(2150181)) + _, reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(2150181)) if reward.Cmp(big.NewInt(3e+18)) != 0 { t.Error("TestCalcBaseBlockReward 3 (end)", "failed. Expected", big.NewInt(3e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(2150182)) + _, reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(2150182)) if reward.Cmp(big.NewInt(2e+18)) != 0 { t.Error("TestCalcBaseBlockReward 2 (start)", "failed. Expected", big.NewInt(2e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(2508545)) + _, reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(2508545)) if reward.Cmp(big.NewInt(2e+18)) != 0 { t.Error("TestCalcBaseBlockReward 2 (end)", "failed. Expected", big.NewInt(2e+18), "and calculated", reward) } - reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(2508546)) + _, reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(2508546)) if reward.Cmp(big.NewInt(1e+18)) != 0 { t.Error("TestCalcBaseBlockReward 1 (start)", "failed. Expected", big.NewInt(1e+18), "and calculated", reward) } } func TestCalcUncleBlockReward(t *testing.T) { - config := ¶ms.ChainConfig{HomesteadBlock: big.NewInt(0), EIP158Block: big.NewInt(10)} + config := params.MainnetChainConfig reward := big.NewInt(8e+18) // depth 1 u := CalcUncleBlockReward(config, big.NewInt(5), big.NewInt(4), reward) @@ -203,7 +203,7 @@ func TestCalcUncleBlockReward(t *testing.T) { t.Error("TestCalcUncleBlockReward 8", "failed. Expected", big.NewInt(0), "and calculated", u) } - reward = big.NewInt(7e+18) + _, reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(358364)) expected := big.NewInt(35e+17) // depth 1 (after stepdown) u = CalcUncleBlockReward(config, big.NewInt(8), big.NewInt(7), reward) @@ -211,11 +211,12 @@ func TestCalcUncleBlockReward(t *testing.T) { t.Error("TestCalcUncleBlockReward 7", "failed. Expected", expected, "and calculated", u) } - reward = big.NewInt(5e+18) + _, reward = CalcBaseBlockReward(config.Ubqhash, big.NewInt(1075091)) expected = big.NewInt(25e+17) // depth 1 (after stepdown) u = CalcUncleBlockReward(config, big.NewInt(8), big.NewInt(7), reward) if u.Cmp(expected) != 0 { t.Error("TestCalcUncleBlockReward 5", "failed. Expected", expected, "and calculated", u) } + } From c7fa5468c94a607151e03379b03404820bbcf7ad Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Thu, 26 Dec 2019 00:39:27 +0100 Subject: [PATCH 27/29] params/config: update default chain params after earlier optimizations --- params/config.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/params/config.go b/params/config.go index 8a413d67fb21..636a23a2cbe4 100644 --- a/params/config.go +++ b/params/config.go @@ -156,7 +156,7 @@ var ( // // This configuration is intentionally not using keyed fields to force anyone // adding flags to the config to also have to set these fields. - AllUbqhashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, &UbqhashConfig{big.NewInt(0), big.NewInt(0), big.NewInt(0), []UbqhashMPStep{}}, nil} + AllUbqhashProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, &UbqhashConfig{big.NewInt(0), big.NewInt(0), []UbqhashMPStep{}}, nil} // AllCliqueProtocolChanges contains every protocol change (EIPs) introduced // and accepted by the Ubiq core developers into the Clique consensus. @@ -165,7 +165,7 @@ var ( // adding flags to the config to also have to set these fields. AllCliqueProtocolChanges = &ChainConfig{big.NewInt(1337), big.NewInt(0), big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, nil, &CliqueConfig{Period: 0, Epoch: 30000}} - TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, &UbqhashConfig{big.NewInt(0), big.NewInt(0), big.NewInt(0), []UbqhashMPStep{}}, nil} + TestChainConfig = &ChainConfig{big.NewInt(1), big.NewInt(0), big.NewInt(0), common.Hash{}, big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), big.NewInt(0), nil, &UbqhashConfig{big.NewInt(0), big.NewInt(0), []UbqhashMPStep{}}, nil} TestRules = TestChainConfig.Rules(new(big.Int)) ) @@ -216,7 +216,6 @@ type UbqhashMPStep struct { // UbqhashConfig is the consensus engine configs for proof-of-work based sealing. type UbqhashConfig struct { - BlockReward *big.Int `json:"blockReward"` // Initial block reward in wei for mining a block DigishieldModBlock *big.Int `json:"digishieldModBlock,omitempty"` // Block to activate the DigiShield V3 mod FluxBlock *big.Int `json:"fluxBlock"` // Block to activate the Flux difficulty algorithm MonetaryPolicy []UbqhashMPStep `json:"monetaryPolicy"` // Blocks to step the block reward down From e0734ca39220b8557ed106cb8bf6da44ae125d0b Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Thu, 26 Dec 2019 00:43:50 +0100 Subject: [PATCH 28/29] Andromeda: Byzantium, Constantinople, Petersburg (mainnet block: 1075090) --- params/config.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/params/config.go b/params/config.go index 636a23a2cbe4..00bf4b799663 100644 --- a/params/config.go +++ b/params/config.go @@ -39,9 +39,9 @@ var ( EIP150Hash: common.HexToHash("0x2086799aeebeae135c246c65021c82b4e15a2c451340993aacfd2751886514f0"), EIP155Block: big.NewInt(10), EIP158Block: big.NewInt(10), - ByzantiumBlock: big.NewInt(math.MaxInt64), - ConstantinopleBlock: big.NewInt(math.MaxInt64), - PetersburgBlock: big.NewInt(math.MaxInt64), + ByzantiumBlock: big.NewInt(1075090), // Andromeda + ConstantinopleBlock: big.NewInt(1075090), // Andromeda + PetersburgBlock: big.NewInt(1075090), // Andromeda Ubqhash: &UbqhashConfig{ DigishieldModBlock: big.NewInt(4088), FluxBlock: big.NewInt(8000), From c2fa674c44210b8196433308dddf06a3e5b8282d Mon Sep 17 00:00:00 2001 From: Luke Williams Date: Thu, 26 Dec 2019 00:46:28 +0100 Subject: [PATCH 29/29] Andromeda: bump version meta --- params/version.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/params/version.go b/params/version.go index 477c49cd427f..27349de94e46 100644 --- a/params/version.go +++ b/params/version.go @@ -21,10 +21,10 @@ import ( ) const ( - VersionMajor = 3 // Major version component of the current release - VersionMinor = 0 // Minor version component of the current release - VersionPatch = 0 // Patch version component of the current release - VersionMeta = "testing" // Version metadata to append to the version string + VersionMajor = 3 // Major version component of the current release + VersionMinor = 0 // Minor version component of the current release + VersionPatch = 0 // Patch version component of the current release + VersionMeta = "andromeda" // Version metadata to append to the version string ) // Version holds the textual version string.