Skip to content
This repository was archived by the owner on Aug 18, 2020. It is now read-only.

Commit 9644c97

Browse files
authored
Merge pull request #3324 from input-output-hk/contrib/add-utxo-explorer-endpoint
added unspent transaction outputs endpoint to explorer
2 parents 658af4f + aeb238d commit 9644c97

File tree

8 files changed

+131
-19
lines changed

8 files changed

+131
-19
lines changed

explorer/src/Pos/Explorer/Aeson/ClientTypes.hs

+7-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@ import Data.Fixed (showFixed)
1717

1818
import Pos.Explorer.Web.ClientTypes (CAda (..), CAddress,
1919
CAddressSummary, CAddressType, CBlockEntry, CBlockSummary,
20-
CCoin, CGenesisAddressInfo, CGenesisSummary, CHash,
21-
CNetworkAddress, CTxBrief, CTxEntry, CTxId, CTxSummary)
20+
CByteString (..), CCoin, CGenesisAddressInfo,
21+
CGenesisSummary, CHash, CNetworkAddress, CTxBrief,
22+
CTxEntry, CTxId, CTxSummary, CUtxo)
2223
import Pos.Explorer.Web.Error (ExplorerError)
2324

2425
deriveJSON defaultOptions ''CHash
@@ -37,6 +38,10 @@ deriveToJSON defaultOptions ''CNetworkAddress
3738
deriveToJSON defaultOptions ''CTxSummary
3839
deriveToJSON defaultOptions ''CGenesisSummary
3940
deriveToJSON defaultOptions ''CGenesisAddressInfo
41+
deriveToJSON defaultOptions ''CUtxo
42+
43+
instance ToJSON CByteString where
44+
toJSON (CByteString bs) = (toJSON.toString) bs
4045

4146
instance ToJSON CAda where
4247
-- https://github.com/bos/aeson/issues/227#issuecomment-245400284

explorer/src/Pos/Explorer/Web/Api.hs

+11-2
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,16 @@ import Universum
1515

1616
import Control.Exception.Safe (try)
1717
import Data.Proxy (Proxy (Proxy))
18-
import Servant.API ((:>), Capture, Get, JSON, QueryParam, Summary)
18+
import Servant.API ((:>), Capture, Get, JSON, Post, QueryParam,
19+
ReqBody, Summary)
1920
import Servant.Generic ((:-), AsApi, ToServant)
2021
import Servant.Server (ServantErr (..))
2122

2223
import Pos.Core (EpochIndex)
2324
import Pos.Explorer.Web.ClientTypes (Byte, CAda, CAddress,
2425
CAddressSummary, CAddressesFilter, CBlockEntry,
2526
CBlockSummary, CGenesisAddressInfo, CGenesisSummary,
26-
CHash, CTxBrief, CTxEntry, CTxId, CTxSummary)
27+
CHash, CTxBrief, CTxEntry, CTxId, CTxSummary, CUtxo)
2728
import Pos.Explorer.Web.Error (ExplorerError)
2829
import Pos.Util.Servant (DQueryParam, ModifiesApiRes (..), VerbMod)
2930

@@ -114,6 +115,14 @@ data ExplorerApiRecord route = ExplorerApiRecord
114115
:> Capture "address" CAddress
115116
:> ExRes Get CAddressSummary
116117

118+
, _addressUtxoBulk :: route
119+
:- Summary "Get summary information about multiple addresses."
120+
:> "bulk"
121+
:> "addresses"
122+
:> "utxo"
123+
:> ReqBody '[JSON] [CAddress]
124+
:> ExRes Post [CUtxo]
125+
117126
, _epochPages :: route
118127
:- Summary "Get epoch pages, all the paged slots in the epoch."
119128
:> "epochs"

explorer/src/Pos/Explorer/Web/ClientTypes.hs

+22
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ module Pos.Explorer.Web.ClientTypes
1717
, CAddressType (..)
1818
, CAddressSummary (..)
1919
, CTxBrief (..)
20+
, CUtxo (..)
2021
, CNetworkAddress (..)
2122
, CTxSummary (..)
2223
, CGenesisSummary (..)
@@ -29,6 +30,7 @@ module Pos.Explorer.Web.ClientTypes
2930
, LocalSlotIndex (..)
3031
, StakeholderId
3132
, Byte
33+
, CByteString (..)
3234
, mkCCoin
3335
, mkCCoinMB
3436
, toCHash
@@ -87,6 +89,8 @@ import Pos.Explorer.Core (TxExtra (..))
8789
import Pos.Explorer.ExplorerMode (ExplorerMode)
8890
import Pos.Explorer.ExtraContext (HasExplorerCSLInterface (..))
8991
import Pos.Explorer.TestUtil (secretKeyToAddress)
92+
93+
9094
-------------------------------------------------------------------------------------
9195
-- Hash types
9296
-------------------------------------------------------------------------------------
@@ -308,6 +312,18 @@ data CTxBrief = CTxBrief
308312
, ctbOutputSum :: !CCoin
309313
} deriving (Show, Generic)
310314

