Skip to content

Commit

Permalink
WIP: modify responses to use tokenRelationships
Browse files Browse the repository at this point in the history
Signed-off-by: Ricky Saechao <[email protected]>
  • Loading branch information
RickyLB committed May 30, 2024
1 parent 230e5f4 commit 35ae8b5
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 43 deletions.
11 changes: 11 additions & 0 deletions Sources/Hedera/Account/AccountInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ public struct AccountInfo: Sendable {
maxAutomaticTokenAssociations: UInt32,
aliasKey: PublicKey?,
ethereumNonce: UInt64,
tokenRelationships: [TokenId: TokenRelationship],
ledgerId: LedgerId,
staking: StakingInfo?
) {
Expand All @@ -66,6 +67,7 @@ public struct AccountInfo: Sendable {
self.ethereumNonce = ethereumNonce
self.ledgerId = ledgerId
self.staking = staking
self.tokenRelationships = tokenRelationships
self.guts = DeprecatedGuts(
proxyAccountId: proxyAccountId,
sendRecordThreshold: sendRecordThreshold,
Expand Down Expand Up @@ -154,6 +156,9 @@ public struct AccountInfo: Sendable {
/// Staking metadata for this account.
public let staking: StakingInfo?

/// Staking metadata for this account.
public let tokenRelationships: [TokenId: TokenRelationship]

/// Decode `Self` from protobuf-encoded `bytes`.
///
/// - Throws: ``HError/ErrorKind/fromProtobuf`` if:
Expand All @@ -178,6 +183,11 @@ extension AccountInfo: TryProtobufCodable {
let staking = proto.hasStakingInfo ? proto.stakingInfo : nil
let proxyAccountId = proto.hasProxyAccountID ? proto.proxyAccountID : nil

var tokenRelationships: [TokenId: TokenRelationship] = [:]
for relationship in proto.tokenRelationships {
tokenRelationships[.fromProtobuf(relationship.tokenID)] = try TokenRelationship.fromProtobuf(relationship)
}

self.init(
accountId: try .fromProtobuf(proto.accountID),
contractAccountId: proto.contractAccountID,
Expand All @@ -196,6 +206,7 @@ extension AccountInfo: TryProtobufCodable {
maxAutomaticTokenAssociations: UInt32(proto.maxAutomaticTokenAssociations),
aliasKey: try .fromAliasBytes(proto.alias),
ethereumNonce: UInt64(proto.ethereumNonce),
tokenRelationships: tokenRelationships,
ledgerId: .fromBytes(proto.ledgerID),
staking: try .fromProtobuf(staking)
)
Expand Down
39 changes: 37 additions & 2 deletions Sources/Hedera/Account/AccountInfoQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,49 @@ public final class AccountInfoQuery: Query<AccountInfo> {
try await Proto_CryptoServiceAsyncClient(channel: channel).getAccountInfo(request)
}

internal override func makeQueryResponse(_ context: Context, _ response: Proto_Response.OneOf_Response) throws
internal override func makeQueryResponse(_ context: Context, _ response: Proto_Response.OneOf_Response) async throws
-> Response
{
let mirrorNodeGateway = try MirrorNodeGateway.forNetwork(context.mirrorNetworkNodes, context.ledgerId)
let mirrorNodeService = MirrorNodeService.init(mirrorNodeGateway)

guard case .cryptoGetInfo(let proto) = response else {
throw HError.fromProtobuf("unexpected \(response) received, expected `cryptoGetInfo`")
}

return try .fromProtobuf(proto.accountInfo)
let accountInfoProto = proto.accountInfo
let accountId = try AccountId.fromProtobuf(accountInfoProto.accountID)
let tokenRelationshipsProto = try await mirrorNodeService.getTokenRelationshipsForAccount(
String(describing: accountId.num))

var tokenRelationships: [TokenId: TokenRelationship] = [:]

for relationship in tokenRelationshipsProto {
tokenRelationships[.fromProtobuf(relationship.tokenID)] = try TokenRelationship.fromProtobuf(relationship)
}

return AccountInfo(
accountId: try AccountId.fromProtobuf(accountInfoProto.accountID),
contractAccountId: accountInfoProto.contractAccountID,
isDeleted: accountInfoProto.deleted,
proxyAccountId: try .fromProtobuf(accountInfoProto.proxyAccountID),
proxyReceived: Hbar.fromTinybars(accountInfoProto.proxyReceived),
key: try .fromProtobuf(accountInfoProto.key),
balance: .fromTinybars(Int64(accountInfoProto.balance)),
sendRecordThreshold: Hbar.fromTinybars(Int64(accountInfoProto.generateSendRecordThreshold)),
receiveRecordThreshold: Hbar.fromTinybars(Int64(accountInfoProto.generateReceiveRecordThreshold)),
isReceiverSignatureRequired: accountInfoProto.receiverSigRequired,
expirationTime: .fromProtobuf(accountInfoProto.expirationTime),
autoRenewPeriod: .fromProtobuf(accountInfoProto.autoRenewPeriod),
accountMemo: accountInfoProto.memo,
ownedNfts: UInt64(accountInfoProto.ownedNfts),
maxAutomaticTokenAssociations: UInt32(accountInfoProto.maxAutomaticTokenAssociations),
aliasKey: try .fromAliasBytes(accountInfoProto.alias),
ethereumNonce: UInt64(accountInfoProto.ethereumNonce),
tokenRelationships: tokenRelationships,
ledgerId: .fromBytes(accountInfoProto.ledgerID),
staking: try .fromProtobuf(accountInfoProto.stakingInfo)
)
}

internal override func validateChecksums(on ledgerId: LedgerId) throws {
Expand Down
12 changes: 12 additions & 0 deletions Sources/Hedera/Contract/ContractId.swift
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,18 @@ public struct ContractId: EntityId {
public func toBytes() -> Data {
toProtobufBytes()
}

public func populateContractNum(_ client: Client) async throws -> Self {
let address = try EvmAddress.fromBytes(self.evmAddress!)

let mirrorNodeGateway = try MirrorNodeGateway.forClient(client)
let mirrorNodeService = MirrorNodeService(mirrorNodeGateway)

let contractNum = try await mirrorNodeService.getContractNum(address.toString())

return Self(shard: shard, realm: realm, num: contractNum)
}

}

#if compiler(>=5.7)
Expand Down
12 changes: 12 additions & 0 deletions Sources/Hedera/Contract/ContractInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,11 @@ public struct ContractInfo {
/// The maximum number of tokens that a contract can be implicitly associated with.
public let maxAutomaticTokenAssociations: UInt32

/// The tokens associated to the contract
///
/// Query mirror node
public let tokenRelationships: [TokenId: TokenRelationship]

/// Ledger ID for the network the response was returned from.
public let ledgerId: LedgerId

Expand Down Expand Up @@ -91,6 +96,12 @@ extension ContractInfo: TryProtobufCodable {
let autoRenewPeriod = proto.hasAutoRenewPeriod ? proto.autoRenewPeriod : nil
let autoRenewAccountId = proto.hasAutoRenewAccountID ? proto.autoRenewAccountID : nil

var tokenRelationships: [TokenId: TokenRelationship] = [:]

for relationship in proto.tokenRelationships {
tokenRelationships[.fromProtobuf(relationship.tokenID)] = try TokenRelationship.fromProtobuf(relationship)
}

self.init(
contractId: try .fromProtobuf(proto.contractID),
accountId: try .fromProtobuf(proto.accountID),
Expand All @@ -104,6 +115,7 @@ extension ContractInfo: TryProtobufCodable {
isDeleted: proto.deleted,
autoRenewAccountId: try .fromProtobuf(autoRenewAccountId),
maxAutomaticTokenAssociations: UInt32(proto.maxAutomaticTokenAssociations),
tokenRelationships: tokenRelationships,
ledgerId: .fromBytes(proto.ledgerID),
stakingInfo: try .fromProtobuf(proto.stakingInfo)
)
Expand Down
43 changes: 30 additions & 13 deletions Sources/Hedera/Contract/ContractInfoQuery.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,30 +55,47 @@ public final class ContractInfoQuery: Query<ContractInfo> {
try await Proto_SmartContractServiceAsyncClient(channel: channel).getContractInfo(request)
}

internal override func makeQueryResponse(_ context: MirrorNetworkContext, _ response: Proto_Response.OneOf_Response) async throws
internal override func makeQueryResponse(_ context: MirrorNetworkContext, _ response: Proto_Response.OneOf_Response)
async throws
-> Response
{
let mirrorNodeGateway = try MirrorNodeGateway.forNetwork(context.mirrorNetworkNodes, context.ledgerId)
let mirrorNodeService = MirrorNodeService.init(mirrorNodeGateway)

switch response {
case .contractGetInfo(let proto):
let contractId = try ContractId(protobuf: proto.contractInfo.contractID)
let tokenRelationships = try await mirrorNodeService.getTokenRelationshipsForAccount(String(describing: contractId.num))

let consensusProto = proto.contractInfo
let updatedProto = consensusProto.

default:

guard case .contractGetInfo(let proto) = response else {
throw HError.fromProtobuf("unexpected \(response) received, expected `contractGetInfo`")
}
let contractInfoProto = proto.contractInfo
let contractId = try ContractId.fromProtobuf(contractInfoProto.contractID)
let tokenRelationshipsProto = try await mirrorNodeService.getTokenRelationshipsForAccount(
String(describing: contractId.num))

var tokenRelationships: [TokenId: TokenRelationship] = [:]

return try .fromProtobuf(proto.contractInfo)
for relationship in tokenRelationshipsProto {
tokenRelationships[.fromProtobuf(relationship.tokenID)] = try TokenRelationship.fromProtobuf(relationship)
}

return ContractInfo(
contractId: try ContractId.fromProtobuf(contractInfoProto.contractID),
accountId: try AccountId.fromProtobuf(contractInfoProto.accountID),
contractAccountId: contractInfoProto.contractAccountID,
adminKey: try .fromProtobuf(contractInfoProto.adminKey),
expirationTime: .fromProtobuf(contractInfoProto.expirationTime),
autoRenewPeriod: .fromProtobuf(contractInfoProto.autoRenewPeriod),
storage: UInt64(contractInfoProto.storage),
contractMemo: contractInfoProto.memo,
balance: .fromTinybars(Int64(contractInfoProto.balance)),
isDeleted: contractInfoProto.deleted,
autoRenewAccountId: try .fromProtobuf(contractInfoProto.autoRenewAccountID),
maxAutomaticTokenAssociations: UInt32(contractInfoProto.maxAutomaticTokenAssociations),
tokenRelationships: tokenRelationships,
ledgerId: .fromBytes(contractInfoProto.ledgerID),
stakingInfo: try .fromProtobuf(contractInfoProto.stakingInfo))
}

internal override func validateChecksums(on ledgerId: LedgerId) throws {
try contractId?.validateChecksums(on: ledgerId)
try super.validateChecksums(on: ledgerId)
}
}

15 changes: 8 additions & 7 deletions Sources/Hedera/MirrorNodeGateway.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ internal struct MirrorNodeGateway {
self.mirrorNodeUrl = mirrorNodeUrl
}

internal static func forClient(client: Client) throws -> MirrorNodeGateway {
internal static func forClient(_ client: Client) throws -> MirrorNodeGateway {
let mirrorNodeUrl = try MirrorNodeRouter.getMirrorNodeUrl(client.mirrorNetwork, client.ledgerId)

return .init(mirrorNodeUrl: mirrorNodeUrl)
Expand All @@ -45,7 +45,7 @@ internal struct MirrorNodeGateway {

internal func getAccountInfo(_ idOrAliasOrEvmAddress: String) async throws -> [String: Any] {
let fullApiUrl = MirrorNodeRouter.buildApiUrl(
self.mirrorNodeUrl, MirrorNodeRouter.ACCOUNTS_ROUTE, idOrAliasOrEvmAddress)
self.mirrorNodeUrl, MirrorNodeRouter.accountsRoute, idOrAliasOrEvmAddress)

let responseBody = try await queryFromMirrorNode(fullApiUrl)

Expand All @@ -66,7 +66,9 @@ internal struct MirrorNodeGateway {

internal func getContractInfo(_ idOrAliasOrEvmAddress: String) async throws -> [String: Any] {
let fullApiUrl = MirrorNodeRouter.buildApiUrl(
self.mirrorNodeUrl, MirrorNodeRouter.CONTRACTS_ROUTE, idOrAliasOrEvmAddress)
self.mirrorNodeUrl, MirrorNodeRouter.contractsRoute, idOrAliasOrEvmAddress)

print("ContractfullApiUrl: \(fullApiUrl)")

let responseBody = try await queryFromMirrorNode(fullApiUrl)

Expand All @@ -75,7 +77,6 @@ internal struct MirrorNodeGateway {
domain: "InvalidResponseError", code: -1,
userInfo: [NSLocalizedDescriptionKey: "Response body is not valid UTF-8"])
}

guard let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] else {
throw NSError(
domain: "InvalidResponseError", code: -1,
Expand All @@ -87,10 +88,10 @@ internal struct MirrorNodeGateway {

internal func getAccountTokens(_ idOrAliasOrEvmAddress: String) async throws -> [String: Any] {
let fullApiUrl = MirrorNodeRouter.buildApiUrl(
self.mirrorNodeUrl, MirrorNodeRouter.ACCOUNTS_ROUTE, idOrAliasOrEvmAddress)

self.mirrorNodeUrl, MirrorNodeRouter.accountTokensRoute, idOrAliasOrEvmAddress)
let responseBody = try await queryFromMirrorNode(fullApiUrl)

guard let jsonData = responseBody.data(using: .utf8) else {
throw NSError(
domain: "InvalidResponseError", code: -1,
Expand Down
33 changes: 15 additions & 18 deletions Sources/Hedera/MirrorNodeRouter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,46 +26,43 @@ import HederaProtobufs
import NIOCore

internal struct MirrorNodeRouter {
static let API_VERSION: String = "/api/v1"
static let apiVersion: String = "/api/v1"

static let LOCAL_NODE_PORT = "5551"
static let localNodePort = "5551"

public static let ACCOUNTS_ROUTE = "accounts"
public static let CONTRACTS_ROUTE = "contracts"
public static let ACCOUNT_TOKENS_ROUTE = "accounts_tokens"
public static let accountsRoute = "accounts"
public static let contractsRoute = "contracts"
public static let accountTokensRoute = "account_tokens"

static let routes: [String: String] = [
ACCOUNTS_ROUTE: "/accounts/%@",
CONTRACTS_ROUTE: "/contracts/%@",
ACCOUNT_TOKENS_ROUTE: "/accounts/%@/tokens",
accountsRoute: "/accounts/%@",
contractsRoute: "/contracts/%@",
accountTokensRoute: "/accounts/%@/tokens",
]

private func MirrorNodeRouter() {}

static func getMirrorNodeUrl(_ mirrorNetwork: [String], _ ledgerId: LedgerId?) throws -> String {
let mirrorNodeAddress: String? =
mirrorNetwork
var mirrorNodeAddress: String = ""

mirrorNetwork
.map { address in
address.prefix { $0 != ":" }
}
.first.map { String($0) }

if mirrorNodeAddress == nil {
fatalError("Mirror address not found")
}
.first.map { mirrorNodeAddress = String($0) }!

var fullMirrorNodeUrl: String

if ledgerId != nil {
fullMirrorNodeUrl = String("http://\(mirrorNodeAddress)")
fullMirrorNodeUrl = String("https://\(mirrorNodeAddress)")
} else {
fullMirrorNodeUrl = String("http://\(mirrorNodeAddress):\(LOCAL_NODE_PORT)")
fullMirrorNodeUrl = String("http://\(mirrorNodeAddress):\(localNodePort)")
}

return fullMirrorNodeUrl
}

static func buildApiUrl(_ mirrorNodeUrl: String, _ route: String, _ id: String) -> String {
return String("\(mirrorNodeUrl)\(API_VERSION)\(String(format: "\(String(describing: routes[route]))", id))")
return String("\(mirrorNodeUrl)\(apiVersion)\(String(format: "\(String(describing: routes[route]))", id))!")
}
}
5 changes: 3 additions & 2 deletions Sources/Hedera/MirrorNodeService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ internal final class MirrorNodeService {

let contractIdNum = ContractId(String(describing: contractId))?.num

fatalError("contract id: \(contractIdNum)")

return contractIdNum!
}

Expand Down Expand Up @@ -104,8 +106,7 @@ internal final class MirrorNodeService {
let accountTokensResponse = try await self.mirrorNodeGateway.getAccountTokens(evmAddress)

guard let tokens = accountTokensResponse["tokens"] else {
fatalError("Error while processing getTokenRelationshipsForAccount mirror node query")

fatalError("Error while processing getTokenRelationshipsForAccount mirror node query: \(accountTokensResponse)")
}

var tokenBalances: [Proto_TokenRelationship] = []
Expand Down
Loading

0 comments on commit 35ae8b5

Please sign in to comment.