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 owner
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/mode"
15	"github.com/bitmark-inc/bitmarkd/ownership"
16	"github.com/bitmark-inc/bitmarkd/reservoir"
17	"github.com/bitmark-inc/bitmarkd/rpc/ratelimit"
18	"github.com/bitmark-inc/bitmarkd/storage"
19	"github.com/bitmark-inc/bitmarkd/transactionrecord"
20	"github.com/bitmark-inc/logger"
21)
22
23// Owner
24// -----
25
26// Owner - type for the RPC
27type Owner struct {
28	Log              *logger.L
29	Limiter          *rate.Limiter
30	PoolTransactions storage.Handle
31	PoolAssets       storage.Handle
32	Ownership        ownership.Ownership
33}
34
35// Owner bitmarks
36// --------------
37
38const (
39	MaximumBitmarksCount = 100
40	rateLimitOwner       = 200
41	rateBurstOwner       = 100
42)
43
44// BitmarksArguments - arguments for RPC
45type BitmarksArguments struct {
46	Owner *account.Account `json:"owner"`        // base58
47	Start uint64           `json:"Start,string"` // first record number
48	Count int              `json:"count"`        // number of records
49}
50
51// BitmarksReply - result of owner RPC
52type BitmarksReply struct {
53	Next uint64                    `json:"next,string"` // Start value for the next call
54	Data []ownership.Record        `json:"data"`        // list of bitmarks either issue or transfer
55	Tx   map[string]BitmarksRecord `json:"tx"`          // table of tx records
56}
57
58// BitmarksRecord - can be any of the transaction records
59type BitmarksRecord struct {
60	Record  string      `json:"record"`
61	TxId    interface{} `json:"txId,omitempty"`
62	InBlock uint64      `json:"inBlock"`
63	AssetId interface{} `json:"assetId,omitempty"`
64	Data    interface{} `json:"data"`
65}
66
67// BlockAsset - special record for owned blocks
68type BlockAsset struct {
69	Number uint64 `json:"number"`
70}
71
72func New(log *logger.L, pools reservoir.Handles, os ownership.Ownership) *Owner {
73	return &Owner{
74		Log:              log,
75		Limiter:          rate.NewLimiter(rateLimitOwner, rateBurstOwner),
76		PoolTransactions: pools.Transactions,
77		PoolAssets:       pools.Assets,
78		Ownership:        os,
79	}
80}
81
82// Bitmarks - list bitmarks belonging to an account
83func (owner *Owner) Bitmarks(arguments *BitmarksArguments, reply *BitmarksReply) error {
84
85	if err := ratelimit.LimitN(owner.Limiter, arguments.Count, MaximumBitmarksCount); nil != err {
86		return err
87	}
88
89	log := owner.Log
90	log.Infof("Owner.Bitmarks: %+v", arguments)
91
92	ownershipData, err := owner.Ownership.ListBitmarksFor(arguments.Owner, arguments.Start, arguments.Count)
93	if nil != err {
94		return err
95	}
96
97	log.Debugf("ownership: %+v", ownershipData)
98
99	// extract unique TxIds
100	//   issues TxId == IssueTxId
101	//   assets could be duplicates
102	txIds := make(map[merkle.Digest]struct{})
103	assetIds := make(map[transactionrecord.AssetIdentifier]struct{})
104	current := uint64(0)
105	for _, r := range ownershipData {
106		txIds[r.TxId] = struct{}{}
107		txIds[r.IssueTxId] = struct{}{}
108		switch r.Item {
109		case ownership.OwnedAsset:
110			ai := r.AssetId
111			if nil == ai {
112				log.Criticalf("asset id is nil: %+v", r)
113				logger.Panicf("asset id is nil: %+v", r)
114			}
115			assetIds[*r.AssetId] = struct{}{}
116		case ownership.OwnedBlock:
117			if nil == r.BlockNumber {
118				log.Criticalf("block number is nil: %+v", r)
119				logger.Panicf("blockNumber is nil: %+v", r)
120			}
121		case ownership.OwnedShare:
122			ai := r.AssetId
123			if nil == ai {
124				log.Criticalf("asset id is nil: %+v", r)
125				logger.Panicf("asset id is nil: %+v", r)
126			}
127			assetIds[*r.AssetId] = struct{}{}
128		default:
129			log.Criticalf("unsupported item type: %d", r.Item)
130			logger.Panicf("unsupported item type: %d", r.Item)
131		}
132		current = r.N
133	}
134
135	records := make(map[string]BitmarksRecord)
136
137	for txId := range txIds {
138
139		log.Debugf("txId: %v", txId)
140
141		inBlock, transaction := owner.PoolTransactions.GetNB(txId[:])
142		if nil == transaction {
143			return fault.LinkToInvalidOrUnconfirmedTransaction
144		}
145
146		tx, _, err := transactionrecord.Packed(transaction).Unpack(mode.IsTesting())
147		if nil != err {
148			return err
149		}
150
151		record, ok := transactionrecord.RecordName(tx)
152		if !ok {
153			log.Errorf("problem tx: %+v", tx)
154			return fault.LinkToInvalidOrUnconfirmedTransaction
155		}
156		textTxId, err := txId.MarshalText()
157		if nil != err {
158			return err
159		}
160
161		records[string(textTxId)] = BitmarksRecord{
162			Record:  record,
163			TxId:    txId,
164			InBlock: inBlock,
165			Data:    tx,
166		}
167	}
168
169assetsLoop:
170	for assetId := range assetIds {
171		log.Debugf("asset id: %v", assetId)
172
173		var nnn transactionrecord.AssetIdentifier
174		if nnn == assetId {
175			records["00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"] = BitmarksRecord{
176				Record: "Block",
177				//AssetId: assetId,
178				Data: BlockAsset{
179					Number: 0,
180				},
181			}
182			continue assetsLoop
183		}
184
185		inBlock, transaction := owner.PoolAssets.GetNB(assetId[:])
186		if nil == transaction {
187			return fault.AssetNotFound
188		}
189
190		tx, _, err := transactionrecord.Packed(transaction).Unpack(mode.IsTesting())
191		if nil != err {
192			return err
193		}
194
195		record, ok := transactionrecord.RecordName(tx)
196		if !ok {
197			return fault.AssetNotFound
198		}
199		textAssetId, err := assetId.MarshalText()
200		if nil != err {
201			return err
202		}
203
204		records[string(textAssetId)] = BitmarksRecord{
205			Record:  record,
206			InBlock: inBlock,
207			AssetId: assetId,
208			Data:    tx,
209		}
210	}
211
212	reply.Data = ownershipData
213	reply.Tx = records
214
215	// if no record were found the just return Next as zero
216	// otherwise the next possible number
217	if 0 == current {
218		reply.Next = 0
219	} else {
220		reply.Next = current + 1
221	}
222	return nil
223}
224