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