diff --git a/cmd/accumulated-http/main.go b/cmd/accumulated-http/main.go index 91583ada6..a026271fe 100644 --- a/cmd/accumulated-http/main.go +++ b/cmd/accumulated-http/main.go @@ -49,18 +49,21 @@ var cmd = &cobra.Command{ } var flag = struct { - Key string - LogLevel string - HttpListen []multiaddr.Multiaddr - P2pListen []multiaddr.Multiaddr - Peers []multiaddr.Multiaddr - Timeout time.Duration - ConnLimit int - CorsOrigins []string - LetsEncrypt []string - TlsCert string - TlsKey string -}{} + Key string + LogLevel string + HttpListen []multiaddr.Multiaddr + P2pListen []multiaddr.Multiaddr + Peers []multiaddr.Multiaddr + Timeout time.Duration + ConnLimit int + CorsOrigins []string + LetsEncrypt []string + TlsCert string + TlsKey string + PeerDatabase string +}{ + Peers: api.BootstrapServers, +} func init() { cmd.Flags().StringVar(&flag.Key, "key", "", "The node key - not required but highly recommended. The value can be a key or a file containing a key. The key must be hex, base64, or an Accumulate secret key address.") @@ -74,9 +77,8 @@ func init() { cmd.Flags().StringSliceVar(&flag.LetsEncrypt, "lets-encrypt", nil, "Enable HTTPS on 443 and use Let's Encrypt to retrieve a certificate. Use of this feature implies acceptance of the LetsEncrypt Terms of Service.") cmd.Flags().StringVar(&flag.TlsCert, "tls-cert", "", "Certificate used for HTTPS") cmd.Flags().StringVar(&flag.TlsKey, "tls-key", "", "Private key used for HTTPS") + cmd.Flags().StringVar(&flag.PeerDatabase, "peer-db", "peerdb.json", "Track peers using a persistent database") cmd.Flags().BoolVar(&jsonrpc2.DebugMethodFunc, "debug", false, "Print out a stack trace if an API method fails") - - _ = cmd.MarkFlagRequired("peer") } func run(_ *cobra.Command, args []string) { @@ -107,6 +109,7 @@ func run(_ *cobra.Command, args []string) { Network: args[0], Listen: flag.P2pListen, BootstrapPeers: flag.Peers, + PeerDatabase: flag.PeerDatabase, EnablePeerTracker: true, }) Check(err) diff --git a/pkg/api/v3/p2p/dial.go b/pkg/api/v3/p2p/dial.go index c45adce1d..a8469761d 100644 --- a/pkg/api/v3/p2p/dial.go +++ b/pkg/api/v3/p2p/dial.go @@ -15,8 +15,8 @@ import ( "github.com/multiformats/go-multiaddr" sortutil "gitlab.com/accumulatenetwork/accumulate/internal/util/sort" "gitlab.com/accumulatenetwork/accumulate/pkg/api/v3" - "gitlab.com/accumulatenetwork/accumulate/pkg/api/v3/p2p/dial" "gitlab.com/accumulatenetwork/accumulate/pkg/api/v3/message" + "gitlab.com/accumulatenetwork/accumulate/pkg/api/v3/p2p/dial" "gitlab.com/accumulatenetwork/accumulate/pkg/errors" "golang.org/x/exp/slog" ) @@ -30,16 +30,36 @@ func (n *Node) DialNetwork() message.Dialer { type discoverer Node func (d *discoverer) Discover(ctx context.Context, req *dial.DiscoveryRequest) (<-chan peer.AddrInfo, error) { - addr, err := req.Service.MultiaddrFor(req.Network) - if err != nil { - return nil, err + var addr multiaddr.Multiaddr + if req.Network != "" { + c, err := multiaddr.NewComponent(api.N_ACC, req.Network) + if err != nil { + return nil, errors.BadRequest.WithFormat("create network multiaddr: %w", err) + } + addr = c + } + if req.Service != nil { + if req.Service.Type == api.ServiceTypeUnknown { + return nil, errors.BadRequest.With("missing service type") + } + c := req.Service.Multiaddr() + if addr == nil { + addr = c + } else { + addr = addr.Encapsulate(c) + } + } + if addr == nil { + return nil, errors.BadRequest.With("no network or service specified") } - _, ok := (*Node)(d).getOwnService(req.Network, req.Service) - if ok { - ch := make(chan peer.AddrInfo, 1) - ch <- peer.AddrInfo{ID: d.host.ID(), Addrs: d.host.Addrs()} - return ch, nil + if req.Service != nil { + _, ok := (*Node)(d).getOwnService(req.Network, req.Service) + if ok { + ch := make(chan peer.AddrInfo, 1) + ch <- peer.AddrInfo{ID: d.host.ID(), Addrs: d.host.Addrs()} + return ch, nil + } } return (*Node)(d).peermgr.getPeers(ctx, addr, req.Limit, req.Timeout) diff --git a/pkg/api/v3/p2p/dial/fake.go b/pkg/api/v3/p2p/dial/tracker_fake.go similarity index 100% rename from pkg/api/v3/p2p/dial/fake.go rename to pkg/api/v3/p2p/dial/tracker_fake.go diff --git a/pkg/api/v3/p2p/dial/tracker_persistent.go b/pkg/api/v3/p2p/dial/tracker_persistent.go new file mode 100644 index 000000000..d28388c29 --- /dev/null +++ b/pkg/api/v3/p2p/dial/tracker_persistent.go @@ -0,0 +1,248 @@ +// Copyright 2023 The Accumulate Authors +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +package dial + +import ( + "context" + "os" + "sort" + "sync" + "time" + + "github.com/libp2p/go-libp2p/core/peer" + "github.com/multiformats/go-multiaddr" + "gitlab.com/accumulatenetwork/accumulate/pkg/api/v3" + "gitlab.com/accumulatenetwork/accumulate/pkg/api/v3/p2p/peerdb" + "golang.org/x/exp/slog" +) + +type PersistentTracker struct { + context context.Context + cancel context.CancelFunc + db *peerdb.DB + file string + host Connector + peers Discoverer + stopwg *sync.WaitGroup +} + +type PersistentTrackerOptions struct { + Filename string + Host Connector + Peers Discoverer + PersistFrequency time.Duration +} + +func NewPersistentTracker(ctx context.Context, opts PersistentTrackerOptions) (*PersistentTracker, error) { + t := new(PersistentTracker) + t.db = peerdb.New() + t.file = opts.Filename + t.host = opts.Host + t.peers = opts.Peers + t.stopwg = new(sync.WaitGroup) + + t.context, t.cancel = context.WithCancel(ctx) + + // Ensure the file can be created + f, err := os.OpenFile(opts.Filename, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + return nil, err + } + defer f.Close() + + // If the file is non-empty, read it + st, err := f.Stat() + if err != nil { + return nil, err + } + if st.Size() > 0 { + err = t.db.Load(f) + if err != nil { + return nil, err + } + } + + if opts.PersistFrequency == 0 { + opts.PersistFrequency = time.Hour + } + t.stopwg.Add(1) + go t.writeDb(opts.PersistFrequency) + + return t, nil +} + +func (t *PersistentTracker) Stop() { + t.cancel() + t.stopwg.Wait() +} + +func (t *PersistentTracker) writeDb(frequency time.Duration) { + defer t.stopwg.Done() + + tick := time.NewTicker(frequency) + go func() { <-t.context.Done(); tick.Stop() }() + + for range tick.C { + slog.InfoCtx(t.context, "Writing peer database") + + f, err := os.OpenFile(t.file, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + slog.ErrorCtx(t.context, "Failed to open peer database", "error", err) + continue + } + + err = t.db.Store(f) + if err != nil { + slog.ErrorCtx(t.context, "Failed to write peer database", "error", err) + } + + f.Close() + } +} + +func (t *PersistentTracker) Mark(peer peer.ID, addr multiaddr.Multiaddr, status api.KnownPeerStatus) { + netName, _, service, inetAddr, err := api.UnpackAddress(addr) + if err != nil { + panic(err) + } + + switch status { + case api.PeerStatusIsKnownGood: + if inetAddr != nil { + // Mark that we connected to the address + t.db.Peer(peer).Address(inetAddr).Last.DidSucceed() + } + + // Mark that we connected to the service + t.db.Peer(peer).Network(netName).Service(service).Last.DidSucceed() + + case api.PeerStatusIsUnknown: + // Reset the last connected time + t.db.Peer(peer).Network(netName).Service(service).Last.Success = nil + + case api.PeerStatusIsKnownBad: + // Don't do anything - last attempt will be greater than last success + } +} + +func (t *PersistentTracker) Status(peer peer.ID, addr multiaddr.Multiaddr) api.KnownPeerStatus { + netName, _, service, _, err := api.UnpackAddress(addr) + if err != nil { + panic(err) + } + + s := t.db.Peer(peer).Network(netName).Service(service) + return statusForLast(s.Last) +} + +func statusForLast(l peerdb.LastStatus) api.KnownPeerStatus { + switch { + case l.Attempt == nil: + // No connection attempted + return api.PeerStatusIsUnknown + + case l.Success == nil: + // No successful connection + + // Attempt was too long ago? + if attemptIsTooOld(l) { + return api.PeerStatusIsKnownBad + } + + // Attempt was recent + return api.PeerStatusIsUnknown + + case l.Attempt.After(*l.Success): + // Connection attempted since the last success + + // Attempt was too long ago? + if attemptIsTooOld(l) { + return api.PeerStatusIsKnownBad + } + + // Last success was too long ago? + return statusForLastSuccess(l) + + default: + // Last attempt was successful + + // Was it too long ago? + return statusForLastSuccess(l) + } +} + +func attemptIsTooOld(l peerdb.LastStatus) bool { + return time.Since(*l.Attempt) > time.Second +} + +func statusForLastSuccess(l peerdb.LastStatus) api.KnownPeerStatus { + // Last success was too long ago? + if time.Since(*l.Success) > 10*time.Minute { + return api.PeerStatusIsUnknown + } + + // Last success was recent + return api.PeerStatusIsKnownGood +} + +func (t *PersistentTracker) Next(addr multiaddr.Multiaddr, status api.KnownPeerStatus) (peer.ID, bool) { + netName, _, service, _, err := api.UnpackAddress(addr) + if err != nil { + panic(err) + } + + // Get all the candidates with the given status + var candidates []*peerdb.PeerStatus + for _, p := range t.db.Peers() { + s := p.Network(netName).Service(service) + if statusForLast(s.Last) == status { + candidates = append(candidates, p) + } + } + if len(candidates) == 0 { + return "", false + } + + switch status { + case api.PeerStatusIsKnownGood, + api.PeerStatusIsKnownBad: + // Pick the least recently used one + sort.Slice(candidates, func(i, j int) bool { + a := candidates[i].Network(netName).Service(service) + b := candidates[j].Network(netName).Service(service) + switch { + case a.Last.Attempt == nil || b.Last.Attempt == nil: + return false + case a.Last.Attempt == nil: + return true + case b.Last.Attempt == nil: + return false + default: + return a.Last.Attempt.Before(*b.Last.Attempt) + } + }) + } + + candidates[0].Network(netName).Service(service).Last.DidAttempt() + return candidates[0].ID, true +} + +func (t *PersistentTracker) All(addr multiaddr.Multiaddr, status api.KnownPeerStatus) []peer.ID { + netName, _, service, _, err := api.UnpackAddress(addr) + if err != nil { + panic(err) + } + + var peers []peer.ID + for _, p := range t.db.Peers() { + s := p.Network(netName).Service(service) + if statusForLast(s.Last) == status { + peers = append(peers, p.ID) + } + } + return peers +} diff --git a/pkg/api/v3/p2p/dial/simple.go b/pkg/api/v3/p2p/dial/tracker_simple.go similarity index 100% rename from pkg/api/v3/p2p/dial/simple.go rename to pkg/api/v3/p2p/dial/tracker_simple.go diff --git a/pkg/api/v3/p2p/p2p.go b/pkg/api/v3/p2p/p2p.go index 7daf835ff..afd95ac7b 100644 --- a/pkg/api/v3/p2p/p2p.go +++ b/pkg/api/v3/p2p/p2p.go @@ -10,6 +10,7 @@ import ( "context" "crypto/ed25519" "net" + "time" "github.com/libp2p/go-libp2p" dht "github.com/libp2p/go-libp2p-kad-dht" @@ -25,21 +26,6 @@ import ( "gitlab.com/accumulatenetwork/accumulate/pkg/errors" ) -var BootstrapNodes = func() []multiaddr.Multiaddr { - p := func(s string) multiaddr.Multiaddr { - addr, err := multiaddr.NewMultiaddr(s) - if err != nil { - panic(err) - } - return addr - } - - return []multiaddr.Multiaddr{ - // Defi Devs bootstrap node - p("/dns/bootstrap.accumulate.defidevs.io/tcp/16593/p2p/12D3KooWGJTh4aeF7bFnwo9sAYRujCkuVU1Cq8wNeTNGpFgZgXdg"), - } -}() - // Node implements peer-to-peer routing of API v3 messages over via binary // message transport. type Node struct { @@ -79,6 +65,8 @@ type Options struct { // EnablePeerTracker enables the peer tracker to reduce the impact of // mis-configured peers. This is currently experimental. EnablePeerTracker bool + + PeerDatabase string } // New creates a node with the given [Options]. @@ -87,7 +75,17 @@ func New(opts Options) (_ *Node, err error) { n := new(Node) n.context, n.cancel = context.WithCancel(context.Background()) - if opts.EnablePeerTracker { + if opts.PeerDatabase != "" { + n.tracker, err = dial.NewPersistentTracker(n.context, dial.PersistentTrackerOptions{ + Filename: opts.PeerDatabase, + Host: (*connector)(n), + Peers: (*discoverer)(n), + PersistFrequency: 10 * time.Second, + }) + if err != nil { + return nil, err + } + } else if opts.EnablePeerTracker { n.tracker = new(dial.SimpleTracker) } else { n.tracker = dial.FakeTracker diff --git a/pkg/api/v3/p2p/peer_manager.go b/pkg/api/v3/p2p/peer_manager.go index df1bf778f..b087b7a0e 100644 --- a/pkg/api/v3/p2p/peer_manager.go +++ b/pkg/api/v3/p2p/peer_manager.go @@ -93,13 +93,28 @@ func newPeerManager(ctx context.Context, host host.Host, getServices func() []*s // getPeers queries the DHT for peers that provide the given service. func (m *peerManager) getPeers(ctx context.Context, ma multiaddr.Multiaddr, limit int, timeout time.Duration) (<-chan peer.AddrInfo, error) { - if timeout > 0 { - var cancel context.CancelFunc - ctx, cancel = context.WithTimeout(ctx, timeout) - defer cancel() + ch, err := m.routing.FindPeers(ctx, ma.String(), discovery.Limit(limit)) + if err != nil || timeout == 0 { + return ch, err } - return m.routing.FindPeers(ctx, ma.String(), discovery.Limit(limit)) + ch2 := make(chan peer.AddrInfo) + stop := time.After(timeout) + go func() { + defer close(ch2) + for { + select { + case <-stop: + return + case v, ok := <-ch: + if !ok { + return + } + ch2 <- v + } + } + }() + return ch2, nil } // advertizeNewService advertizes new whoami info to everyone. @@ -144,7 +159,7 @@ func (m *peerManager) waitFor(ctx context.Context, addr multiaddr.Multiaddr) err wait := <-m.wait // Look for a peer - ch, err := m.routing.FindPeers(ctx, addr.String(), discovery.Limit(1)) + ch, err := m.getPeers(ctx, addr, 1, 0) if err != nil { return err } diff --git a/pkg/api/v3/p2p/peerdb/atomic.go b/pkg/api/v3/p2p/peerdb/atomic.go new file mode 100644 index 000000000..b9cef5e13 --- /dev/null +++ b/pkg/api/v3/p2p/peerdb/atomic.go @@ -0,0 +1,122 @@ +// Copyright 2023 The Accumulate Authors +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +package peerdb + +import ( + "encoding/json" + "sync/atomic" + + sortutil "gitlab.com/accumulatenetwork/accumulate/internal/util/sort" +) + +type ptrValue[T any] interface { + ~*T + Copy() *T + Equal(*T) bool + Compare(*T) int +} + +type AtomicSlice[PT ptrValue[T], T any] atomic.Pointer[[]PT] + +func (s *AtomicSlice[PT, T]) p() *atomic.Pointer[[]PT] { + return (*atomic.Pointer[[]PT])(s) +} + +func (s *AtomicSlice[PT, T]) Get(target PT) (PT, bool) { + l := s.Load() + i, ok := sortutil.Search(l, func(entry PT) int { + return entry.Compare((*T)(target)) + }) + if ok { + return l[i], true + } + return nil, false +} + +func (s *AtomicSlice[PT, T]) Insert(target PT) PT { + for { + // Is the list empty? + l := s.p().Load() + if l == nil { + m := []PT{target} + if s.p().CompareAndSwap(l, &m) { + return target + } + continue + } + + // Is the element present? + i, found := sortutil.Search(*l, func(entry PT) int { + return entry.Compare((*T)(target)) + }) + if found { + return (*l)[i] + } + + // Copy the list and insert a new element + m := make([]PT, len(*l)+1) + copy(m, (*l)[:i]) + copy(m[i+1:], (*l)[i:]) + m[i] = target + if s.p().CompareAndSwap(l, &m) { + return m[i] + } + } +} + +func (s *AtomicSlice[PT, T]) Load() []PT { + if s == nil { + return nil + } + p := s.p().Load() + if p == nil { + return nil + } + return *p +} + +func (s *AtomicSlice[PT, T]) Store(v []PT) { + s.p().Store(&v) +} + +func (s *AtomicSlice[PT, T]) Copy() *AtomicSlice[PT, T] { + v := s.Load() + u := make([]PT, len(v)) + for i, v := range v { + u[i] = v.Copy() + } + w := new(AtomicSlice[PT, T]) + w.Store(u) + return w +} + +func (s *AtomicSlice[PT, T]) Equal(t *AtomicSlice[PT, T]) bool { + if s == t { + return true + } + v, u := s.Load(), t.Load() + if len(v) != len(u) { + return false + } + for i, v := range v { + if !v.Equal((*T)(u[i])) { + return false + } + } + return true +} + +func (s *AtomicSlice[PT, T]) MarshalJSON() ([]byte, error) { + return json.Marshal(s.Load()) +} + +func (s *AtomicSlice[PT, T]) UnmarshalJSON(b []byte) error { + var l []PT + err := json.Unmarshal(b, &l) + s.Store(l) + return err +} diff --git a/pkg/api/v3/p2p/peerdb/db.go b/pkg/api/v3/p2p/peerdb/db.go new file mode 100644 index 000000000..05742ae3f --- /dev/null +++ b/pkg/api/v3/p2p/peerdb/db.go @@ -0,0 +1,93 @@ +// Copyright 2023 The Accumulate Authors +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +package peerdb + +import ( + "encoding/json" + "errors" + "io" + "io/fs" + "os" + + "github.com/libp2p/go-libp2p/core/peer" + "github.com/multiformats/go-multiaddr" + "gitlab.com/accumulatenetwork/accumulate/pkg/api/v3" +) + +type DB struct { + peers *AtomicSlice[*PeerStatus, PeerStatus] +} + +func New() *DB { + return &DB{peers: new(AtomicSlice[*PeerStatus, PeerStatus])} +} + +func LoadFile(file string) (*DB, error) { + f, err := os.Open(file) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + return New(), nil + } + return nil, err + } + defer f.Close() + db := New() + return db, db.Load(f) +} + +func (db *DB) Load(rd io.Reader) error { + dec := json.NewDecoder(rd) + dec.DisallowUnknownFields() + err := dec.Decode(db.peers) + return err +} + +func (db *DB) Store(wr io.Writer) error { + enc := json.NewEncoder(wr) + enc.SetIndent("", " ") + return enc.Encode(db.peers) +} + +func (db *DB) StoreFile(file string) error { + f, err := os.OpenFile(file, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer f.Close() + return db.Store(f) +} + +func (db *DB) Peers() []*PeerStatus { + return db.peers.Load() +} + +func (db *DB) Peer(id peer.ID) *PeerStatus { + return db.peers.Insert(&PeerStatus{ + ID: id, + Networks: &AtomicSlice[*PeerNetworkStatus, PeerNetworkStatus]{}, + Addresses: &AtomicSlice[*PeerAddressStatus, PeerAddressStatus]{}, + }) +} + +func (p *PeerStatus) Address(addr multiaddr.Multiaddr) *PeerAddressStatus { + return p.Addresses.Insert(&PeerAddressStatus{ + Address: addr, + }) +} + +func (p *PeerStatus) Network(name string) *PeerNetworkStatus { + return p.Networks.Insert(&PeerNetworkStatus{ + Name: name, + Services: &AtomicSlice[*PeerServiceStatus, PeerServiceStatus]{}, + }) +} + +func (p *PeerNetworkStatus) Service(addr *api.ServiceAddress) *PeerServiceStatus { + return p.Services.Insert(&PeerServiceStatus{ + Address: addr, + }) +} diff --git a/pkg/api/v3/p2p/peerdb/types.go b/pkg/api/v3/p2p/peerdb/types.go new file mode 100644 index 000000000..46576fb0d --- /dev/null +++ b/pkg/api/v3/p2p/peerdb/types.go @@ -0,0 +1,68 @@ +// Copyright 2023 The Accumulate Authors +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +package peerdb + +import ( + "encoding/json" + "strings" + "time" +) + +//go:generate go run gitlab.com/accumulatenetwork/accumulate/tools/cmd/gen-types --package peerdb types.yml + +func (s *PeerStatus) Compare(q *PeerStatus) int { + return strings.Compare(s.ID.String(), q.ID.String()) +} + +func (s *PeerAddressStatus) Compare(b *PeerAddressStatus) int { + return strings.Compare(s.Address.String(), b.Address.String()) +} + +func (s *PeerNetworkStatus) Compare(b *PeerNetworkStatus) int { + return strings.Compare(strings.ToLower(s.Name), strings.ToLower(b.Name)) +} + +func (s *PeerServiceStatus) Compare(q *PeerServiceStatus) int { + return s.Address.Compare(q.Address) +} + +func (s *PeerStatus) UnmarshalJSON(b []byte) error { + type T PeerStatus + err := json.Unmarshal(b, (*T)(s)) + if err != nil { + return err + } + if s.Addresses == nil { + s.Addresses = new(AtomicSlice[*PeerAddressStatus, PeerAddressStatus]) + } + if s.Networks == nil { + s.Networks = new(AtomicSlice[*PeerNetworkStatus, PeerNetworkStatus]) + } + return nil +} + +func (s *PeerNetworkStatus) UnmarshalJSON(b []byte) error { + type T PeerNetworkStatus + err := json.Unmarshal(b, (*T)(s)) + if err != nil { + return err + } + if s.Services == nil { + s.Services = new(AtomicSlice[*PeerServiceStatus, PeerServiceStatus]) + } + return nil +} + +func (l *LastStatus) DidAttempt() { + now := time.Now() + l.Attempt = &now +} + +func (l *LastStatus) DidSucceed() { + now := time.Now() + l.Success = &now +} diff --git a/pkg/api/v3/p2p/peerdb/types.yml b/pkg/api/v3/p2p/peerdb/types.yml new file mode 100644 index 000000000..26515b9a5 --- /dev/null +++ b/pkg/api/v3/p2p/peerdb/types.yml @@ -0,0 +1,58 @@ +PeerStatus: + non-binary: true + fields: + - name: ID + type: p2p.PeerID + marshal-as: none + - name: Operator + type: url + pointer: true + - name: Addresses + type: { name: AtomicSlice, parameters: [{ type: PeerAddressStatus, pointer: true }, { type: PeerAddressStatus }] } + marshal-as: reference + pointer: true + - name: Networks + type: { name: AtomicSlice, parameters: [{ type: PeerNetworkStatus, pointer: true }, { type: PeerNetworkStatus }] } + marshal-as: reference + pointer: true + +PeerAddressStatus: + non-binary: true + fields: + - name: Address + type: p2p.Multiaddr + marshal-as: union + - name: Last + type: LastStatus + marshal-as: reference + +PeerNetworkStatus: + non-binary: true + fields: + - name: Name + type: string + - name: Services + type: { name: AtomicSlice, parameters: [{ type: PeerServiceStatus, pointer: true }, { type: PeerServiceStatus }] } + marshal-as: reference + pointer: true + +PeerServiceStatus: + non-binary: true + fields: + - name: Address + type: api.ServiceAddress + marshal-as: reference + pointer: true + - name: Last + type: LastStatus + marshal-as: reference + +LastStatus: + non-binary: true + fields: + - name: Success + type: time + pointer: true + - name: Attempt + type: time + pointer: true \ No newline at end of file diff --git a/pkg/api/v3/p2p/peerdb/types_gen.go b/pkg/api/v3/p2p/peerdb/types_gen.go new file mode 100644 index 000000000..053c03cea --- /dev/null +++ b/pkg/api/v3/p2p/peerdb/types_gen.go @@ -0,0 +1,247 @@ +// Copyright 2022 The Accumulate Authors +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +package peerdb + +// GENERATED BY go run ./tools/cmd/gen-types. DO NOT EDIT. + +//lint:file-ignore S1001,S1002,S1008,SA4013 generated code + +import ( + "encoding/json" + "time" + + "gitlab.com/accumulatenetwork/accumulate/pkg/api/v3" + "gitlab.com/accumulatenetwork/accumulate/pkg/types/encoding" + "gitlab.com/accumulatenetwork/accumulate/pkg/types/p2p" + "gitlab.com/accumulatenetwork/accumulate/pkg/url" +) + +type LastStatus struct { + Success *time.Time `json:"success,omitempty" form:"success" query:"success" validate:"required"` + Attempt *time.Time `json:"attempt,omitempty" form:"attempt" query:"attempt" validate:"required"` +} + +type PeerAddressStatus struct { + Address p2p.Multiaddr `json:"address,omitempty" form:"address" query:"address" validate:"required"` + Last LastStatus `json:"last,omitempty" form:"last" query:"last" validate:"required"` +} + +type PeerNetworkStatus struct { + Name string `json:"name,omitempty" form:"name" query:"name" validate:"required"` + Services *AtomicSlice[*PeerServiceStatus, PeerServiceStatus] `json:"services,omitempty" form:"services" query:"services" validate:"required"` +} + +type PeerServiceStatus struct { + Address *api.ServiceAddress `json:"address,omitempty" form:"address" query:"address" validate:"required"` + Last LastStatus `json:"last,omitempty" form:"last" query:"last" validate:"required"` +} + +type PeerStatus struct { + ID p2p.PeerID + Operator *url.URL `json:"operator,omitempty" form:"operator" query:"operator" validate:"required"` + Addresses *AtomicSlice[*PeerAddressStatus, PeerAddressStatus] `json:"addresses,omitempty" form:"addresses" query:"addresses" validate:"required"` + Networks *AtomicSlice[*PeerNetworkStatus, PeerNetworkStatus] `json:"networks,omitempty" form:"networks" query:"networks" validate:"required"` +} + +func (v *LastStatus) Copy() *LastStatus { + u := new(LastStatus) + + if v.Success != nil { + u.Success = new(time.Time) + *u.Success = *v.Success + } + if v.Attempt != nil { + u.Attempt = new(time.Time) + *u.Attempt = *v.Attempt + } + + return u +} + +func (v *LastStatus) CopyAsInterface() interface{} { return v.Copy() } + +func (v *PeerAddressStatus) Copy() *PeerAddressStatus { + u := new(PeerAddressStatus) + + if v.Address != nil { + u.Address = p2p.CopyMultiaddr(v.Address) + } + u.Last = *(&v.Last).Copy() + + return u +} + +func (v *PeerAddressStatus) CopyAsInterface() interface{} { return v.Copy() } + +func (v *PeerNetworkStatus) Copy() *PeerNetworkStatus { + u := new(PeerNetworkStatus) + + u.Name = v.Name + if v.Services != nil { + u.Services = (v.Services).Copy() + } + + return u +} + +func (v *PeerNetworkStatus) CopyAsInterface() interface{} { return v.Copy() } + +func (v *PeerServiceStatus) Copy() *PeerServiceStatus { + u := new(PeerServiceStatus) + + if v.Address != nil { + u.Address = (v.Address).Copy() + } + u.Last = *(&v.Last).Copy() + + return u +} + +func (v *PeerServiceStatus) CopyAsInterface() interface{} { return v.Copy() } + +func (v *PeerStatus) Copy() *PeerStatus { + u := new(PeerStatus) + + if v.Operator != nil { + u.Operator = v.Operator + } + if v.Addresses != nil { + u.Addresses = (v.Addresses).Copy() + } + if v.Networks != nil { + u.Networks = (v.Networks).Copy() + } + + return u +} + +func (v *PeerStatus) CopyAsInterface() interface{} { return v.Copy() } + +func (v *LastStatus) Equal(u *LastStatus) bool { + switch { + case v.Success == u.Success: + // equal + case v.Success == nil || u.Success == nil: + return false + case !((*v.Success).Equal(*u.Success)): + return false + } + switch { + case v.Attempt == u.Attempt: + // equal + case v.Attempt == nil || u.Attempt == nil: + return false + case !((*v.Attempt).Equal(*u.Attempt)): + return false + } + + return true +} + +func (v *PeerAddressStatus) Equal(u *PeerAddressStatus) bool { + if !(p2p.EqualMultiaddr(v.Address, u.Address)) { + return false + } + if !((&v.Last).Equal(&u.Last)) { + return false + } + + return true +} + +func (v *PeerNetworkStatus) Equal(u *PeerNetworkStatus) bool { + if !(v.Name == u.Name) { + return false + } + switch { + case v.Services == u.Services: + // equal + case v.Services == nil || u.Services == nil: + return false + case !((v.Services).Equal(u.Services)): + return false + } + + return true +} + +func (v *PeerServiceStatus) Equal(u *PeerServiceStatus) bool { + switch { + case v.Address == u.Address: + // equal + case v.Address == nil || u.Address == nil: + return false + case !((v.Address).Equal(u.Address)): + return false + } + if !((&v.Last).Equal(&u.Last)) { + return false + } + + return true +} + +func (v *PeerStatus) Equal(u *PeerStatus) bool { + switch { + case v.Operator == u.Operator: + // equal + case v.Operator == nil || u.Operator == nil: + return false + case !((v.Operator).Equal(u.Operator)): + return false + } + switch { + case v.Addresses == u.Addresses: + // equal + case v.Addresses == nil || u.Addresses == nil: + return false + case !((v.Addresses).Equal(u.Addresses)): + return false + } + switch { + case v.Networks == u.Networks: + // equal + case v.Networks == nil || u.Networks == nil: + return false + case !((v.Networks).Equal(u.Networks)): + return false + } + + return true +} + +func (v *PeerAddressStatus) MarshalJSON() ([]byte, error) { + u := struct { + Address *encoding.JsonUnmarshalWith[p2p.Multiaddr] `json:"address,omitempty"` + Last LastStatus `json:"last,omitempty"` + }{} + if !(p2p.EqualMultiaddr(v.Address, nil)) { + u.Address = &encoding.JsonUnmarshalWith[p2p.Multiaddr]{Value: v.Address, Func: p2p.UnmarshalMultiaddrJSON} + } + if !((v.Last).Equal(new(LastStatus))) { + u.Last = v.Last + } + return json.Marshal(&u) +} + +func (v *PeerAddressStatus) UnmarshalJSON(data []byte) error { + u := struct { + Address *encoding.JsonUnmarshalWith[p2p.Multiaddr] `json:"address,omitempty"` + Last LastStatus `json:"last,omitempty"` + }{} + u.Address = &encoding.JsonUnmarshalWith[p2p.Multiaddr]{Value: v.Address, Func: p2p.UnmarshalMultiaddrJSON} + u.Last = v.Last + if err := json.Unmarshal(data, &u); err != nil { + return err + } + if u.Address != nil { + v.Address = u.Address.Value + } + + v.Last = u.Last + return nil +}