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
14 changes: 13 additions & 1 deletion .github/workflows/dynamic-contracts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -137,8 +137,20 @@ jobs:
fi
sleep 2
done
# Assert the mainnet nesting, not just that a tick advanced.
shape_ok=0
if [ "$ticked" -eq 1 ]; then
ti=$(wget -qO- http://127.0.0.1:41841/live/v1/tick-info 2>/dev/null) || true
bh=$(wget -qO- http://127.0.0.1:41841/live/v1/block-height 2>/dev/null) || true
nested=$(printf "%s" "$ti" | sed -n "s/.*\"tickInfo\":{\([^}]*\)}.*/\1/p")
shape_ok=1
for field in tick duration epoch initialTick; do
printf "%s" "$nested" | grep -q "\"$field\":" || { echo "tick-info: tickInfo.$field missing"; shape_ok=0; }
done
printf "%s" "$bh" | grep -q "\"blockHeight\":{" || { echo "block-height: blockHeight wrapper missing"; shape_ok=0; }
fi
kill "$node_pid" 2>/dev/null || true
if [ "$ticked" -ne 1 ]; then
if [ "$ticked" -ne 1 ] || [ "$shape_ok" -ne 1 ]; then
cat node-smoke.log
exit 1
fi
Expand Down
101 changes: 45 additions & 56 deletions src/extensions/http/controller/rpc_live_controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@
#include "extensions/rpc/rpc_core.h"

// ============================ live (/live/v1/...) ============================
// Mainnet returns errors as HTTP 4xx/5xx, never 200: clients branch on res.ok.
static RpcResp rpcErr(int code, const std::string& message, int status = 400)
{
Json::Value e;
e["code"] = code;
e["message"] = message;
e["details"] = Json::Value(Json::arrayValue);
return jsonResp(e, status);
}

RPC_ROUTE("GET", "/live/v1/assets/issuances")
{
std::string issuerIdentity = req.getParameter("issuerIdentity");
Expand All @@ -37,14 +47,15 @@ RPC_ROUTE("GET", "/live/v1/assets/issuances")
Json::Value root;
Json::Value assetJson = HttpUtils::issuanceToJson((HttpUtils::AssetIssuanceType *)&asset);
root["data"] = assetJson;
root["tick"] = system.tick;
root["universeIndex"] = Json::UInt64(i);
assetsArray.append(root);
targetUniverseIndex = i;
break;
}
}
(void)targetUniverseIndex;
result["assets"] = assetsArray;
result["tick"] = system.tick;
result["universeIndex"] = Json::UInt64(targetUniverseIndex);
return jsonResp(result);
}

Expand All @@ -54,15 +65,11 @@ RPC_ROUTE("GET", "/live/v1/assets/issuances/:index")
unsigned long long index = std::stoull(req.getParameter("index"));
if (index >= ASSETS_CAPACITY)
{
result["code"] = 3;
result["message"] = "Index out of range";
return jsonResp(result);
return rpcErr(3, "Index out of range", 400);
}
if (assets[index].varStruct.issuance.type != ISSUANCE)
{
result["code"] = 3;
result["message"] = "No asset issuance at the given index";
return jsonResp(result);
return rpcErr(3, "No asset issuance at the given index", 400);
}
auto &asset = assets[index].varStruct.issuance;
Json::Value assetJson = HttpUtils::issuanceToJson((HttpUtils::AssetIssuanceType *)&asset);
Expand Down Expand Up @@ -118,15 +125,11 @@ RPC_ROUTE("GET", "/live/v1/assets/ownerships/:index")
unsigned long long index = std::stoull(req.getParameter("index"));
if (index >= ASSETS_CAPACITY)
{
result["code"] = 3;
result["message"] = "Index out of range";
return jsonResp(result);
return rpcErr(3, "Index out of range", 400);
}
if (assets[index].varStruct.ownership.type != OWNERSHIP)
{
result["code"] = 3;
result["message"] = "No asset ownership at the given index";
return jsonResp(result);
return rpcErr(3, "No asset ownership at the given index", 400);
}
auto &asset = assets[index].varStruct.ownership;
Json::Value assetJson = HttpUtils::ownershipToJson((HttpUtils::AssetOwnershipType *)&asset);
Expand Down Expand Up @@ -189,15 +192,11 @@ RPC_ROUTE("GET", "/live/v1/assets/possessions/:index")
unsigned long long index = std::stoull(req.getParameter("index"));
if (index >= ASSETS_CAPACITY)
{
result["code"] = 3;
result["message"] = "Index out of range";
return jsonResp(result);
return rpcErr(3, "Index out of range", 400);
}
if (assets[index].varStruct.possession.type != POSSESSION)
{
result["code"] = 3;
result["message"] = "No asset possession at the given index";
return jsonResp(result);
return rpcErr(3, "No asset possession at the given index", 400);
}
auto &asset = assets[index].varStruct.possession;
Json::Value assetJson = HttpUtils::possessionToJson((HttpUtils::AssetPossessionType *)&asset);
Expand Down Expand Up @@ -258,6 +257,8 @@ RPC_ROUTE("GET", "/live/v1/assets/:identity/owned")