315+
data CUtxo = CUtxo
316+
{ cuId :: !CTxId
317+
, cuOutIndex :: !Int
318+
, cuAddress :: !CAddress
319+
, cuCoins :: !CCoin
320+
}
321+
| CUtxoUnknown
322+
{ cuTag :: !Int
323+
, cuBs :: !CByteString
324+
}
325+
deriving (Show, Generic)
326+
311327
newtype CNetworkAddress = CNetworkAddress Text
312328
deriving (Show, Generic)
313329

@@ -431,6 +447,12 @@ sumCoinOfInputsOutputs addressListMB
431447
mkCCoin $ mkCoin $ fromIntegral $ sum addressCoinList
432448
| otherwise = mkCCoinMB Nothing
433449

450+
newtype CByteString = CByteString ByteString
451+
deriving (Generic)
452+
453+
instance Show CByteString where
454+
show (CByteString bs) = (show . toString) bs
455+
434456
--------------------------------------------------------------------------------
435457
-- Arbitrary instances
436458
--------------------------------------------------------------------------------

explorer/src/Pos/Explorer/Web/Server.hs

+53-12
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ import Network.Wai.Middleware.RequestLogger (logStdoutDev)
4444

4545
import qualified Serokell.Util.Base64 as B64
4646
import Servant.Generic (AsServerT, toServant)
47-
import Servant.Server (Server, ServerT, serve)
47+
import Servant.Server (Server, ServerT, err405, errReasonPhrase,
48+
serve)
4849
import System.Wlog (logDebug)
4950

