作为Chainlink黑客马拉松的特等奖获得者,智能合约开发者Harry和Matt使用Chainlink外部适配器将特斯拉车辆API连接到Chainlink预言机,用于点对点车辆租赁应用。他们的特斯拉智能合约是一个说明Chainlink用于连接链外API和智能合约的很好的例子,并实现全新的商业模式。
作为Chainlink黑客马拉松的特等奖获得者,智能合约开发者Harry Papacharissiou和Matt Durkin使用Chainlink外部适配器将特斯拉车辆API连接到Chainlink预言机,用于点对点车辆租赁应用。他们的特斯拉智能合约是一个说明Chainlink用于连接链外API和智能合约的很好的例子,并实现全新的商业模式。在这篇文章中,Harry和Matt将介绍他们是如何创建实现的。
作者:Harry Papacharissiou和Matt Durkin
Chainlink的外部适配器功能可以轻松地将智能合约连接到任何API,实现智能合约触发链外事件的各种用例,并将防篡改的数字协议带到外部系统。
特斯拉公司生产了一系列创新的电动汽车,配备了技术先进的功能和特性。其中一项就是丰富的API,可以为经过认证的客户端提供丰富的车辆数据,以及远程访问并执行车辆上各种状态变化的功能。
通过外部适配器和Chainlink节点来调用API,特斯拉智能合约可以与特斯拉车辆完全集成,这就开辟了几个独特的用例。
在这篇技术文章中,我们将通过:
如车辆数据API所示,特斯拉官方移动应用可以让特斯拉车主获得车辆位置、里程表读数、车辆电池充电状态等数据。这款移动应用还允许用户执行各种远程命令,如锁定和解锁车辆、远程启动车辆、打开和关闭充电口、设置限速等,另外还有很多特斯拉远程命令列表中列出的内容。
这款移动应用使用REST API连接到特斯拉的服务器,而服务器则与每辆车进行通信。在撰写本文的时候,特斯拉还没有向车主发布任何API的官方文档,但社区开发者已经通过逆向工程的方式制作了非官方文档。目前,社区已经在几个第三方应用中应用了这些API,比如这个自带的数据记录器。
特斯拉API使用OAuth标准进行身份验证,在成功请求到验证端点后,API会授予访问令牌。向 API 连续发出的需要身份验证的请求时,需要在请求头中包含身份验证令牌,前提是该令牌尚未过期或被撤销。特斯拉API生成的访问令牌有一个长达45天的有效期,并且每次认证请求都会生成一个寿命较长的刷新令牌。如果访问令牌接近到期或过期,我们也可以申请新的访问令牌。
在与特斯拉汽车进行通信之前,必须首先通过向身份验证 API 端点发出 HTTP POST 请求,成功获取其中一个身份验证令牌。使用下面请求体中的参数来完成这一任务。将电子邮件和密码设置为特斯拉车辆账户所有者在特斯拉网站上的登录名。
{
"grant_type": "password",
"client_id": "81527cff06843c8634fdc09e8ac0abefb46ac849f38fe1e431c2ef2106796384",
"client_secret": "c7257eb71a564034f9419ee651c7d0e5f7aa6bfbd18bafb5c5c033b093bb2fa3",
"email": "teslaowner@gmail.com",
"password": "password"
}
你会收到一个含有访问令牌的回复:
{
"access_token": "bc031af9351deb7a33e92f689be9eaad4b840e98b49f050a5e951347f140493d",
"token_type": "bearer",
"expires_in": 3888000,
"refresh_token": "77bfff0afe006b7093d7ee23e85d3c667d36c23181e1e938a049237c35aba19c",
"created_at": 1598934492
}
一旦你有了一个有效的认证令牌,你就需要通过在请求头中传递这个认证令牌到所需的API端点来找出你的车辆ID:
curl -X POST https://owner-api.teslamotors.com/api/1/vehicles -H "content-type:application/json" -H "Authorization: Bearer bc031af9351deb7a33e92f689be9eaad4b840e98b49f050a5e951347f140493d"
所需的车辆ID将在响应'id_s'元素中返回。这是特斯拉服务器将成功验证的车辆ID。其他'id'和'vehicle_id'字段用于其他目的,不适用于网络服务请求。
{
"response": [
{
"id": 42555797050350370,
"vehicle_id": 1832501921,
"vin": "5YJ3F7EB1KF447777",
"display_name": "BASED",
"option_codes": "AD15,MDL3,PBSB,RENA,BT37,ID3W,RF3G,S3PB,DRLH,DV2W,W39B,APF0,COUS,BC3B,CH07,PC30,FC3P,FG31,GLFR,HL31,HM31,IL31,LTPB,MR31,FM3B,RS3H,SA3P,STCP,SC04,SU3C,T3CA,TW00,TM00,UT3P,WR00,AU3P,APH3,AF00,ZCST,MI00,CDM0",
"color": null,
"access_type": "OWNER",
"tokens": [
"7e6c201b322e9a43",
"3487a30d7e9ff6ec"
],
"state": "asleep",
"in_service": false,
"id_s": "42555797050350366",
"calendar_enabled": true,
"api_version": 10,
"backseat_token": null,
"backseat_token_updated_at": null,
"vehicle_config": null
}
],
"count": 1
}
在上面这个例子中,验证令牌bc031af9351deb7a33e92f689be9eaad4b840e98b49f050a5e951347f140493d和车辆ID 42555797050350366都将在后续对车辆的API调用中使用。
作为Chainlink黑客马拉松2020获奖作品Link My Ride的一部分,我们创建了一个外部适配器,将Chainlink节点连接到特斯拉API的特定端点,以促进车主和租车人之间的点对点车辆租赁协议。
这个外部适配器现在已经在Chainlink市场列出,其他开发者可以使用、修改或扩展。
一旦你从Github上下载了外部适配器的代码,并按照说明让它运行,你就可以将外部适配器添加到你的Chainlink 节点中,然后创建一个使用它的Job Specification。如果你需要帮助设置Chainlink节点,你可以查看这个文档。
这个示例Job Specification寻找来自特定预言机合约地址的传入请求,将请求传递给外部适配器,然后将结果返回给智能合约。
{
"initiators": [
{
"type": "runlog",
"params": {
"address": "0x05c8fadf1798437c143683e665800d58a42b6e19"
}
}
],
"tasks": [
{
"type": "tesla-external-adapter",
"confirmations": null,
"params": {
}
},
{
"type": "ethbytes32",
"confirmations": null,
"params": {
}
},
{
"type": "ethtx",
"confirmations": null,
"params": {
}
}
],
"startAt": null,
"endAt": null
}
如果你无法访问特斯拉车辆,但仍然想玩玩外部适配器,你可以使用下面的一个无服务接口(serverless function)。这个目前指向一个仿真特斯拉服务器端点来模拟真实的特斯拉服务器响应。
https://australia-southeast1-link-my-ride.cloudfunctions.net/Tesla-External-Adapter
如上所述,认证令牌对车辆的请求进行认证。将这些令牌暴露在链上是一种安全风险,因为它们控制着对车辆的访问,并可用于确定车辆的确切位置。因此,我们需要一个解决方案,以确保认证令牌可以保留和使用,但永远不会暴露在链上被其他人可以看到。
如果你只需要将一辆车集成到你的智能合约中,那么最简单的解决方案就是将认证令牌存储在适配器运行的主机上作为环境变量。你可以在构建外部适配器指南中找到一个示范。但是如果需要为多辆汽车存储和使用多个身份验证令牌怎么办?
在这种情况下,外部适配器需要存储和检索多个键/值对。键是每辆车的车辆ID或一些独特的标识符,而值是身份验证令牌。在外部适配器中存储和使用多个键/值对有很多解决方案。最创新的解决方案之一是使用云端的无服务 NoSQL数据存储认证令牌。如果您还将外部适配器作为无服务计算(serverless function)在您的云提供商上运行,您的外部适配器就会成为一个真正的无服务器、高可用和可扩展的混合区块链/云计算功能。
该外部适配器使用谷歌云的Firestore NoSQL文档数据库来支持存储和检索多个车辆认证代币。要设置Firestore数据库,请遵循这个指导。如果你没有谷歌云账户,你可以注册一个免费账户。
一旦您的Firestore数据库设置完毕,您就可以为外部适配器设置所需的环境变量,然后按照外部适配器文档中的说明启动它。
一旦外部适配器和Firestore数据库运行完毕,在进入智能合约之前的最后一步就是对车辆进行认证。认证过程是适配器获取车辆的特定信息,用这些信息连接到特斯拉服务器,然后将给定的车辆ID和认证令牌作为新的键/值对存储在Firestore数据库中,最后返回一个成功消息。从这一点来看,对给定车辆ID的任何请求都不需要认证令牌。外部适配器将在需要时从Firestore数据库中获取它。
要执行这一步,以下面的格式向外部适配器URL发出HTTP POST请求。在本例中,job ID 是 534ea675a9524e8e834585b00368b178;我们将在向 Tesla 服务器发出的请求中使用车辆 ID 和 apiToken 字段。认证操作告诉适配器对给定的车辆详细信息进行认证,如果凭证有效,它就会将车辆详细信息存储在Firestore数据库中。
curl -X POST -H "content-type:application/json" "http://localhost:8080/" --data '{ "id": 534ea675a9524e8e834585b00368b178, "data": { "apiToken": "81527cff06843c8634fdc09e8ac0abefb46ac849f38fe1e431c2ef2106796384", "vehicleId": "42555797050350777", "action": "authenticate" } }'
我们可以通过REST客户端手动提出这个请求,也可以直接通过web应用提出,或者如果适配器只允许从特定的Chainlink节点进行连接,那么你可以通过web发起的job specification来发起,如下图所示。在这个例子中,认证请求进入Chainlink节点,Chainlink节点将其转发到外部适配器(包含所有参数),然后外部适配器将结果发送到智能合约中的链上函数。
{
"initiators": [
{
"type": "web",
"params": {
}
}
],
"tasks": [
{
"type": "tesla-external-adapter",
"confirmations": null,
"params": {
}
},
{
"type": "ethtx",
"confirmations": 0,
"params": {
"address": "0x1EF45689050F47BAc80Df38A10a199bb26af0f6b",
"functionSelector": "approveVehicle(address)"
}
}
],
"startAt": null,
"endAt": null
}
一旦外部适配器运行并对车辆进行了认证,我们就需要采取适当的措施来确保对适配器的访问安全。我们可以在适配器内部和外部采取更多的安全措施,以确保只有经过授权的Chainlink节点或进程才能访问调用外部适配器。你可以在适配器本身通过白名单来实现。如果适配器在云环境中作为无服务函数计算运行,您可以在那里配置安全和角色访问。
现在,我们正在运行一个外部适配器,我们已经将其添加到Chainlink节点作业规范(job specification)中,并且我们还安全地存储了车辆的验证验证令牌。接下来,我们可以创建一个智能合约来对车辆进行操作,同时,获取车辆的位置、里程表和充电水平的数据。
第一步是创建一个新的API消费者合约,根据所选择的以太坊网络设置所有所需参数。
您应该在合同中创建两个函数:"unlockVehicle "和 "unlockVehicleCallback",如下面的例子。调用unlockVehicle函数与特斯拉车辆进行交互。
unlockVehicle函数将车辆ID和job ID作为参数。这应该是前面在特斯拉外部适配器部分提到的第一个作业规范(Job Specification)的ID。我们将LINK支付金额设置为0.1LINK。下面是我们的Solidity例子,通过我们的Chainlink 预言机进行HTTP POST请求。
function unlockVehicle(string _vehicleId, bytes32 _jobId) external {
Chainlink.Request memory req = buildChainlinkRequest(jobId, address(this), this.unlockVehicleCallback.selector);
req.add("apiToken", "");
req.add("vehicleId", _vehicleId);
req.add("action", "unlock");
sendChainlinkRequestTo(chainlinkOracleAddress(), req, 0.1 * 1 ether);
}
如果调用特斯拉服务器成功,车辆就会解锁车门,并返回一条成功消息和一个JSON对象,其中包含车辆里程表、充电等级百分比和位置坐标。
{
"jobRunID": 134ea675a9524e8e231585b00368b178,
"data": "{50000,55,-35008518,138575206}",
"result": null,
"statusCode": 200
}
这个响应数据将被返回到unlockVehicleCallback函数,我们可以手动提取每个值进行链上存储。
function unlockVehicleCallback(bytes32 _requestId, bytes32 _vehicleData) public recordChainlinkFulfillment(_requestId) {
//first split the results into individual strings based on the delimiter
var s = bytes32ToString(_vehicleData).toSlice();
var delim = ",".toSlice();
//store each string in an array
string[] memory splitResults = new string[](s.count(delim)+ 1);
for (uint i = 0; i < splitResults.length; i++) {
splitResults[i] = s.split(delim).toString();
}
//Now for each one, convert to uint
odometer = stringToUint(splitResults[0]);
chargeState = stringToUint(splitResults[1]);
tmpLongitude = stringToUint(splitResults[2]);
tmpLatitude = stringToUint(splitResults[3]);
//Now store location coordinates in signed variables. Will always be positive, but will check in the next step if need to make negative
vehicleLongitude = int(tmpLongitude);
vehicleLatitude = int(tmpLatitude);
//Finally, check first bye in the string for the location variables. If it was a '-', then multiply location coordinate by -1
//first get the first byte of each location coordinate string
longitudeBytes = bytes(splitResults[2]);
latitudeBytes = bytes(splitResults[3]);
//First check longitude
if (uint(longitudeBytes[0]) == 0x2d) {
//first byte was a '-', multiply result by -1
vehicleLongitude = vehicleLongitude * -1;
}
//Now check latitude
if (uint(latitudeBytes[0]) == 0x2d) {
//first byte was a '-', multiply result by -1
vehicleLatitude = vehicleLatitude * -1;
}
//Emit an event with the vehicle data
emit vehicleDetails(odometer,chargeState,vehicleLongitude,vehicleLatitude);
}
上述合约的完整版本可以在GitHub上获得,或者你可以使用易于部署的Remix链接。这个实现目前连接到一个模拟的特斯拉服务器,用于开发和测试目的。为了将其修改为生产环境并连接到实际的特斯拉车辆,需要将作业规范(job specification)更新为运行在指向真实特斯拉生产服务器的外部适配器上。
利用Chainlink网络及其多功能的外部适配器功能,我们已经演示了如何将智能合约与特斯拉车辆进行整合。通过整合,智能合约可以完全访问特斯拉丰富的车辆数据集,并能够远程执行车辆上的所有各种操作。
这一演示为智能合约和车辆集成开辟了许多令人兴奋的潜在用例,例如点对点车辆租赁,正如我们的Chainlink黑客马拉松2020获奖作品Link My Ride所展示的那样。其他用例可能包括短期的按次使用车辆登记或数据驱动的车辆保险,实时适应驾驶员的行为。随着我们快速迈向一个拥有自主车辆的世界,想象一下在无人驾驶车辆上预订和出行会变得更容易,由一个高度安全、确定性的智能合约管理车主和客户之间的协议和交易。
如果你是一名开发者,并希望将你的智能合约连接到底层区块链之外的现有数据和基础设施,请在这里联系或访问开发者文档。
Website | Twitter | Discord | Reddit | YouTube | Telegram | Events | GitHub | Price Feeds | DeFi
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!