Skip to content

Commit

Permalink
PLT-1311 Add JSON-RPC querying of the stake pool delegation (SPD) by …
Browse files Browse the repository at this point in the history
…epoch in `marconi-sidechain`.

* Add indexing and JSON-RPC querying of the SPD by epoch in `marconi-sidechain`.

* Added golden test for JSON response of stake pool delegation by
  epoch

* Fixed resuming of Utxo indexer to include *all* possible chain points,
  not only the latest ones.

* Updated README of `marconi-sidechain` to include an example of request
  and response for the JSON-RPC server on that specific endpoint.
  • Loading branch information
koslambrou committed Mar 22, 2023
1 parent 5f09d05 commit 9113a84
Show file tree
Hide file tree
Showing 20 changed files with 397 additions and 160 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ import Cardano.Slotting.Slot (EpochNo)
import Codec.CBOR.Read qualified as CBOR
import Codec.CBOR.Write qualified as CBOR
import Control.Monad (forM_, when)
import Data.Aeson (FromJSON (parseJSON), ToJSON (toJSON), Value (Object), object, (.:), (.=))
import Data.ByteString.Base16 qualified as Base16
import Data.ByteString.Lazy qualified as BS
import Data.Coerce (coerce)
Expand Down Expand Up @@ -233,6 +234,33 @@ data EpochSPDRow = EpochSPDRow
, epochSPDRowBlockNo :: !C.BlockNo
} deriving (Eq, Ord, Show, Generic, SQL.FromRow, SQL.ToRow)

instance FromJSON EpochSPDRow where
parseJSON (Object v) =
EpochSPDRow
<$> (C.EpochNo <$> v .: "epochNo")
<*> v .: "poolId"
<*> v .: "lovelace"
<*> (C.SlotNo <$> v .: "slotNo")
<*> v .: "blockHeaderHash"
<*> (C.BlockNo <$> v .: "blockNo")
parseJSON _ = mempty

instance ToJSON EpochSPDRow where
toJSON (EpochSPDRow (C.EpochNo epochNo)
poolId
lovelace
(C.SlotNo slotNo)
blockHeaderHash
(C.BlockNo blockNo)) =
object
[ "epochNo" .= epochNo
, "poolId" .= poolId
, "lovelace" .= lovelace
, "slotNo" .= slotNo
, "blockHeaderHash" .= blockHeaderHash
, "blockNo" .= blockNo
]

instance Buffered EpochSPDHandle where
-- We should only store on disk SPD from the last slot of each epoch.
persistToStorage
Expand Down
10 changes: 7 additions & 3 deletions marconi-chain-index/src/Marconi/ChainIndex/Indexers/Utxo.hs
Original file line number Diff line number Diff line change
Expand Up @@ -474,12 +474,16 @@ instance Rewindable UtxoHandle where

-- For resuming we need to provide a list of points where we can resume from.
instance Resumable UtxoHandle where
resumeFromStorage h = do
es <- Storable.getStoredEvents h
resumeFromStorage (UtxoHandle c _) = do
chainPoints <- fmap (uncurry C.ChainPoint) <$>
SQL.query c
[r|SELECT slotNo, blockHash
FROM unspent_transactions
ORDER BY slotNo DESC|] ()
-- The ordering here matters. The node will try to find the first point in the
-- ledger, then move to the next and so on, so we will send the latest point
-- first.
pure $ map ueChainPoint es ++ [C.ChainPointAtGenesis]
pure $ chainPoints ++ [C.ChainPointAtGenesis]

