1// SPDX-License-Identifier: ISC
2// Copyright (c) 2014-2020 Bitmark Inc.
3// Use of this source code is governed by an ISC
4// license that can be found in the LICENSE file.
5
6package share
7
8import (
9	"golang.org/x/time/rate"
10
11	"github.com/bitmark-inc/bitmarkd/account"
12	"github.com/bitmark-inc/bitmarkd/fault"
13	"github.com/bitmark-inc/bitmarkd/merkle"
14	"github.com/bitmark-inc/bitmarkd/messagebus"
15	"github.com/bitmark-inc/bitmarkd/mode"
16	"github.com/bitmark-inc/bitmarkd/pay"
17	"github.com/bitmark-inc/bitmarkd/reservoir"
18	"github.com/bitmark-inc/bitmarkd/rpc/owner"
19	"github.com/bitmark-inc/bitmarkd/rpc/ratelimit"
20	"github.com/bitmark-inc/bitmarkd/transactionrecord"
21	"github.com/bitmark-inc/logger"
22)
23
24// Share
25// --------
26
27const (
28	rateLimitShare = 200
29	rateBurstShare = 100
30)
31
32// Share - type for RPC
33type Share struct {
34	Log          *logger.L
35	Limiter      *rate.Limiter
36	IsNormalMode func(mode.Mode) bool
37	Rsvr         reservoir.Reservoir
38}
39
40func New(log *logger.L, isNormalMode func(mode.Mode) bool, rsvr reservoir.Reservoir) *Share {
41	return &Share{
42		Log:          log,
43		Limiter:      rate.NewLimiter(rateLimitShare, rateBurstShare),
44		IsNormalMode: isNormalMode,
45		Rsvr:         rsvr,
46	}
47}
48
49// Create a share with initial balance
50// -----------------------------------
51
52// CreateReply - results from creating a share
53type CreateReply struct {
54	TxId     merkle.Digest                                   `json:"txId"`
55	ShareId  merkle.Digest                                   `json:"shareId"`
56	PayId    pay.PayId                                       `json:"payId"`
57	Payments map[string]transactionrecord.PaymentAlternative `json:"payments"`
58}
59
60// Create - create fractional bitmark
61func (share *Share) Create(bmfr *transactionrecord.BitmarkShare, reply *CreateReply) error {
62
63	if err := ratelimit.Limit(share.Limiter); nil != err {
64		return err
65	}
66
67	log := share.Log
68
69	log.Infof("Share.Create: %+v", bmfr)
70
71	if !share.IsNormalMode(mode.Normal) {
72		return fault.NotAvailableDuringSynchronise
73	}
74
75	// save transfer/check for duplicate
76	stored, duplicate, err := share.Rsvr.StoreTransfer(bmfr)
77	if nil != err {
78		return err
79	}
80
81	payId := stored.Id
82	txId := stored.TxId
83	shareId := stored.IssueTxId
84	packed := stored.Packed
85
86	log.Debugf("id: %v", txId)
87	log.Debugf("share id: %v", shareId)
88
89	reply.TxId = txId
90	reply.ShareId = shareId
91	reply.PayId = payId
92	reply.Payments = make(map[string]transactionrecord.PaymentAlternative)
93
94	for _, payment := range stored.Payments {
95		c := payment[0].Currency.String()
96		reply.Payments[c] = payment
97	}
98
99	// announce transaction block to other peers
100	if !duplicate {
101		messagebus.Bus.Broadcast.Send("transfer", packed)
102	}
103
104	return nil
105}
106
107// Get share balance
108// --------------------
109
110// BalanceArguments - arguments for RPC
111type BalanceArguments struct {
112	Owner   *account.Account `json:"owner"` // base58
113	ShareId merkle.Digest    `json:"shareId"`
114	Count   int              `json:"count"` // number of records
115}
116
117// BalanceReply - balances of shares belonging to an account
118type BalanceReply struct {
119	Balances []reservoir.BalanceInfo `json:"balances"`
120}
121
122// Balance - list balances for an account
123func (share *Share) Balance(arguments *BalanceArguments, reply *BalanceReply) error {
124
125	if err := ratelimit.Limit(share.Limiter); nil != err {
126		return err
127	}
128
129	log := share.Log
130
131	log.Infof("Share.Balance: %+v", arguments)
132
133	if nil == arguments || nil == arguments.Owner {
134		return fault.InvalidItem
135	}
136
137	count := arguments.Count
138	if count <= 0 {
139		return fault.InvalidCount
140	}
141	if count > owner.MaximumBitmarksCount {
142		count = owner.MaximumBitmarksCount
143	}
144
145	if !share.IsNormalMode(mode.Normal) {
146		return fault.NotAvailableDuringSynchronise
147	}
148
149	if arguments.Owner.IsTesting() != mode.IsTesting() {
150		return fault.WrongNetworkForPublicKey
151	}
152
153	result, err := share.Rsvr.ShareBalance(arguments.Owner, arguments.ShareId, arguments.Count)
154	if nil != err {
155		return err
156	}
157
158	reply.Balances = result
159
160	return nil
161}
162
163// Grant some shares
164// -----------------
165
166// GrantReply - result of granting some shares to another account
167type GrantReply struct {
168	Remaining uint64                                          `json:"remaining"`
169	TxId      merkle.Digest                                   `json:"txId"`
170	PayId     pay.PayId                                       `json:"payId"`
171	Payments  map[string]transactionrecord.PaymentAlternative `json:"payments"`
172}
173
174// Grant - grant a number of shares to another account
175func (share *Share) Grant(arguments *transactionrecord.ShareGrant, reply *GrantReply) error {
176
177	if err := ratelimit.Limit(share.Limiter); nil != err {
178		return err
179	}
180
181	log := share.Log
182
183	log.Infof("Share.Grant: %+v", arguments)
184
185	if nil == arguments || nil == arguments.Owner || nil == arguments.Recipient {
186		return fault.InvalidItem
187	}
188
189	if arguments.Quantity < 1 {
190		return fault.ShareQuantityTooSmall
191	}
192
193	if !share.IsNormalMode(mode.Normal) {
194		return fault.NotAvailableDuringSynchronise
195	}
196
197	if arguments.Owner.IsTesting() != mode.IsTesting() {
198		return fault.WrongNetworkForPublicKey
199	}
200
201	if arguments.Recipient.IsTesting() != mode.IsTesting() {
202		return fault.WrongNetworkForPublicKey
203	}
204
205	// save transfer/check for duplicate
206	stored, duplicate, err := share.Rsvr.StoreGrant(arguments)
207	if nil != err {
208		return err
209	}
210
211	// only first result needs to be considered
212	payId := stored.Id
213	txId := stored.TxId
214	packed := stored.Packed
215
216	log.Debugf("id: %v", txId)
217	reply.Remaining = stored.Remaining
218	reply.TxId = txId
219	reply.PayId = payId
220	reply.Payments = make(map[string]transactionrecord.PaymentAlternative)
221
222	for _, payment := range stored.Payments {
223		c := payment[0].Currency.String()
224		reply.Payments[c] = payment
225	}
226
227	// announce transaction block to other peers
228	if !duplicate {
229		messagebus.Bus.Broadcast.Send("transfer", packed)
230	}
231
232	return nil
233}
234
235// Swap some shares
236// -------------------
237
238// SwapReply - result of a share swap
239type SwapReply struct {
240	RemainingOne uint64                                          `json:"remainingOne"`
241	RemainingTwo uint64                                          `json:"remainingTwo"`
242	TxId         merkle.Digest                                   `json:"txId"`
243	PayId        pay.PayId                                       `json:"payId"`
244	Payments     map[string]transactionrecord.PaymentAlternative `json:"payments"`
245}
246
247// Swap - atomically swap shares between accounts
248func (share *Share) Swap(arguments *transactionrecord.ShareSwap, reply *SwapReply) error {
249
250	if err := ratelimit.Limit(share.Limiter); nil != err {
251		return err
252	}
253
254	log := share.Log
255
256	log.Infof("Share.Swap: %+v", arguments)
257
258	if nil == arguments || nil == arguments.OwnerOne || nil == arguments.OwnerTwo {
259		return fault.InvalidItem
260	}
261
262	if arguments.QuantityOne < 1 || arguments.QuantityTwo < 1 {
263		return fault.ShareQuantityTooSmall
264	}
265
266	if !share.IsNormalMode(mode.Normal) {
267		return fault.NotAvailableDuringSynchronise
268	}
269
270	if arguments.OwnerOne.IsTesting() != mode.IsTesting() {
271		return fault.WrongNetworkForPublicKey
272	}
273
274	if arguments.OwnerTwo.IsTesting() != mode.IsTesting() {
275		return fault.WrongNetworkForPublicKey
276	}
277
278	// save transfer/check for duplicate
279	stored, duplicate, err := share.Rsvr.StoreSwap(arguments)
280	if nil != err {
281		return err
282	}
283
284	// only first result needs to be considered
285	payId := stored.Id
286	txId := stored.TxId
287	packed := stored.Packed
288
289	log.Debugf("id: %v", txId)
290	reply.RemainingOne = stored.RemainingOne
291	reply.RemainingTwo = stored.RemainingTwo
292	reply.TxId = txId
293	reply.PayId = payId
294	reply.Payments = make(map[string]transactionrecord.PaymentAlternative)
295
296	for _, payment := range stored.Payments {
297		c := payment[0].Currency.String()
298		reply.Payments[c] = payment
299	}
300
301	// announce transaction block to other peers
302	if !duplicate {
303		messagebus.Bus.Broadcast.Send("transfer", packed)
304	}
305
306	return nil
307}
308