Json::Value root;
Json::Value assetJson = HttpUtils::ownershipToJson((HttpUtils::AssetOwnershipType *)&asset);
// Only the per-identity routes carry padding; the universe-index ones do not.
assetJson["padding"] = (int)asset.padding[0];
assetJson["issuedAsset"] = HttpUtils::issuanceToJson((HttpUtils::AssetIssuanceType *)issuanceAsset);
root["data"] = assetJson;
Json::Value info(Json::objectValue);
Expand Down Expand Up @@ -292,7 +293,12 @@ RPC_ROUTE("GET", "/live/v1/assets/:identity/possessed")

Json::Value root;
Json::Value assetJson = HttpUtils::possessionToJson((HttpUtils::AssetPossessionType *)&asset);
// Per-identity possessions report the owned asset's issuanceIndex, not ownershipIndex.
assetJson["padding"] = (int)asset.padding[0];
assetJson.removeMember("ownershipIndex");
assetJson["issuanceIndex"] = ownershipAsset->issuanceIndex;
assetJson["ownedAsset"] = HttpUtils::ownershipToJson((HttpUtils::AssetOwnershipType *)ownershipAsset);
assetJson["ownedAsset"]["padding"] = (int)ownershipAsset->padding[0];
assetJson["ownedAsset"]["issuedAsset"] = HttpUtils::issuanceToJson((HttpUtils::AssetIssuanceType *)issuanceAsset);
root["data"] = assetJson;
Json::Value info(Json::objectValue);
Expand Down Expand Up @@ -327,23 +333,24 @@ RPC_ROUTE("GET", "/live/v1/balances/:id")
return jsonResp(result);
}

// block-height + tick-info share the same body in the drogon controller.
static RpcResp rpcLiveTickInfo(const RpcReq& req)
static RpcResp rpcLiveTickInfo(const RpcReq& req, const char* wrapperKey)
{
(void)req;
Json::Value tickInfo;
tickInfo["tick"] = system.tick;
tickInfo["duration"] = 0;
tickInfo["epoch"] = system.epoch;
tickInfo["initialTick"] = system.initialTick;

Json::Value json;
json["epoch"] = system.epoch;
json["tick"] = system.tick;
json["initialTick"] = system.initialTick;
json[wrapperKey] = tickInfo;
json["alignedVotes"] = gTickNumberOfComputors;
json["misalignedVotes"] = gTickTotalNumberOfComputors - gTickNumberOfComputors;
json["mainAuxStatus"] = mainAuxStatus;
json["duration"] = 0;
json["tickInfo"]["tick"] = system.tick;
return jsonResp(json);
}
RPC_ROUTE("GET", "/live/v1/block-height") { return rpcLiveTickInfo(req); }
RPC_ROUTE("GET", "/live/v1/tick-info") { return rpcLiveTickInfo(req); }
RPC_ROUTE("GET", "/live/v1/block-height") { return rpcLiveTickInfo(req, "blockHeight"); }
RPC_ROUTE("GET", "/live/v1/tick-info") { return rpcLiveTickInfo(req, "tickInfo"); }

