2018-10-02 10:06:26 +02:00
|
|
|
import * as EthCrypto from "eth-crypto"
|
|
|
|
import EthEcies from "eth-ecies"
|
|
|
|
import * as EthjsUtil from "ethereumjs-util"
|
|
|
|
import JWT from "jsonwebtoken"
|
|
|
|
import Asset from "../models/Asset"
|
|
|
|
import OrderModel from "../models/Order"
|
|
|
|
import Logger from "../utils/Logger"
|
2018-10-09 10:55:53 +02:00
|
|
|
import OceanBase from "./OceanBase"
|
2018-10-02 10:06:26 +02:00
|
|
|
|
|
|
|
declare var fetch
|
2018-10-01 18:10:26 +02:00
|
|
|
|
2018-10-09 10:55:53 +02:00
|
|
|
export default class Order extends OceanBase {
|
2018-10-01 18:10:26 +02:00
|
|
|
|
|
|
|
private static create(asset: Asset, args, key): OrderModel {
|
2018-10-02 10:06:26 +02:00
|
|
|
const accessId = args._id
|
2018-10-10 11:02:00 +02:00
|
|
|
Logger.log(`got new access request id: ${accessId}`)
|
|
|
|
const order: OrderModel = {
|
2018-10-01 18:10:26 +02:00
|
|
|
id: accessId,
|
|
|
|
assetId: asset.assetId,
|
|
|
|
asset,
|
2018-10-10 11:02:00 +02:00
|
|
|
timeout: parseInt(args._timeout, 10),
|
2018-10-01 18:10:26 +02:00
|
|
|
pubkey: args._pubKey,
|
|
|
|
key,
|
2018-10-02 10:06:26 +02:00
|
|
|
} as OrderModel
|
2018-10-10 11:02:00 +02:00
|
|
|
// Logger.log("Created order", order)
|
|
|
|
|
|
|
|
return order
|
2018-10-01 18:10:26 +02:00
|
|
|
}
|
|
|
|
|
2018-10-10 11:02:00 +02:00
|
|
|
public async getOrdersByConsumer(consumerAddress: string): Promise<OrderModel[]> {
|
2018-10-02 10:06:26 +02:00
|
|
|
const {auth, market} = this.keeper
|
2018-10-04 13:14:03 +02:00
|
|
|
|
2018-10-09 10:55:53 +02:00
|
|
|
Logger.log("Getting orders")
|
2018-10-01 18:10:26 +02:00
|
|
|
|
2018-10-09 10:55:53 +02:00
|
|
|
const accessConsentRequestedData = await auth.getEventData(
|
|
|
|
"AccessConsentRequested", {
|
|
|
|
filter: {
|
|
|
|
_consumer: consumerAddress,
|
|
|
|
},
|
|
|
|
fromBlock: 0,
|
|
|
|
toBlock: "latest",
|
|
|
|
})
|
2018-10-04 13:14:03 +02:00
|
|
|
|
|
|
|
const orders = await Promise.all(
|
|
|
|
accessConsentRequestedData
|
2018-10-09 10:55:53 +02:00
|
|
|
.filter((event: any) => {
|
|
|
|
return event.returnValues._consumer === consumerAddress
|
|
|
|
})
|
2018-10-10 11:02:00 +02:00
|
|
|
// todo: this is not orders model maybe? lacking proper typing here
|
2018-10-04 13:14:03 +02:00
|
|
|
.map(async (event: any) => ({
|
2018-10-10 11:02:00 +02:00
|
|
|
id: event.returnValues._id,
|
|
|
|
asset: null,
|
|
|
|
assetId: event.returnValues._resourceId,
|
2018-10-09 10:55:53 +02:00
|
|
|
timeout: parseInt(event.returnValues._timeout, 10),
|
2018-10-10 11:02:00 +02:00
|
|
|
pubkey: null,
|
|
|
|
key: null,
|
2018-10-09 10:55:53 +02:00
|
|
|
status: await auth.getOrderStatus(event.returnValues._id),
|
|
|
|
paid: await market.verifyOrderPayment(event.returnValues._id),
|
2018-10-10 11:02:00 +02:00
|
|
|
} as OrderModel
|
2018-10-04 13:14:03 +02:00
|
|
|
),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
2018-10-09 10:55:53 +02:00
|
|
|
// Logger.log("Got orders:", JSON.stringify(orders, null, 2))
|
|
|
|
Logger.log(`Got ${Object.keys(orders).length} orders`)
|
|
|
|
|
2018-10-02 10:06:26 +02:00
|
|
|
return orders
|
2018-10-01 18:10:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
public async purchaseAsset(asset: Asset, timeout: number, buyerAddress: string): Promise<OrderModel> {
|
2018-10-02 10:06:26 +02:00
|
|
|
const {token, market, auth} = this.keeper
|
2018-10-01 18:10:26 +02:00
|
|
|
|
2018-10-02 10:06:26 +02:00
|
|
|
const key = EthCrypto.createIdentity()
|
|
|
|
const publicKey = EthjsUtil.privateToPublic(key.privateKey).toString("hex")
|
|
|
|
const price = await market.getAssetPrice(asset.assetId)
|
|
|
|
const isValid = await market.isAssetActive(asset.assetId)
|
2018-10-01 18:10:26 +02:00
|
|
|
|
2018-10-02 10:06:26 +02:00
|
|
|
Logger.log("The asset:", asset.assetId, "is it valid?", isValid, "it's price is:", price)
|
2018-10-01 18:10:26 +02:00
|
|
|
|
|
|
|
if (!isValid) {
|
2018-10-10 11:02:00 +02:00
|
|
|
throw new Error("The Asset is not valid!")
|
2018-10-01 18:10:26 +02:00
|
|
|
}
|
|
|
|
try {
|
2018-10-10 11:02:00 +02:00
|
|
|
const marketAddr = market.getAddress()
|
2018-10-01 18:10:26 +02:00
|
|
|
// Allow market contract to transfer funds on the consumer"s behalf
|
2018-10-10 11:02:00 +02:00
|
|
|
await token.approve(marketAddr, price, buyerAddress)
|
|
|
|
Logger.log(`${price} tokens approved on market with id: ${marketAddr}`)
|
2018-10-01 18:10:26 +02:00
|
|
|
} catch (err) {
|
2018-10-09 10:55:53 +02:00
|
|
|
Logger.error("token.approve failed", err)
|
2018-10-01 18:10:26 +02:00
|
|
|
}
|
2018-10-09 10:55:53 +02:00
|
|
|
let order: OrderModel
|
2018-10-01 18:10:26 +02:00
|
|
|
try {
|
|
|
|
// Submit the access request
|
2018-10-09 10:55:53 +02:00
|
|
|
const initiateAccessRequestReceipt = await auth.initiateAccessRequest(asset,
|
|
|
|
publicKey, timeout, buyerAddress)
|
|
|
|
|
|
|
|
const args = initiateAccessRequestReceipt.events.AccessConsentRequested.returnValues
|
2018-10-10 11:02:00 +02:00
|
|
|
Logger.log(`keeper AccessConsentRequested event received on asset: ${asset.assetId}`)
|
2018-10-09 10:55:53 +02:00
|
|
|
order = Order.create(asset, args, key)
|
2018-10-01 18:10:26 +02:00
|
|
|
} catch (err) {
|
2018-10-09 10:55:53 +02:00
|
|
|
Logger.error("auth.initiateAccessRequest failed", err)
|
2018-10-01 18:10:26 +02:00
|
|
|
}
|
2018-10-04 13:14:03 +02:00
|
|
|
|
2018-10-09 10:55:53 +02:00
|
|
|
return order
|
|
|
|
if (false) {
|
|
|
|
// todo: AccessRequestCommitted event is not emitted in this flow
|
2018-10-11 14:16:59 +02:00
|
|
|
await auth.listenToEventOnce(
|
2018-10-09 10:55:53 +02:00
|
|
|
"AccessRequestCommitted", {
|
2018-10-04 13:14:03 +02:00
|
|
|
filter: {
|
|
|
|
_id: order.id,
|
|
|
|
},
|
|
|
|
})
|
2018-10-09 10:55:53 +02:00
|
|
|
.then((accessRequestCommittedResult) => {
|
|
|
|
Logger.log("Got AccessRequestCommitted Event")
|
|
|
|
|
|
|
|
return this.payAsset(asset, accessRequestCommittedResult.returnValues, order, buyerAddress)
|
2018-10-04 13:14:03 +02:00
|
|
|
})
|
2018-10-09 10:55:53 +02:00
|
|
|
.then((payAssetReceipt) => {
|
|
|
|
return auth.listenToEventOnce(
|
|
|
|
"EncryptedTokenPublished", {
|
|
|
|
filter: {
|
|
|
|
_id: order.id,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.then((result) => {
|
|
|
|
Logger.log("Got EncryptedTokenPublished Event")
|
2018-10-01 18:10:26 +02:00
|
|
|
|
2018-10-09 10:55:53 +02:00
|
|
|
return this.finalizePurchaseAsset(
|
|
|
|
result, order, key, buyerAddress,
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
2018-10-01 18:10:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private async payAsset(asset: Asset, args, order, buyerAddress) {
|
2018-10-02 10:06:26 +02:00
|
|
|
const {market} = this.keeper
|
2018-10-01 18:10:26 +02:00
|
|
|
|
|
|
|
// send payment
|
2018-10-02 10:06:26 +02:00
|
|
|
Logger.log("Sending payment: ", order.id, args._id, asset.publisherId, asset.price, order.timeout)
|
|
|
|
return market.payAsset(asset, order, buyerAddress)
|
2018-10-01 18:10:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private async finalizePurchaseAsset(args, order, key, buyerAddress): Promise<OrderModel> {
|
2018-10-02 10:06:26 +02:00
|
|
|
const {auth, web3Helper} = this.keeper
|
2018-10-01 18:10:26 +02:00
|
|
|
|
2018-10-02 10:06:26 +02:00
|
|
|
const encryptedAccessToken = await auth.getEncryptedAccessToken(args._id, buyerAddress)
|
2018-10-01 18:10:26 +02:00
|
|
|
|
|
|
|
// grab the access token from acl contract
|
2018-10-02 10:06:26 +02:00
|
|
|
const tokenNo0x = encryptedAccessToken.slice(2)
|
|
|
|
const encryptedTokenBuffer = Buffer.from(tokenNo0x, "hex")
|
2018-10-01 18:10:26 +02:00
|
|
|
|
2018-10-02 10:06:26 +02:00
|
|
|
const privateKey = key.privateKey.slice(2)
|
|
|
|
const accessTokenEncoded = EthEcies.Decrypt(Buffer.from(privateKey, "hex"), encryptedTokenBuffer)
|
|
|
|
const accessToken = JWT.decode(accessTokenEncoded) // Returns a json object
|
2018-10-01 18:10:26 +02:00
|
|
|
|
|
|
|
// sign it
|
2018-10-02 10:06:26 +02:00
|
|
|
const hexEncrToken = `0x${encryptedTokenBuffer.toString("hex")}`
|
2018-10-01 18:10:26 +02:00
|
|
|
|
2018-10-02 10:06:26 +02:00
|
|
|
const signature = web3Helper.sign(buyerAddress, hexEncrToken)
|
|
|
|
const fixedMsgSha = web3Helper.getWeb3().utils.sha3(encryptedAccessToken)
|
2018-10-01 18:10:26 +02:00
|
|
|
|
|
|
|
// Download the data set from the provider using the url in the access token
|
|
|
|
// decode the access token, grab the service_endpoint, request_id,
|
|
|
|
|
|
|
|
// payload keys: ['consumerId', 'fixed_msg', 'sigEncJWT', 'jwt']
|
|
|
|
const payload = JSON.stringify({
|
|
|
|
consumerId: buyerAddress,
|
|
|
|
fixed_msg: fixedMsgSha,
|
|
|
|
sigEncJWT: signature,
|
|
|
|
jwt: accessTokenEncoded,
|
2018-10-02 10:06:26 +02:00
|
|
|
})
|
2018-10-01 18:10:26 +02:00
|
|
|
const accessUrl = await fetch(`${accessToken.service_endpoint}/${accessToken.resource_id}`, {
|
|
|
|
method: "POST",
|
|
|
|
body: payload,
|
|
|
|
headers: {
|
|
|
|
"Content-type": "application/json",
|
|
|
|
},
|
|
|
|
})
|
|
|
|
.then((response: any) => {
|
|
|
|
if (response.ok) {
|
2018-10-02 10:06:26 +02:00
|
|
|
return response.text()
|
2018-10-01 18:10:26 +02:00
|
|
|
}
|
2018-10-02 10:06:26 +02:00
|
|
|
Logger.log("Failed: ", response.status, response.statusText)
|
2018-10-01 18:10:26 +02:00
|
|
|
})
|
|
|
|
.then((consumptionUrl: string) => {
|
2018-10-02 10:06:26 +02:00
|
|
|
Logger.log("Success accessing consume endpoint: ", consumptionUrl)
|
|
|
|
return consumptionUrl
|
2018-10-01 18:10:26 +02:00
|
|
|
})
|
|
|
|
.catch((error) => {
|
2018-10-02 10:06:26 +02:00
|
|
|
Logger.error("Error fetching the data asset consumption url: ", error)
|
|
|
|
})
|
|
|
|
Logger.log("consume url: ", accessUrl)
|
|
|
|
order.accessUrl = accessUrl
|
2018-10-01 18:10:26 +02:00
|
|
|
|
2018-10-02 10:06:26 +02:00
|
|
|
return order
|
2018-10-01 18:10:26 +02:00
|
|
|
}
|
|
|
|
}
|