5051
import Pos.Crypto (WithHash (..), hash, redeemPkBuild, withHash)
@@ -65,10 +66,10 @@ import Pos.Core (AddrType (..), Address (..), Coin, EpochIndex,
6566
import Pos.Core.Block (Block, HeaderHash, MainBlock, gbHeader,
6667
gbhConsensus, mainBlockSlot, mainBlockTxPayload, mcdSlot)
6768
import Pos.Core.Chrono (NewestFirst (..))
68-
import Pos.Core.Txp (Tx (..), TxAux, TxId, TxOutAux (..), taTx,
69-
txOutValue, txpTxs, _txOutputs)
70-
import Pos.DB.Txp (MonadTxpMem, getLocalTxs, getMemPool,
71-
withTxpLocalData)
69+
import Pos.Core.Txp (Tx (..), TxAux, TxId, TxIn (..), TxOutAux (..),
70+
taTx, txOutAddress, txOutValue, txpTxs, _txOutputs)
71+
import Pos.DB.Txp (MonadTxpMem, getFilteredUtxo, getLocalTxs,
72+
getMemPool, withTxpLocalData)
7273
import Pos.Infra.Slotting (MonadSlots (..), getSlotStart)
7374
import Pos.Util (divRoundUp, maybeThrow)
7475
import Pos.Web (serveImpl)
@@ -86,15 +87,18 @@ import Pos.Explorer.Web.Api (ExplorerApi, ExplorerApiRecord (..),
8687
import Pos.Explorer.Web.ClientTypes (Byte, CAda (..), CAddress (..),
8788
CAddressSummary (..), CAddressType (..),
8889
CAddressesFilter (..), CBlockEntry (..),
89-
CBlockSummary (..), CGenesisAddressInfo (..),
90-
CGenesisSummary (..), CHash, CTxBrief (..), CTxEntry (..),
91-
CTxId (..), CTxSummary (..), TxInternal (..),
92-
convertTxOutputs, convertTxOutputsMB, fromCAddress,
93-
fromCHash, fromCTxId, getEpochIndex, getSlotIndex,
94-
mkCCoin, mkCCoinMB, tiToTxEntry, toBlockEntry,
95-
toBlockSummary, toCAddress, toCHash, toCTxId, toTxBrief)
90+
CBlockSummary (..), CByteString (..),
91+
CGenesisAddressInfo (..), CGenesisSummary (..), CHash,
92+
CTxBrief (..), CTxEntry (..), CTxId (..), CTxSummary (..),
93+
CUtxo (..), TxInternal (..), convertTxOutputs,
94+
convertTxOutputsMB, fromCAddress, fromCHash, fromCTxId,
95+
getEpochIndex, getSlotIndex, mkCCoin, mkCCoinMB,
96+
tiToTxEntry, toBlockEntry, toBlockSummary, toCAddress,
97+
toCHash, toCTxId, toTxBrief)
9698
import Pos.Explorer.Web.Error (ExplorerError (..))
9799

100+
import qualified Data.Map as M
101+
import Pos.Configuration (explorerExtendedApi)
98102

99103

100104
----------------------------------------------------------------
@@ -132,6 +136,7 @@ explorerHandlers _diffusion =
132136
, _txsLast = getLastTxs
133137
, _txsSummary = getTxSummary
134138
, _addressSummary = getAddressSummary
139+
, _addressUtxoBulk = getAddressUtxoBulk
135140
, _epochPages = getEpochPage
136141
, _epochSlots = getEpochSlot
137142
, _genesisSummary = getGenesisSummary
@@ -371,6 +376,42 @@ getAddressSummary cAddr = do
371376
ATRedeem -> CRedeemAddress
372377
ATUnknown {} -> CUnknownAddress
373378

379+
380+
getAddressUtxoBulk
381+
:: (ExplorerMode ctx m)
382+
=> [CAddress]
383+
-> m [CUtxo]
384+
getAddressUtxoBulk cAddrs = do
385+
unless explorerExtendedApi $
386+
throwM err405
387+
{ errReasonPhrase = "Explorer extended API is disabled by configuration!"
388+
}
389+
390+
let nAddrs = length cAddrs
391+
392+
when (nAddrs > 10) $
393+
throwM err405
394+
{ errReasonPhrase = "Maximum number of addresses you can send to fetch Utxo in bulk is 10!"
395+
}
396+
397+
addrs <- mapM cAddrToAddr cAddrs
398+
utxo <- getFilteredUtxo addrs
399+
400+
pure . map futxoToCUtxo . M.toList $ utxo
401+
where
402+
futxoToCUtxo :: (TxIn, TxOutAux) -> CUtxo
403+
futxoToCUtxo ((TxInUtxo txInHash txInIndex), txOutAux) = CUtxo {
404+
cuId = toCTxId txInHash,
405+
cuOutIndex = fromIntegral txInIndex,
406+
cuAddress = toCAddress . txOutAddress . toaOut $ txOutAux,
407+
cuCoins = mkCCoin . txOutValue . toaOut $ txOutAux
408+
}
409+
futxoToCUtxo ((TxInUnknown tag bs), _) = CUtxoUnknown {
410+
cuTag = fromIntegral tag,
411+
cuBs = CByteString bs
412+
}
413+
414+
374415
-- | Get transaction summary from transaction id. Looks at both the database
375416
-- and the memory (mempool) for the transaction. What we have at the mempool
376417
-- are transactions that have to be written in the blockchain.

explorer/src/Pos/Explorer/Web/TestServer.hs

+14-1
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ import Pos.Explorer.Web.ClientTypes (Byte, CAda (..), CAddress (..),
2424
CAddressesFilter (..), CBlockEntry (..),
2525
CBlockSummary (..), CGenesisAddressInfo (..),
2626
CGenesisSummary (..), CHash (..), CTxBrief (..),
27-
CTxEntry (..), CTxId (..), CTxSummary (..), mkCCoin)
27+
CTxEntry (..), CTxId (..), CTxSummary (..), CUtxo (..),
28+
mkCCoin)
2829
import Pos.Explorer.Web.Error (ExplorerError (..))
2930
import Pos.Web ()
3031

32+
3133
----------------------------------------------------------------
3234
-- Top level functionality
3335
----------------------------------------------------------------
@@ -54,6 +56,7 @@ explorerHandlers =
5456
, _txsLast = testTxsLast
5557
, _txsSummary = testTxsSummary
5658
, _addressSummary = testAddressSummary
59+
, _addressUtxoBulk = testAddressUtxoBulk
5760
, _epochPages = testEpochPageSearch
5861
, _epochSlots = testEpochSlotSearch
5962
, _genesisSummary = testGenesisSummary
@@ -185,6 +188,16 @@ testAddressSummary
185188
-> Handler CAddressSummary
186189
testAddressSummary _ = pure sampleAddressSummary
187190