RPC_ROUTE("POST", "/live/v1/broadcast-transaction")
{
Expand All @@ -353,9 +360,7 @@ RPC_ROUTE("POST", "/live/v1/broadcast-transaction")
auto json = rpcJsonBody(req.body);
if (!json)
{
result["code"] = 3;
result["message"] = "Invalid JSON";
return jsonResp(result);
return rpcErr(3, "Invalid JSON", 400);
}

std::string txBase64 = (*json)["encodedTransaction"].asString();
Expand All @@ -364,19 +369,15 @@ RPC_ROUTE("POST", "/live/v1/broadcast-transaction")
Transaction *tx = (Transaction*)txData.data();
if (!tx->checkValidity())
{
result["code"] = 3;
result["message"] = "Invalid validity";
return jsonResp(result);
return rpcErr(3, "Invalid validity", 400);
}
std::cout << "tx json" << HttpUtils::transactionToJson(tx, false) << std::endl;
{
unsigned char digest[32];
KangarooTwelve(txData.data(), tx->totalSize() - SIGNATURE_SIZE, digest, sizeof(digest));
if (!verify(tx->sourcePublicKey.m256i_u8, digest, tx->signaturePtr()))
{
result["code"] = 3;
result["message"] = "Invalid signature";
return jsonResp(result);
return rpcErr(3, "Invalid signature", 400);
}
}

Expand All @@ -400,9 +401,7 @@ RPC_ROUTE("POST", "/live/v1/broadcast-transaction")
}
catch (const std::exception &e)
{
result["code"] = -1;
result["message"] = "Exception: " + std::string(e.what());
return jsonResp(result);
return rpcErr(-1, "Exception: " + std::string(e.what()), 500);
}
}

Expand Down Expand Up @@ -433,27 +432,21 @@ RPC_ROUTE("POST", "/live/v1/querySmartContract")
auto json = rpcJsonBody(req.body);
if (!json)
{
result["code"] = 3;
result["message"] = "Invalid JSON";
return jsonResp(result);
return rpcErr(3, "Invalid JSON", 400);
}

unsigned int contractIndex = (*json)["contractIndex"].asUInt();
if (contractIndex < 1 || contractIndex >= contractCount)
{
result["code"] = 3;
result["message"] = "contractIndex out of range";
return jsonResp(result, 400);
return rpcErr(3, "contractIndex out of range", 400);
}
unsigned short inputType = (*json)["inputType"].asUInt();
unsigned short inputSize = (*json)["inputSize"].asUInt();
std::string requestData = (*json)["requestData"].asString();
std::vector<uint8_t> inputData = base64_decode(requestData);
if (inputData.size() != inputSize)
{
result["code"] = 3;
result["message"] = "Input size mismatch";
return jsonResp(result, 400);
return rpcErr(3, "Input size mismatch", 400);
}
QpiContextUserFunctionCall qpiContext(contractIndex);
auto errorCode = qpiContext.call(inputType, inputData.data(), inputSize);
Expand All @@ -466,16 +459,12 @@ RPC_ROUTE("POST", "/live/v1/querySmartContract")
}
else
{
result["code"] = -1;
result["message"] = "Error calling smart contract function: " + std::to_string(errorCode);
return jsonResp(result, 500);
return rpcErr(-1, "Error calling smart contract function: " + std::to_string(errorCode), 500);
}
}
catch (const std::exception &e)
{
result["code"] = -1;
result["message"] = "Exception: " + std::string(e.what());
return jsonResp(result, 500);
return rpcErr(-1, "Exception: " + std::string(e.what()), 500);
}
}

