Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 87 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ QUBIC_API_SIDECAR_POOL_NODE_FETCHER_URL: "http://127.0.0.1:8080/status"
QUBIC_API_SIDECAR_SERVER_READ_TIMEOUT: (default: 5s)
QUBIC_API_SIDECAR_SERVER_WRITE_TIMEOUT: (default: 5s)
QUBIC_API_SIDECAR_SERVER_SHUTDOWN_TIMEOUT: (default: 5s)
QUBIC_API_SIDECAR_SERVER_MAX_BATCH_IDENTITIES: (default: 15)

QUBIC_API_SIDECAR_POOL_NODE_FETCHER_TIMEOUT: (default: 2s)
QUBIC_API_SIDECAR_POOL_INITIAL_CAP: (default: 5)
Expand Down Expand Up @@ -316,7 +317,7 @@ curl localhost:8000/assets/IGJQYTMFLVNIMEAKLANHKGNGZPFCFJGSMVOWMNGLWCZWKFHANHGCB
"numberOfUnits": "2",
"ownedAsset": {
"ownerIdentity": "IGJQYTMFLVNIMEAKLANHKGNGZPFCFJGSMVOWMNGLWCZWKFHANHGCBYODMKBC",
"type": 3,
"type": 2,
"padding": 0,
"managingContractIndex": 1,
"issuanceIndex": 9707976,
Expand Down Expand Up @@ -345,7 +346,7 @@ curl localhost:8000/assets/IGJQYTMFLVNIMEAKLANHKGNGZPFCFJGSMVOWMNGLWCZWKFHANHGCB
"numberOfUnits": "1",
"ownedAsset": {
"ownerIdentity": "IGJQYTMFLVNIMEAKLANHKGNGZPFCFJGSMVOWMNGLWCZWKFHANHGCBYODMKBC",
"type": 3,
"type": 2,
"padding": 0,
"managingContractIndex": 1,
"issuanceIndex": 9707978,
Expand Down Expand Up @@ -374,7 +375,7 @@ curl localhost:8000/assets/IGJQYTMFLVNIMEAKLANHKGNGZPFCFJGSMVOWMNGLWCZWKFHANHGCB
"numberOfUnits": "186685601",
"ownedAsset": {
"ownerIdentity": "IGJQYTMFLVNIMEAKLANHKGNGZPFCFJGSMVOWMNGLWCZWKFHANHGCBYODMKBC",
"type": 3,
"type": 2,
"padding": 0,
"managingContractIndex": 1,
"issuanceIndex": 9707980,
Expand Down Expand Up @@ -403,7 +404,7 @@ curl localhost:8000/assets/IGJQYTMFLVNIMEAKLANHKGNGZPFCFJGSMVOWMNGLWCZWKFHANHGCB
"numberOfUnits": "10",
"ownedAsset": {
"ownerIdentity": "IGJQYTMFLVNIMEAKLANHKGNGZPFCFJGSMVOWMNGLWCZWKFHANHGCBYODMKBC",
"type": 3,
"type": 2,
"padding": 0,
"managingContractIndex": 1,
"issuanceIndex": 9707982,
Expand All @@ -424,4 +425,85 @@ curl localhost:8000/assets/IGJQYTMFLVNIMEAKLANHKGNGZPFCFJGSMVOWMNGLWCZWKFHANHGCB
}
]
}
```
```
#### /assets (batch)

Fetches the assets owned and possessed by several identities at once. The requests are
pipelined over a single node connection, so one call costs roughly one round trip for the
whole set instead of one per identity. Prefer this over calling
`/assets/{identity}/owned` and `/assets/{identity}/possessed` in a loop.

Results are returned in the order the identities were given, and every requested identity
appears in the response, with empty lists if it holds no assets. The list must contain
between 1 and `QUBIC_API_SIDECAR_SERVER_MAX_BATCH_IDENTITIES` (default 15) identities,
with no duplicates. All identities are validated before any node call, so a single
malformed entry fails the whole request without fetching anything.

```shell
curl -X POST localhost:8000/assets \
-H 'Content-Type: application/json' \
-d '{"identities":["IGJQYTMFLVNIMEAKLANHKGNGZPFCFJGSMVOWMNGLWCZWKFHANHGCBYODMKBC"]}'
```
```json
{
"assets": [
{
"identity": "IGJQYTMFLVNIMEAKLANHKGNGZPFCFJGSMVOWMNGLWCZWKFHANHGCBYODMKBC",
"ownedAssets": [
{
"data": {
"ownerIdentity": "IGJQYTMFLVNIMEAKLANHKGNGZPFCFJGSMVOWMNGLWCZWKFHANHGCBYODMKBC",
"type": 2,
"padding": 0,
"managingContractIndex": 1,
"issuanceIndex": 9707976,
"numberOfUnits": "2",
"issuedAsset": {
"issuerIdentity": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFXIB",
"type": 1,
"name": "RANDOM",
"numberOfDecimalPlaces": 0,
"unitOfMeasurement": [0, 0, 0, 0, 0, 0, 0]
}
},
"info": {
"tick": 14057759,
"universeIndex": 0
}
}
],
"possessedAssets": [
{
"data": {
"possessorIdentity": "IGJQYTMFLVNIMEAKLANHKGNGZPFCFJGSMVOWMNGLWCZWKFHANHGCBYODMKBC",
"type": 3,
"padding": 0,
"managingContractIndex": 1,
"issuanceIndex": 9707976,
"numberOfUnits": "2",
"ownedAsset": {
"ownerIdentity": "IGJQYTMFLVNIMEAKLANHKGNGZPFCFJGSMVOWMNGLWCZWKFHANHGCBYODMKBC",
"type": 2,
"padding": 0,
"managingContractIndex": 1,
"issuanceIndex": 9707976,
"numberOfUnits": "2",
"issuedAsset": {
"issuerIdentity": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFXIB",
"type": 1,
"name": "RANDOM",
"numberOfDecimalPlaces": 0,
"unitOfMeasurement": [0, 0, 0, 0, 0, 0, 0]
}
}
},
"info": {
"tick": 14057759,
"universeIndex": 0
}
}
]
}
]
}
```
4 changes: 3 additions & 1 deletion app/grpc_server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ func run(logger *log.Logger) error {
HttpHost string `conf:"default:0.0.0.0:8000"`
GrpcHost string `conf:"default:0.0.0.0:8001"`
MaxTickFetchUrl string `conf:"default:http://localhost:8080/max-tick"`
// MaxBatchIdentities caps how many identities one batch asset request may ask for.
MaxBatchIdentities int `conf:"default:15"`
}
Pool struct {
NodeFetcherUrl string `conf:"default:http://localhost:8080/status"`
Expand Down Expand Up @@ -84,7 +86,7 @@ func run(logger *log.Logger) error {
return errors.Wrap(err, "creating qubic pool")
}

rpcServer := rpc.NewServer(cfg.Server.GrpcHost, cfg.Server.HttpHost, logger, pool, cfg.Server.MaxTickFetchUrl)
rpcServer := rpc.NewServer(cfg.Server.GrpcHost, cfg.Server.HttpHost, logger, pool, cfg.Server.MaxTickFetchUrl, cfg.Server.MaxBatchIdentities)
err = rpcServer.Start()
if err != nil {
return errors.Wrap(err, "starting rpc server")
Expand Down
163 changes: 163 additions & 0 deletions foundation/rpc_server/asset_converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,173 @@ import (
"fmt"

"github.com/pkg/errors"
qubic "github.com/qubic/go-node-connector/v2"
"github.com/qubic/go-node-connector/v2/types"
"github.com/qubic/qubic-http/protobuff"
)

func convertAssetInfo(source types.AssetInfo) *protobuff.AssetInfo {
return &protobuff.AssetInfo{
Tick: source.Tick,
UniverseIndex: source.UniverseIndex,
}
}

func convertIssuedAssetData(source types.IssuedAssetData) (*protobuff.IssuedAssetData, error) {
var issuer types.Identity
issuer, err := issuer.FromPubKey(source.PublicKey, false)
if err != nil {
return nil, errors.Wrap(err, "failed to get identity for issued asset public key")
}

return &protobuff.IssuedAssetData{
IssuerIdentity: issuer.String(),
Type: uint32(source.Type),
Name: int8ArrayToString(source.Name[:]),
NumberOfDecimalPlaces: int32(source.NumberOfDecimalPlaces),
UnitOfMeasurement: int8ArrayToInt32Array(source.UnitOfMeasurement[:]),
}, nil
}

func convertOwnedAssetData(source types.OwnedAssetData) (*protobuff.OwnedAssetData, error) {
var owner types.Identity
owner, err := owner.FromPubKey(source.PublicKey, false)
if err != nil {
return nil, errors.Wrap(err, "failed to get identity for owned asset public key")
}

issuedAsset, err := convertIssuedAssetData(source.IssuedAsset)
if err != nil {
return nil, errors.Wrap(err, "converting issued asset")
}

return &protobuff.OwnedAssetData{
OwnerIdentity: owner.String(),
Type: uint32(source.Type),
Padding: int32(source.Padding[0]),
ManagingContractIndex: uint32(source.ManagingContractIndex),
IssuanceIndex: source.IssuanceIndex,
NumberOfUnits: source.NumberOfUnits,
IssuedAsset: issuedAsset,
}, nil
}

func convertPossessedAssetData(source types.PossessedAssetData) (*protobuff.PossessedAssetData, error) {
var possessor types.Identity
possessor, err := possessor.FromPubKey(source.PublicKey, false)
if err != nil {
return nil, errors.Wrap(err, "failed to get identity for possessed asset public key")
}

// the nested ownership record is converted from its own data, not from the
// possession record that contains it
ownedAsset, err := convertOwnedAssetData(source.OwnedAsset)
if err != nil {
return nil, errors.Wrap(err, "converting owned asset")
}

return &protobuff.PossessedAssetData{
PossessorIdentity: possessor.String(),
Type: uint32(source.Type),
Padding: int32(source.Padding[0]),
ManagingContractIndex: uint32(source.ManagingContractIndex),
IssuanceIndex: source.IssuanceIndex,
NumberOfUnits: source.NumberOfUnits,
OwnedAsset: ownedAsset,
}, nil
}

func convertIssuedAsset(source types.IssuedAsset) (*protobuff.IssuedAsset, error) {
data, err := convertIssuedAssetData(source.Data)
if err != nil {
return nil, err
}

return &protobuff.IssuedAsset{Data: data, Info: convertAssetInfo(source.Info)}, nil
}

func convertOwnedAsset(source types.OwnedAsset) (*protobuff.OwnedAsset, error) {
data, err := convertOwnedAssetData(source.Data)
if err != nil {
return nil, err
}

return &protobuff.OwnedAsset{Data: data, Info: convertAssetInfo(source.Info)}, nil
}

func convertPossessedAsset(source types.PossessedAsset) (*protobuff.PossessedAsset, error) {
data, err := convertPossessedAssetData(source.Data)
if err != nil {
return nil, err
}

return &protobuff.PossessedAsset{Data: data, Info: convertAssetInfo(source.Info)}, nil
}

func convertIssuedAssets(source types.IssuedAssets) ([]*protobuff.IssuedAsset, error) {
converted := make([]*protobuff.IssuedAsset, 0, len(source))
for _, asset := range source {
issuedAsset, err := convertIssuedAsset(asset)
if err != nil {
return nil, err
}
converted = append(converted, issuedAsset)
}

return converted, nil
}

func convertOwnedAssets(source types.OwnedAssets) ([]*protobuff.OwnedAsset, error) {
converted := make([]*protobuff.OwnedAsset, 0, len(source))
for _, asset := range source {
ownedAsset, err := convertOwnedAsset(asset)
if err != nil {
return nil, err
}
converted = append(converted, ownedAsset)
}

return converted, nil
}

func convertPossessedAssets(source types.PossessedAssets) ([]*protobuff.PossessedAsset, error) {
converted := make([]*protobuff.PossessedAsset, 0, len(source))
for _, asset := range source {
possessedAsset, err := convertPossessedAsset(asset)
if err != nil {
return nil, err
}
converted = append(converted, possessedAsset)
}

return converted, nil
}

// convertAddressAssets maps the batched node response into the proto response, keeping
// the order the identities were requested in.
func convertAddressAssets(source []qubic.AddressAssets) ([]*protobuff.IdentityAssets, error) {
converted := make([]*protobuff.IdentityAssets, 0, len(source))
for _, addressAssets := range source {
owned, err := convertOwnedAssets(addressAssets.Owned)
if err != nil {
return nil, errors.Wrapf(err, "converting owned assets for identity %s", addressAssets.Identity)
}

possessed, err := convertPossessedAssets(addressAssets.Possessed)
if err != nil {
return nil, errors.Wrapf(err, "converting possessed assets for identity %s", addressAssets.Identity)
}

converted = append(converted, &protobuff.IdentityAssets{
Identity: addressAssets.Identity,
OwnedAssets: owned,
PossessedAssets: possessed,
})
}

return converted, nil
}

func convertAssetIssuance(source types.AssetIssuance) (*protobuff.AssetIssuance, error) {
var issuerIdentity types.Identity
issuerIdentity, err := issuerIdentity.FromPubKey(source.Asset.PublicKey, false)
Expand Down
Loading
Loading