191+
testAddressUtxoBulk
192+
:: [CAddress]
193+
-> Handler [CUtxo]
194+
testAddressUtxoBulk _ = pure [CUtxo
195+
{ cuId = CTxId $ CHash "8aac4a6b18fafa2783071c66519332157ce96c67e88fc0cc3cb04ba0342d12a1"
196+
, cuOutIndex = 0
197+
, cuAddress = CAddress "19F6U1Go5B4KakVoCZfzCtqNAWhUBprxVzL3JsGu74TEwQnXPvAKPUbvG8o4Qe5RaY8Z7WKLfxmNFwBqPV1NQ2hRpKkdEN"
198+
, cuCoins = mkCCoin $ mkCoin 3
199+
}]
200+
188201
testEpochSlotSearch
189202
:: EpochIndex
190203
-> Word16

explorer/src/documentation/Main.hs

+7-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ import Control.Lens (mapped, (?~))
2727
import Data.Aeson (encode)
2828
import qualified Data.ByteString.Lazy.Char8 as BSL8
2929
import Data.Fixed (Fixed (..), Micro)
30-
import Data.Swagger (Swagger, ToParamSchema (..), ToSchema (..),
31-
declareNamedSchema, defaultSchemaOptions, description,
30+
import Data.Swagger (NamedSchema (..), Swagger, ToParamSchema (..),
31+
ToSchema (..), binarySchema, declareNamedSchema,
32+
defaultSchemaOptions, description,
3233
genericDeclareNamedSchema, host, info, name, title,
3334
version)
3435
import Data.Typeable (Typeable, typeRep)
@@ -85,6 +86,7 @@ instance ToParamSchema C.EpochIndex
8586
instance ToSchema C.CTxSummary
8687
instance ToSchema C.CTxEntry
8788
instance ToSchema C.CTxBrief
89+
instance ToSchema C.CUtxo
8890
instance ToSchema C.CBlockSummary
8991
instance ToSchema C.CBlockEntry
9092
instance ToSchema C.CAddressType
@@ -100,6 +102,9 @@ instance ToParamSchema C.CAddressesFilter
100102

101103
deriving instance Generic Micro
102104

105+
instance ToSchema C.CByteString where
106+
declareNamedSchema _ = return $ NamedSchema (Just "CByteString") binarySchema
107+
103108
-- | Instance for Either-based types (types we return as 'Right') in responses.
104109
-- Due 'typeOf' these types must be 'Typeable'.
105110
-- We need this instance for correct Swagger-specification.

lib/configuration.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ dev: &dev
112112
pendingTxResubmissionPeriod: 7 # seconds
113113
walletProductionApi: false
114114
walletTxCreationDisabled: false
115+
explorerExtendedApi: false
115116

116117
tls: &dev_tls
117118
ca:
@@ -310,6 +311,7 @@ mainnet_base: &mainnet_base
310311
pendingTxResubmissionPeriod: 7 # seconds
311312
walletProductionApi: true
312313
walletTxCreationDisabled: false
314+
explorerExtendedApi: false
313315

314316
tls: &mainnet_base_tls
315317
ca:

lib/src/Pos/Configuration.hs

+15
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ module Pos.Configuration
1717
, pendingTxResubmitionPeriod
1818
, walletProductionApi
1919
, walletTxCreationDisabled
20+
21+
-- * Explorer constants
22+
, explorerExtendedApi
2023
) where
2124

2225
import Universum
@@ -52,6 +55,9 @@ data NodeConfiguration = NodeConfiguration
5255
, ccWalletTxCreationDisabled :: !Bool
5356
-- ^ Disallow transaction creation or re-submission of
5457
-- pending transactions by the wallet
58+
, ccExplorerExtendedApi :: !Bool
59+
-- ^ Enable explorer extended API for fetching more
60+
-- info about addresses (like utxos) and bulk endpoints
5561
} deriving (Show, Generic)
5662

5763
instance ToJSON NodeConfiguration where
@@ -91,3 +97,12 @@ walletProductionApi = ccWalletProductionApi $ nodeConfiguration
9197
-- existing pending transactions.
9298
walletTxCreationDisabled :: HasNodeConfiguration => Bool
9399
walletTxCreationDisabled = ccWalletTxCreationDisabled $ nodeConfiguration
100+
101+
----------------------------------------------------------------------------
102+
-- Explorer parameters
103+
----------------------------------------------------------------------------
104+
105+
-- | If 'True', explorer extended API, like fetching utxos for address is enabled.
106+
-- WARNING Those endpoints are potentially expensive!
107+
explorerExtendedApi :: HasNodeConfiguration => Bool
108+
explorerExtendedApi = ccExplorerExtendedApi $ nodeConfiguration

0 commit comments

Comments
 (0)