Expand Down
8 changes: 5 additions & 3 deletions src/extensions/http/controller/rpc_queryv2_controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ RPC_ROUTE("POST", "/query/v1/getTickData")
jsonObject["tickNumber"] = localTickData.tick;
jsonObject["epoch"] = localTickData.epoch;
jsonObject["computorIndex"] = localTickData.computorIndex;
jsonObject["timelock"] = base64_encode(localTickData.timelock.m256i_u8, 32);
jsonObject["timeLock"] = base64_encode(localTickData.timelock.m256i_u8, 32);
jsonObject["timestamp"] = HttpUtils::formatTimestamp(localTickData.millisecond, localTickData.second, localTickData.minute,
localTickData.hour, localTickData.day, localTickData.month, localTickData.year);
jsonObject["varStruct"] = "";
Expand All @@ -152,10 +152,12 @@ RPC_ROUTE("POST", "/query/v1/getTickData")
txDigestsJson.append(wchar_to_string(id));
}
}
jsonObject["transactionDigests"] = txDigestsJson;
jsonObject["transactionHashes"] = txDigestsJson;
Json::Value contractFeesJson(Json::arrayValue);
// Zero fees are dropped, as go-archiver's contractFeesToProto() does.
for (unsigned int i = 0; i < MAX_NUMBER_OF_CONTRACTS; i++)
contractFeesJson.append(Json::UInt64(localTickData.contractFees[i]));
if (localTickData.contractFees[i] != 0)
contractFeesJson.append(std::to_string(localTickData.contractFees[i]));
jsonObject["contractFees"] = contractFeesJson;
jsonObject["signature"] = base64_encode(localTickData.signature, SIGNATURE_SIZE);
return jsonResp(jsonObject);
Expand Down
4 changes: 2 additions & 2 deletions src/extensions/http/controller/rpc_stats_controller.h
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ RPC_ROUTE("GET", "/v1/latest-stats")
}
TickStorage::tickData.releaseLock();

data["circulatingSupply"] = Json::UInt64(spectrumInfo.totalAmount);
data["circulatingSupply"] = std::to_string(spectrumInfo.totalAmount);
data["activeAddresses"] = spectrumInfo.numberOfEntities;
data["price"] = 0;
data["marketCap"] = "0";
Expand All @@ -237,7 +237,7 @@ RPC_ROUTE("GET", "/v1/latest-stats")
}
data["emptyTicksInCurrentEpoch"] = emptyTicks;
data["epochTickQuality"] = system.tick - system.initialTick == 0 ? 0 : std::roundf((float)(system.tick - system.initialTick - emptyTicks) / (float)(system.tick - system.initialTick) * 100000.0f) / 100000.0f;
data["burnedQus"] = 0;
data["burnedQus"] = "0";
result["data"] = data;
return jsonResp(result);
}
Expand Down
4 changes: 2 additions & 2 deletions src/extensions/http/static/explorer/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,7 @@ async function renderTick(n) {
<td>${tx.hash ? `<button class="logs-btn" onclick="window.__showTxLogs('${fmt.esc(tx.hash)}', ${td.tickNumber ?? n})">◈ LOGS</button>` : ''}</td>
</tr>`).join('');

const digests = (td.transactionDigests || []);
const digests = (td.transactionHashes || []);

$view().innerHTML = `
<h2 class="section">TICK ${fmt.n(td.tickNumber ?? n)}</h2>
Expand Down Expand Up @@ -482,7 +482,7 @@ async function renderTick(n) {
<div class="grid-2" style="margin-top:1.5em">
<div>
<h2 class="section">TIMELOCK</h2>
<pre class="hex">${fmt.esc(td.timelock || '—')}</pre>
<pre class="hex">${fmt.esc(td.timeLock || '—')}</pre>
</div>
<div>
<h2 class="section">SIGNATURE</h2>
Expand Down
6 changes: 4 additions & 2 deletions src/extensions/http/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ class HttpUtils
assetJson["name"] = std::string(asset->name);
assetJson["type"] = ASSET_ISSUANCE;
assetJson["numberOfDecimalPlaces"] = asset->numberOfDecimalPlaces;
for (int i = 0; i < 8; i++)
// char[7]: the old i < 8 ran past the struct.
for (size_t i = 0; i < sizeof(asset->unitOfMeasurement); i++)
{
unitOfMeasurementArray.append(asset->unitOfMeasurement[i]);
}
Expand Down Expand Up @@ -124,7 +125,8 @@ class HttpUtils
getIdentity(digest, txHashStr, true);
CHAR16 humanId[61] = {0};
jsonObject["hash"] = wchar_to_string(txHashStr);
jsonObject["amount"] = Json::UInt64(tx->amount);
// int64 as string, per protobuf JSON: a number loses precision past 2^53.
jsonObject["amount"] = std::to_string(tx->amount);
getIdentity((const unsigned char *)&tx->sourcePublicKey, humanId, false);
jsonObject["source"] = wchar_to_string(humanId);
getIdentity((const unsigned char *)&tx->destinationPublicKey, humanId, false);
Expand Down
Loading