-- | Convert from 'AddressInEra' of the 'CurrentEra' to 'AddressAny'.
toAddr :: C.AddressInEra era -> C.AddressAny
Expand Down
63 changes: 45 additions & 18 deletions marconi-sidechain/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,18 +217,21 @@ OR
}
```

#### getUtxoFromAddress
#### getUtxoFromAddress (NOT IMPLEMENTED YET)

Retrieves user provided addresses.
Retrieves UTXOs of a given address until a given point in time (measured in slots).

**Parameters**:

**Parameters**: Address encoded in the Bech32 representation
* `address`: address encoded in the Bech32 format
* `slotNo`: slot number.

**Returns**: List of UTXOs linked to the provided address.
**Returns**: List of resolved UTXOs.

**Example**:

```sh
$ curl -d '{"jsonrpc": "2.0" , "method": "getUtxoFromAddress" , "params": "addr_test1qz0ru2w9suwv8mcskg8r9ws3zvguekkkx6kpcnn058pe2ql2ym0y64huzhpu0wl8ewzdxya0hj0z5ejyt3g98lpu8xxs8faq0m", "id": 1}' -H 'Content-Type: application/json' -X POST http://localhost:3000/json-rpc | jq
$ curl -d '{"jsonrpc": "2.0" , "method": "getUtxoFromAddress" , "params": { "address": "addr_test1qz0ru2w9suwv8mcskg8r9ws3zvguekkkx6kpcnn058pe2ql2ym0y64huzhpu0wl8ewzdxya0hj0z5ejyt3g98lpu8xxs8faq0m", "slotNo": 1 }, "id": 1}' -H 'Content-Type: application/json' -X POST http://localhost:3000/json-rpc | jq
{
"id": 1,
"jsonrpc": "2.0",
Expand Down Expand Up @@ -278,16 +281,19 @@ $ curl -d '{"jsonrpc": "2.0" , "method": "getUtxoFromAddress" , "params": "addr_

#### getTxWithMintingPolicy (NOT IMPLEMENTED YET)

Retrieves transactions that include a minting policy for minting/burning tokens.
Retrieves transactions that include a minting policy for minting/burning tokens until a given point in time (measured in slots).

**Parameters**: Hash of the minting policy
**Parameters**:

* `mps`: Hash of the minting policy
* `slotNo`: slot number

**Returns**: List of transaction IDs

**Example**:

```sh
$ curl -d '{"jsonrpc": "2.0" , "method": "getTxWithMintingPolicy" , "params": "...", "id": 1}' -H 'Content-Type: application/json' -X POST http://localhost:3000/json-rpc | jq
$ curl -d '{"jsonrpc": "2.0" , "method": "getTxWithMintingPolicy" , "params": { "mps": "284d60f7e56f5fd54faed4c50fd5cab0307da1c4034d6a92c5dbb940", "slotNo": 1 }, "id": 1}' -H 'Content-Type: application/json' -X POST http://localhost:3000/json-rpc | jq
{
"id": 1,
"jsonrpc": "2.0",
Expand Down Expand Up @@ -316,27 +322,48 @@ $ curl -d '{"jsonrpc": "2.0" , "method": "getTxWithMintingPolicy" , "params": ".
}
```

#### getStakePoolDelegationByEpoch (NOT IMPLEMENTED YET)
#### getStakePoolDelegationByEpoch

Retrieves the stake pool delegation per epoch.

**Parameters**: Epoch number

**Returns**: List of stake pool IDs with the amount of staked lovelace
**Returns**: List of stake pool IDs in Bech32 format with the total staked lovelace for that epoch.

**Example**:

```sh
$ curl -d '{"jsonrpc": "2.0" , "method": "getStakePoolDelegationByEpoch" , "params": "...", "id": 1}' -H 'Content-Type: application/json' -X POST http://localhost:3000/json-rpc | jq
$ curl -d '{"jsonrpc": "2.0" , "method": "getStakePoolDelegationByEpoch" , "params": 6, "id": 1}' -H 'Content-Type: application/json' -X POST http://localhost:3000/json-rpc | jq
{
"id": 1,
"jsonrpc": "2.0",
"result": [
{
"poolId": "...",
"lovelace": 10000000
}
]
"result":
[
{
"blockHeaderHash": "578f3cb70f4153e1622db792fea9005c80ff80f83df028210c7a914fb780a6f6",
"blockNo": 64903,
"epochNo": 6,
"lovelace": 100000000000000,
"poolId": "pool1z22x50lqsrwent6en0llzzs9e577rx7n3mv9kfw7udwa2rf42fa",
"slotNo": 1382422
},
{
"blockHeaderHash": "578f3cb70f4153e1622db792fea9005c80ff80f83df028210c7a914fb780a6f6",
"blockNo": 64903,
"epochNo": 6,
"lovelace": 100000000000000,
"poolId": "pool1547tew8vmuj0g6vj3k5jfddudextcw6hsk2hwgg6pkhk7lwphe6",
"slotNo": 1382422
},
{
"blockHeaderHash": "578f3cb70f4153e1622db792fea9005c80ff80f83df028210c7a914fb780a6f6",
"blockNo": 64903,
"epochNo": 6,
"lovelace": 100000000000000,
"poolId": "pool174mw7e20768e8vj4fn8y6p536n8rkzswsapwtwn354dckpjqzr8",
"slotNo": 1382422
}
]
}
```

Expand All @@ -351,7 +378,7 @@ Retrieves transactions that include a minting policy for minting/burning tokens.
**Example**:

```sh
$ curl -d '{"jsonrpc": "2.0" , "method": "getNonceByEpoch " , "params": 398, "id": 1}' -H 'Content-Type: application/json' -X POST http://localhost:3000/json-rpc | jq
$ curl -d '{"jsonrpc": "2.0" , "method": "getNonceByEpoch" , "params": 398, "id": 1}' -H 'Content-Type: application/json' -X POST http://localhost:3000/json-rpc | jq
{
"id": 1,
"jsonrpc": "2.0",
Expand Down
5 changes: 2 additions & 3 deletions marconi-sidechain/app/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Main where
import Control.Concurrent.Async (race_)
import Marconi.Sidechain.Api.HttpServer qualified as Http
import Marconi.Sidechain.Api.Types (CliArgs (CliArgs, httpPort, targetAddresses))
import Marconi.Sidechain.Bootstrap (bootstrapIndexers, initializeIndexerEnv)
import Marconi.Sidechain.Bootstrap (bootstrapIndexers, initializeSidechainEnv)
import Marconi.Sidechain.CLI (parseCli)

-- | Concurrently start:
Expand All @@ -16,8 +16,7 @@ import Marconi.Sidechain.CLI (parseCli)
main :: IO ()
main = do
cli@CliArgs { httpPort, targetAddresses } <- parseCli

rpcEnv <- initializeIndexerEnv httpPort targetAddresses
rpcEnv <- initializeSidechainEnv httpPort targetAddresses

race_
(Http.bootstrap rpcEnv) -- Start HTTP server
Expand Down
24 changes: 12 additions & 12 deletions marconi-sidechain/examples/json-rpc-server/src/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -15,24 +15,24 @@ import Control.Concurrent (threadDelay)
import Control.Concurrent.Async (race_)
import Control.Concurrent.STM (atomically)
import Control.Lens.Operators ((^.))
import Options.Applicative (Parser, execParser, help, helper, info, long, metavar, optional, short, strOption, (<**>))
import System.FilePath ((</>))

import Marconi.ChainIndex.CLI (multiString)
import Marconi.ChainIndex.Indexers.Utxo qualified as Utxo
import Marconi.ChainIndex.Types (TargetAddresses)
import Marconi.Sidechain.Api.HttpServer qualified as Http
import Marconi.Sidechain.Api.HttpServer (bootstrap)
import Marconi.Sidechain.Api.Query.Indexers.Utxo qualified as UIQ
import Marconi.Sidechain.Api.Types (IndexerEnv, queryEnv, uiIndexer)
import Marconi.Sidechain.Bootstrap (initializeIndexerEnv)
import Marconi.Sidechain.Api.Types (SidechainEnv, sidechainAddressUtxoIndexer, sidechainEnvIndexers)
import Marconi.Sidechain.Bootstrap (initializeSidechainEnv)
import Options.Applicative (Parser, execParser, help, helper, info, long, metavar, optional, short, strOption, (<**>))
import System.FilePath ((</>))

data CliOptions = CliOptions
{ _utxoDirPath :: FilePath -- ^ Filepath to utxo sqlite database
, _addresses :: Maybe TargetAddresses
{ _utxoDirPath :: !FilePath -- ^ Filepath to utxo sqlite database
, _addresses :: !(Maybe TargetAddresses)
}

utxoDbFileName :: String
utxoDbFileName = "utxodb"

cliParser :: Parser CliOptions
cliParser = CliOptions
<$> strOption (long "utxo-db"
Expand All @@ -54,16 +54,16 @@ main = do
<>"\nport =" <> show (3000 :: Int)
<> "\nmarconi-db-dir =" <> dbpath
<> "\nnumber of addresses to index = " <> show (length <$> addresses)
env <- initializeIndexerEnv Nothing addresses
race_ (Http.bootstrap env) (mocUtxoIndexer dbpath (env ^. queryEnv) )
env <- initializeSidechainEnv Nothing addresses
race_ (bootstrap env) (mocUtxoIndexer dbpath env)

-- | moc marconi utxo indexer.
-- This will allow us to use the UtxoIndexer query interface without having cardano-node or marconi online
-- Effectively we are going to query SQLite only
mocUtxoIndexer :: FilePath -> IndexerEnv -> IO ()
mocUtxoIndexer :: FilePath -> SidechainEnv -> IO ()
mocUtxoIndexer dbpath env =
Utxo.open dbpath (Utxo.Depth 4) >>= callback >> innerLoop
where
callback :: Utxo.UtxoIndexer -> IO ()
callback = atomically . UIQ.writeTMVar' (env ^. uiIndexer)
callback = atomically . UIQ.updateEnvState (env ^. sidechainEnvIndexers . sidechainAddressUtxoIndexer)
innerLoop = threadDelay 1000000 >> innerLoop -- create some latency
4 changes: 4 additions & 0 deletions marconi-sidechain/marconi-sidechain.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,13 @@ library
hs-source-dirs: src
exposed-modules:
Marconi.Sidechain.Api.HttpServer
Marconi.Sidechain.Api.Query.Indexers.EpochSPD
Marconi.Sidechain.Api.Query.Indexers.Utxo
Marconi.Sidechain.Api.Routes
Marconi.Sidechain.Api.Types
Marconi.Sidechain.Bootstrap
Marconi.Sidechain.CLI
Marconi.Sidechain.Utils

--------------------
-- Local components
Expand All @@ -69,6 +71,7 @@ library
build-depends:
, aeson
, base >=4.9 && <5
, containers
, filepath
, lens
, optparse-applicative
Expand All @@ -77,6 +80,7 @@ library
, stm >=2.5
, text
, time
, vector
, warp

executable marconi-sidechain
Expand Down
Loading

0 comments on commit 9113a84

Please sign in to comment.