- commit
- 96111eb
- parent
- 11fcf63
- author
- Evgenii Akentev
- date
- 2024-09-02 13:23:39 +0400 +04
Add example of the chain listener
10 files changed,
+418,
-225
+52,
-0
1@@ -0,0 +1,52 @@
2+package main
3+
4+import (
5+ "context"
6+ "log"
7+
8+ "github.com/xssnick/tonutils-go/liteclient"
9+ "github.com/xssnick/tonutils-go/ton"
10+
11+ "main/contracts"
12+)
13+
14+func listenChain() {
15+ log.Println("Start liteclient")
16+ client := liteclient.NewConnectionPool()
17+
18+ var liteServers string
19+ switch contracts.GetContractEnvironment() {
20+ case "MAINNET":
21+ liteServers = "https://ton.org/global.config.json"
22+ default:
23+ liteServers = "https://ton.org/testnet-global.config.json"
24+ }
25+
26+ // connect to mainnet lite servers
27+ err := client.AddConnectionsFromConfigUrl(context.Background(), liteServers)
28+ if err != nil {
29+ log.Fatalln("connection err: ", err.Error())
30+ return
31+ }
32+
33+ api := ton.NewAPIClient(client, ton.ProofCheckPolicyFast).WithRetry()
34+ ctx := client.StickyContext(context.Background())
35+
36+ for {
37+ log.Println("get masterchain info")
38+ b, err := api.CurrentMasterchainInfo(ctx)
39+ if err != nil {
40+ log.Fatalln("get block err:", err.Error())
41+ return
42+ }
43+
44+ log.Println("read declarations")
45+ declarations := readDeclarations(api, ctx, b, nil)
46+
47+ for _, decl := range declarations {
48+ log.Println("%V", decl)
49+ }
50+
51+
52+ }
53+}
+64,
-107
1@@ -1,134 +1,91 @@
2 package main
3
4 import (
5- "bytes"
6- "log"
7 "net/http"
8- "time"
9-
10+ "log"
11 "github.com/gorilla/websocket"
12 )
13
14-const (
15- // Time allowed to write a message to the peer.
16- writeWait = 10 * time.Second
17-
18- // Time allowed to read the next pong message from the peer.
19- pongWait = 60 * time.Second
20-
21- // Send pings to peer with this period. Must be less than pongWait.
22- pingPeriod = (pongWait * 9) / 10
23-
24- // Maximum message size allowed from peer.
25- maxMessageSize = 512
26-)
27-
28-var (
29- newline = []byte{'\n'}
30- space = []byte{' '}
31-)
32-
33 var upgrader = websocket.Upgrader{
34 ReadBufferSize: 1024,
35 WriteBufferSize: 1024,
36 CheckOrigin: func(r *http.Request) bool{ return true },
37 }
38
39-// Client is a middleman between the websocket connection and the hub.
40 type Client struct {
41 hub *Hub
42-
43- // The websocket connection.
44 conn *websocket.Conn
45-
46- // Buffered channel of outbound messages.
47- send chan []byte
48+ hubResponse chan HubResponse
49 }
50
51-// readPump pumps messages from the websocket connection to the hub.
52-//
53-// The application runs readPump in a per-connection goroutine. The application
54-// ensures that there is at most one reader on a connection by executing all
55-// reads from this goroutine.
56-func (c *Client) readPump() {
57- defer func() {
58- c.hub.unregister <- c
59- c.conn.Close()
60- }()
61- c.conn.SetReadLimit(maxMessageSize)
62- c.conn.SetReadDeadline(time.Now().Add(pongWait))
63- c.conn.SetPongHandler(func(string) error { c.conn.SetReadDeadline(time.Now().Add(pongWait)); return nil })
64- for {
65- _, message, err := c.conn.ReadMessage()
66- if err != nil {
67- if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
68- log.Printf("error: %v", err)
69- }
70- break
71- }
72- message = bytes.TrimSpace(bytes.Replace(message, newline, space, -1))
73- c.hub.broadcast <- message
74- }
75+
76+func (c *Client) run() {
77+ go c.handleChannels()
78+ go c.listenConnection()
79 }
80
81-// writePump pumps messages from the hub to the websocket connection.
82-//
83-// A goroutine running writePump is started for each connection. The
84-// application ensures that there is at most one writer to a connection by
85-// executing all writes from this goroutine.
86-func (c *Client) writePump() {
87- ticker := time.NewTicker(pingPeriod)
88- defer func() {
89- ticker.Stop()
90- c.conn.Close()
91- }()
92- for {
93- select {
94- case message, ok := <-c.send:
95- c.conn.SetWriteDeadline(time.Now().Add(writeWait))
96- if !ok {
97- // The hub closed the channel.
98- c.conn.WriteMessage(websocket.CloseMessage, []byte{})
99- return
100- }
101+func (c *Client) handleChannels() {
102+ defer func() {
103+ close(c.hubResponse)
104+ }()
105+
106+ for {
107+ select {
108+ case response := <- c.hubResponse:
109+ switch r := response.(type) {
110+ case CopyrightsHubResponse:
111+ c.conn.WriteJSON(CopyrightsResponse{"copyrights", r})
112+ case CopyrightStatesHubResponse:
113+ c.conn.WriteJSON(CopyrightStatesResponse{"new-states", r})
114+ }
115+ }
116+ }
117+}
118
119- w, err := c.conn.NextWriter(websocket.TextMessage)
120- if err != nil {
121- return
122- }
123- w.Write(message)
124+func (c *Client) listenConnection() {
125+ defer func() {
126+ c.hub.unregister <- c
127+ c.conn.Close()
128+ close(c.hubResponse)
129+ }()
130
131- // Add queued chat messages to the current websocket message.
132- n := len(c.send)
133- for i := 0; i < n; i++ {
134- w.Write(newline)
135- w.Write(<-c.send)
136- }
137+ for {
138+ var m Message
139+ err := c.conn.ReadJSON(&m)
140
141- if err := w.Close(); err != nil {
142- return
143- }
144- case <-ticker.C:
145- c.conn.SetWriteDeadline(time.Now().Add(writeWait))
146- if err := c.conn.WriteMessage(websocket.PingMessage, nil); err != nil {
147- return
148+ if err != nil {
149+ if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
150+ log.Printf("error: %v", err)
151 }
152+ log.Printf("error: %v", err)
153+ break
154 }
155- }
156-}
157-
158-func serveWs(hub *Hub, w http.ResponseWriter, r *http.Request) {
159- conn, err := upgrader.Upgrade(w, r, nil)
160- if err != nil {
161- log.Println(err)
162- return
163- }
164- client := &Client{hub: hub, conn: conn, send: make(chan []byte, 256)}
165- client.hub.register <- client
166
167- // Allow collection of memory referenced by the caller by doing all work in
168- // new goroutines.
169- go client.writePump()
170- go client.readPump()
171+ switch m.Event {
172+ case "subscribe-copyrights":
173+ for _, addr := range m.Addrs {
174+ c.hub.subscribe <- SubMessage{Contract, addr, c}
175+ }
176+ case "unsubscribe-copyrights":
177+ for _, addr := range m.Addrs {
178+ c.hub.unsubscribe <- SubMessage{Contract, addr, c}
179+ }
180+ case "subscribe-wallets":
181+ for _, addr := range m.Addrs {
182+ c.hub.subscribe <- SubMessage{Wallet, addr, c}
183+ }
184+ case "unsubscribe-wallets":
185+ for _, addr := range m.Addrs {
186+ c.hub.unsubscribe <- SubMessage{Wallet, addr, c}
187+ }
188+ case "get-copyrights":
189+ c.hub.clientQuery <- ClientQuery{c, GetCopyrightsQuery(m.Addrs)}
190+ case "get-copyrights-by-wallets":
191+ c.hub.clientQuery <- ClientQuery{c, GetCopyrightsByWalletsQuery(m.Addrs)}
192+ case "get-states":
193+ c.hub.clientQuery <- ClientQuery{c, GetCopyrightStatesQuery(m.Addrs)}
194+ default:
195+ log.Printf("error: Unsupported event: %v", m)
196+ }
197+ }
198 }
199-
R entities.go =>
contract-entities.go
+28,
-13
1@@ -1,15 +1,5 @@
2 package main
3
4-
5-import (
6-// "encoding/json"
7-)
8-
9-type Message struct {
10- Event string `json:"event"`
11- Addrs []string `json:"addresses"`
12-}
13-
14 type PersonDetails struct {
15 name string
16 address string
17@@ -27,12 +17,13 @@ const (
18 )
19
20 type ExclusiveRightsClaim struct {
21+ author Address
22 }
23
24 type Copyright struct {
25- address string
26- author string
27- owner string
28+ address Address
29+ author Address
30+ owner Address
31 ownerDetails PersonDetails
32 royalty *string
33
34@@ -42,4 +33,28 @@ type Copyright struct {
35 price *int
36 assignmentHash *string
37 claim *ExclusiveRightsClaim
38+
39+ initTxLt int
40+}
41+
42+type CopyrightState struct {
43+ owner Address
44+ ownerDetails PersonDetails
45+ claim *ExclusiveRightsClaim
46+ assignmentHash *string
47+ price *int
48+ balance int
49+ status DeclarationStatus
50+}
51+
52+func (c Copyright) mkState() *CopyrightState {
53+ return &CopyrightState{
54+ owner: c.owner,
55+ ownerDetails: c.ownerDetails,
56+ claim: c.claim,
57+ assignmentHash: c.assignmentHash,
58+ price: c.price,
59+ balance: c.balance,
60+ status: c.status,
61+ }
62 }
+63,
-0
1@@ -0,0 +1,63 @@
2+package main
3+
4+import (
5+ "log"
6+ "context"
7+
8+ "github.com/xssnick/tonutils-go/ton"
9+
10+ "main/contracts"
11+)
12+
13+type DeclareDocument struct {
14+}
15+
16+type DocumentDeclaration struct {
17+ authorAddress Address
18+ declarationAddress Address
19+ txLt int
20+ txHash string
21+ declaration DeclareDocument
22+}
23+
24+func readDeclarations(client ton.APIClientWrapped, ctx context.Context, b *ton.BlockIDExt, prevLt *uint64) []DocumentDeclaration {
25+ res, err := client.WaitForBlock(b.SeqNo).GetAccount(ctx, b, contracts.GetContractAddress())
26+ if err != nil {
27+ log.Fatalln("get account err:", err.Error())
28+ return nil
29+ }
30+
31+ var declarations []DocumentDeclaration
32+
33+ var lastLt = res.LastTxLT
34+ var lastHash = res.LastTxHash
35+
36+ for {
37+ if lastLt == 0 { break }
38+
39+ list, err := client.ListTransactions(ctx, contracts.GetContractAddress(), 15, lastLt, lastHash)
40+ if err != nil {
41+ log.Printf("send err: %s", err.Error())
42+ return nil
43+ }
44+
45+ for _, tx := range list {
46+
47+ if prevLt != nil && tx.LT <= *prevLt {
48+ return declarations
49+ }
50+
51+// var declaration DocumentDeclaration
52+
53+ outMessage := tx.IO.Out.List
54+ log.Println("%V", outMessage)
55+
56+// append(declarations, declaration)
57+ }
58+
59+ lastLt = list[0].PrevTxLT
60+ lastHash = list[0].PrevTxHash
61+ }
62+
63+ return declarations
64+}
M
go.mod
+11,
-1
1@@ -2,4 +2,14 @@ module main
2
3 go 1.22.5
4
5-require github.com/gorilla/websocket v1.5.3
6+require (
7+ github.com/gorilla/websocket v1.5.3
8+ github.com/xssnick/tonutils-go v1.9.9
9+)
10+
11+require (
12+ github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae // indirect
13+ github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 // indirect
14+ golang.org/x/crypto v0.17.0 // indirect
15+ golang.org/x/sys v0.15.0 // indirect
16+)
M
go.sum
+10,
-0
1@@ -1,2 +1,12 @@
2 github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
3 github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
4+github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae h1:7smdlrfdcZic4VfsGKD2ulWL804a4GVphr4s7WZxGiY=
5+github.com/oasisprotocol/curve25519-voi v0.0.0-20220328075252-7dd334e3daae/go.mod h1:hVoHR2EVESiICEMbg137etN/Lx+lSrHPTD39Z/uE+2s=
6+github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3 h1:aQKxg3+2p+IFXXg97McgDGT5zcMrQoi0EICZs8Pgchs=
7+github.com/sigurn/crc16 v0.0.0-20211026045750-20ab5afb07e3/go.mod h1:9/etS5gpQq9BJsJMWg1wpLbfuSnkm8dPF6FdW2JXVhA=
8+github.com/xssnick/tonutils-go v1.9.9 h1:J0hVJI4LNEFHqgRHzpWTjFuv/Ga89OqLRUc9gxmjCoc=
9+github.com/xssnick/tonutils-go v1.9.9/go.mod h1:p1l1Bxdv9sz6x2jfbuGQUGJn6g5cqg7xsTp8rBHFoJY=
10+golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
11+golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
12+golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
13+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
M
hub.go
+56,
-21
1@@ -1,48 +1,83 @@
2 package main
3
4-// Hub maintains the set of active clients and broadcasts messages to the
5-// clients.
6+import (
7+ "slices"
8+ "cmp"
9+ "log"
10+)
11+
12 type Hub struct {
13- // Registered clients.
14 clients map[*Client]bool
15+
16+ copyrights map[Address]*Copyright
17+ clientQuery chan ClientQuery
18
19- // Inbound messages from the clients.
20- broadcast chan []byte
21+ subs *Subscriptions
22+ subscribe chan SubMessage
23+ unsubscribe chan SubMessage
24
25- // Register requests from the clients.
26+ messages chan string
27 register chan *Client
28-
29- // Unregister requests from clients.
30 unregister chan *Client
31 }
32
33 func newHub() *Hub {
34 return &Hub{
35- broadcast: make(chan []byte),
36+ clients: make(map[*Client]bool),
37+ copyrights: make(map[Address]*Copyright),
38+ clientQuery: make(chan ClientQuery),
39+
40+ subs: newSubscriptions(),
41+ subscribe: make(chan SubMessage),
42+ unsubscribe: make(chan SubMessage),
43+
44 register: make(chan *Client),
45- unregister: make(chan *Client),
46- clients: make(map[*Client]bool),
47+ unregister: make(chan *Client),
48 }
49 }
50
51 func (h *Hub) run() {
52 for {
53 select {
54+ case cq := <-h.clientQuery:
55+ switch q := cq.query.(type) {
56+ case GetCopyrightsQuery:
57+ var copyrights []*Copyright
58+ for _, addr := range q {
59+ if c, ok := h.copyrights[addr]; ok {
60+ copyrights = append(copyrights, c)
61+ }
62+ }
63+ cq.client.hubResponse <- CopyrightsHubResponse(copyrights)
64+ case GetCopyrightsByWalletsQuery:
65+ var copyrights []*Copyright
66+ for _, cp := range h.copyrights {
67+ if (slices.Contains(q, cp.author) || slices.Contains(q, cp.owner) || slices.Contains(q, cp.claim.author)) {
68+ copyrights = append(copyrights, cp)
69+ }
70+ }
71+ slices.SortFunc(copyrights, func(a, b *Copyright) int { return cmp.Compare(a.initTxLt, b.initTxLt)})
72+ cq.client.hubResponse <- CopyrightsHubResponse(copyrights)
73+ case GetCopyrightStatesQuery:
74+ var copyrightStates = make(map[Address]*CopyrightState)
75+ for _, addr := range q {
76+ if cp, ok := h.copyrights[addr]; ok {
77+ copyrightStates[addr] = cp.mkState()
78+ }
79+ }
80+ cq.client.hubResponse <- CopyrightStatesHubResponse(copyrightStates)
81+ }
82+ case s := <-h.subscribe:
83+ log.Println("%V", h.subs)
84+ h.subs.subscribe(s.addressType, s.address, s.client)
85+ log.Println("%V", h.subs)
86+ case s := <-h.unsubscribe:
87+ h.subs.unsubscribe(s.addressType, s.address, s.client)
88 case client := <-h.register:
89 h.clients[client] = true
90 case client := <-h.unregister:
91 if _, ok := h.clients[client]; ok {
92 delete(h.clients, client)
93- close(client.send)
94- }
95- case message := <-h.broadcast:
96- for client := range h.clients {
97- select {
98- case client.send <- message:
99- default:
100- close(client.send)
101- delete(h.clients, client)
102- }
103 }
104 }
105 }
M
main.go
+20,
-83
1@@ -4,96 +4,20 @@ import (
2 "flag"
3 "log"
4 "net/http"
5-
6- "github.com/gorilla/websocket"
7+
8 )
9
10-type Subscribers = map[string]([](*websocket.Conn))
11-
12-type State struct {
13- copyrights map[string]Copyright
14- copyrightSubs map[string]([]*websocket.Conn)
15- walletSubs map[string]([]*websocket.Conn)
16-}
17-
18-func newState() *State {
19- return &State {
20- copyrights: make(map[string]Copyright),
21- copyrightSubs: make(map[string]([]*websocket.Conn)),
22- walletSubs: make(map[string]([]*websocket.Conn)),
23- }
24-}
25-
26-func subscribe(m Subscribers, k string, c *websocket.Conn) {
27- var newSubs [](*websocket.Conn)
28- if subs, ok := m[k]; ok {
29- newSubs = append(subs, c)
30- m[k] = newSubs
31- } else {
32- newSubs = make([](*websocket.Conn), 1)
33- newSubs[0] = c
34- m[k] = newSubs
35- }
36-}
37-
38-func unsubscribe(m Subscribers, k string, c *websocket.Conn) {
39- var newSubs [](*websocket.Conn)
40-
41- if subs, ok := m[k]; ok {
42- for _, sub := range subs {
43- if (sub != c) {
44- newSubs = append(newSubs, sub)
45- }
46- }
47- m[k] = newSubs
48- }
49-}
50-
51-func listenConnection(state *State, c *websocket.Conn) {
52- defer func() {
53- c.Close()
54- }()
55-
56- for {
57- var m Message
58- err := c.ReadJSON(&m)
59-
60- if err != nil {
61- if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
62- log.Printf("error: %v", err)
63- }
64- log.Printf("error: %v", err)
65- break
66- }
67-
68- switch m.Event {
69- case "subscribe-wallets":
70- for _, addr := range m.Addrs {
71- subscribe((*state).walletSubs, addr, c)
72- }
73- default:
74- log.Printf("error: Unsupported event: %v", m)
75- }
76- }
77-}
78-
79-func serve(state *State, w http.ResponseWriter, r *http.Request) {
80- conn, err := upgrader.Upgrade(w, r, nil)
81- if err != nil {
82- log.Println(err)
83- return
84- }
85-
86- go listenConnection(state, conn)
87-}
88-
89 func main() {
90 flag.Parse()
91+
92+ hub := newHub()
93+
94+ go hub.run()
95
96- state := newState()
97+ go listenChain()
98
99 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
100- serve(state, w, r)
101+ serve(hub, w, r)
102 })
103
104 log.Println("Started websocket server at :3000")
105@@ -104,3 +28,16 @@ func main() {
106 }
107 }
108
109+func serve(hub *Hub, w http.ResponseWriter, r *http.Request) {
110+ conn, err := upgrader.Upgrade(w, r, nil)
111+ if err != nil {
112+ log.Println(err)
113+ return
114+ }
115+
116+ client := Client{hub: hub, conn: conn}
117+
118+ hub.register <- &client
119+
120+ go client.run()
121+}
+63,
-0
1@@ -0,0 +1,63 @@
2+package main
3+
4+import (
5+// "encoding/json"
6+)
7+
8+type Address string
9+type AddressType int
10+
11+const (
12+ Wallet = iota
13+ Contract
14+)
15+
16+type SubMessage struct {
17+ addressType AddressType
18+ address Address
19+ client *Client
20+}
21+
22+type Query interface { isQuery() }
23+
24+type GetCopyrightsQuery []Address
25+func (gc GetCopyrightsQuery) isQuery() {}
26+
27+type GetCopyrightsByWalletsQuery []Address
28+func (gc GetCopyrightsByWalletsQuery) isQuery() {}
29+
30+type GetCopyrightStatesQuery []Address
31+func (gc GetCopyrightStatesQuery) isQuery() {}
32+
33+type ClientQuery struct {
34+ client *Client
35+ query Query
36+}
37+
38+type HubResponse interface { isHubResponse() }
39+
40+type CopyrightsHubResponse []*Copyright
41+func (cr CopyrightsHubResponse) isHubResponse() {}
42+
43+type CopyrightStatesHubResponse map[Address]*CopyrightState
44+func (cr CopyrightStatesHubResponse) isHubResponse() {}
45+
46+type Message struct {
47+ Event string `json:"event"`
48+ Addrs []Address `json:"addresses"`
49+}
50+
51+type CopyrightsResponse struct {
52+ Event string `json:"event"`
53+ Data any `json:"data"`
54+}
55+
56+type CopyrightStatesResponse struct {
57+ Event string `json:"event"`
58+ States map[Address]*CopyrightState `json:"states"`
59+}
60+
61+type Response struct {
62+ copyrightsResponse *CopyrightsResponse
63+ copyrightStatesResponse *CopyrightStatesResponse
64+}
+51,
-0
1@@ -0,0 +1,51 @@
2+package main
3+
4+type Subscription map[Address](map[*Client]struct{})
5+
6+type Subscriptions struct {
7+ wallets Subscription
8+ copyrights Subscription
9+}
10+
11+func newSubscriptions() *Subscriptions {
12+ return &Subscriptions{
13+ wallets: make(map[Address](map[*Client]struct{})),
14+ copyrights: make(map[Address](map[*Client]struct{})),
15+ }
16+}
17+
18+func (s *Subscriptions) removeClient(c *Client) {
19+ for _, v := range s.wallets {
20+ delete(v, c)
21+ }
22+}
23+
24+func (s *Subscriptions) getSub(at AddressType) Subscription {
25+ switch at {
26+ case Wallet:
27+ return s.wallets
28+ case Contract:
29+ return s.copyrights
30+ }
31+ return nil
32+}
33+
34+func (s *Subscriptions) subscribe(at AddressType, addr Address, c *Client) {
35+ var m = s.getSub(at)
36+ if subs, ok := m[addr]; ok {
37+ subs[c] = struct{}{}
38+ //TODO: not sure if it works, maybe need m[addr] ...
39+ } else {
40+ newsubs := make(map[*Client]struct{})
41+ newsubs[c] = struct{}{}
42+ m[addr] = newsubs
43+ }
44+}
45+
46+func (s *Subscriptions) unsubscribe(at AddressType, addr Address, c *Client) {
47+ var m = s.getSub(at)
48+ if subs, ok := m[addr]; ok {
49+ // TODO: not sure as well
50+ delete(subs, c)
51+ }
52+}