{"id":13392490,"url":"https://github.com/IBM-Blockchain-Archive/marbles","last_synced_at":"2025-03-13T18:31:50.774Z","repository":{"id":57561120,"uuid":"51540625","full_name":"IBM-Blockchain-Archive/marbles","owner":"IBM-Blockchain-Archive","description":"WARNING: This repository is no longer maintained ⚠️ This repository will not be updated. The repository will be kept available in read-only mode.","archived":false,"fork":false,"pushed_at":"2019-03-28T03:30:12.000Z","size":44035,"stargazers_count":1024,"open_issues_count":21,"forks_count":978,"subscribers_count":132,"default_branch":"master","last_synced_at":"2024-10-29T20:32:55.745Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/IBM-Blockchain-Archive.png","metadata":{"files":{"readme":"README-cn.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-02-11T19:27:43.000Z","updated_at":"2024-10-07T06:06:07.000Z","dependencies_parsed_at":"2022-09-26T19:31:03.626Z","dependency_job_id":null,"html_url":"https://github.com/IBM-Blockchain-Archive/marbles","commit_stats":null,"previous_names":["ibm-blockchain/marbles"],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IBM-Blockchain-Archive%2Fmarbles","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IBM-Blockchain-Archive%2Fmarbles/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IBM-Blockchain-Archive%2Fmarbles/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IBM-Blockchain-Archive%2Fmarbles/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/IBM-Blockchain-Archive","download_url":"https://codeload.github.com/IBM-Blockchain-Archive/marbles/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243459108,"owners_count":20294340,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-30T17:00:23.327Z","updated_at":"2025-03-13T18:31:50.730Z","avatar_url":"https://github.com/IBM-Blockchain-Archive.png","language":"JavaScript","funding_links":[],"categories":["Uncategorized","Apps","Hyperledger Fabric","JavaScript"],"sub_categories":["Uncategorized","Samples"],"readme":"*阅读本资料的其他语言版本：[English](README.md)。*\n# Marbles 演示\n\n## 关于 Marbles\n- 这个应用程序的基础网络是 [Hyperledger Fabric](https://github.com/hyperledger/fabric/tree/master/docs)，后者是一个 Linux Foundation 项目。  您可能想查阅以下操作说明来稍微了解一下 Hyperledger Fabric。\n- **本演示旨在帮助开发人员了解链代码的基础知识以及如何使用 Fabric 网络开发应用程序。**\n- 这是一个`非常简单`的资产转移演示。多个用户可以创建并相互转移弹珠。\n\n\t![](/doc_images/marbles-peek.gif)\n\n### 版本\n各种版本的 marbles 同时存在。 本版本兼容 **Hyperledger Fabric v1.1x**。 你可以通过检出别的分支来获取别的版本的 marble。\n\n***\n\n# 应用程序背景\n\n请大家集中注意力，这个应用程序将演示如何利用 Hyperledger Fabric 在许多弹珠所有者之间转移弹珠。\n我们将在 Node.js 中使用一些 GoLang 代码完成此任务。\n该应用程序的后端将是在我们的区块链网络中运行的 GoLang 代码。\n从现在开始，这些 GoLang 代码将称为“链代码”或“cc”。\n该链代码本身会创建一颗弹珠，将它存储到链代码状态中。\n该链代码本身可以将数据作为字符串存储在键/值对设置中。\n因此，我们将字符串化 JSON 对象，以便存储更复杂的结构。\n\n弹珠的属性包括：\n\n  1. ID（唯一字符串，将用作键）\n  2. 颜色（字符串，CSS 颜色名称）\n  3. 尺寸（int，以毫米为单位）\n  4. 所有者（字符串）\n\n我们将创建一个用户界面，它可以设置这些值并将它们存储在区块链的账本中。\n弹珠实际上是一个键值对。\n`键`为弹珠 ID，`值`为一个包含（上面列出的）弹珠属性的 JSON 字符串。\n与 cc 的交互是通过对网络上的一个对等节点使用 gRPC 协议来完成的。\ngRPC 协议的细节由一个名为 [Hyperledger Fabric Client](https://www.npmjs.com/package/fabric-client) SDK 的 SDK 处理。\n请查看下图了解拓扑结构细节。\n\n### 应用程序通信流\n\n![](/doc_images/comm_flow.png)\n\n1. 管理员将在他们的浏览器中与我们的 Node.js 应用程序 Marbles 进行交互。\n2. 此客户端 JS 代码将打开一个与后端 Node.js 应用程序的 Websocket 连接。管理员与该站点交互时，客户端 JS 将消息发送到后端。\n3. 读取或写入账本称为提案。这个提案由 Marbles（通过 SDK）构建，然后发送到一个区块链对等节点。\n4. 该对等节点将与它的 Marbles 链代码容器进行通信。链代码将运行/模拟该交易。如果没有问题，它会对该交易进行背书，并将其发回我们的 Marbles 程序。\n5. 然后，Marbles（通过 SDK）将背书后的提案发送到订购服务。  订购方将来自整个网络的许多提案打包到一个区块中。  然后，它将新的区块广播到网络中的对等节点。\n6. 最后，对等节点会验证该区块并将它写入自己的账本中。该交易现在已经生效，所有后续读取都会反映此更改。\n\n***\n\n# Marbles 设置\n\n我既有好消息也有坏消息。\n好消息是，可以根据您的偏好，针对不同的配置来设置 Marbles 和区块链网络。\n坏消息是，这会让操作说明变得很复杂。\n**如果您不熟悉 Hyperledger Fabric 并想要最简单的设置，请关注 :lollipop: 表情符号。**\n只要有选项而且您必须做出自己的选择，我就会在最简单的选项上放一个 :lollipop: 表情符号。\n这是适合您的选项。\n\n### 0.设置本地环境\n\n按照这些环境设置[操作说明](./docs/env_setup.md) 来安装 **Git、Go** 和 **Node.js**。\n\n- 完成上述操作后返回到本教程。开始阅读下一节“下载 Marbles”。\n\n\u003ca name=\"downloadmarbles\"\u003e\u003c/a\u003e\n\n### 1.下载 Marbles\n我们需要将 Marbles 下载到本地系统。\n让我们使用 Git 通过克隆此存储库来完成该任务。\n即使您计划将 Marbles 托管在 IBM Cloud 中，也需要执行这一步。\n\n- 打开一个命令提示符/终端并浏览到您想要的工作目录\n- 运行以下命令：\n\n\t```\n\tgit clone https://github.com/IBM-Blockchain/marbles.git --depth 1\n\tcd marbles\t\n\t```\n\n- 非常棒，第 2 步再见。\n\n\u003ca name=\"getnetwork\"\u003e\u003c/a\u003e\n\n### 2.获取一个网络\n\n我们又见面了。现在我们需要一个区块链网络。\n\n**选择下面的一个选项：**\n\n- **选项 1：** 使用 IBM Cloud IBM Blockchain 服务创建一个网络 - [操作说明](./docs/use_bluemix_hyperledger.md)\n- **选项 2：**:lollipop: 使用本地托管的 Hyperledger Fabric 网络 - [操作说明](./docs/use_local_hyperledger.md)\n\n\u003ca name=\"installchaincode\"\u003e\u003c/a\u003e\n\n### 3.安装并实例化链代码\n\n很好，就快要完成了！现在，我们需要运行我们的 Marbles 链代码。\n请记住，链代码是一个关键组件，它最终会在账本上创建我们的 Marbles 事务。\n该链代码是需要安装在对等节点上，然后在一个通道上实例化的 GoLang 代码。\n已为您编写好该代码！\n我们只需要运行它。\n可通过两种方式运行它。\n\n**仅**选择与您的设置相关的选项：\n\n- **选项 1：** 使用 IBM Blockchain 服务安装并实例化链代码 - [操作说明](./docs/install_chaincode.md)\n- **选项 2：**:lollipop: 在本地使用 SDK 安装并实例化链代码 - [操作说明](./docs/install_chaincode_locally.md)\n\n\u003ca name=\"hostmarbles\"\u003e\u003c/a\u003e\n\n### 4.托管 Marbles\n\n最后但同样重要的是，我们需要在某个地方运行 Marbles。\n\n**选择下面的一个选项：**\n\n- **选项 1：** 将 Marbles 托管在 IBM Cloud 上 - [操作说明](./docs/host_marbles_bluemix.md)\n- **选项 2：**:lollipop: 在本地托管 Marbles - [操作说明](./docs/host_marbles_locally.md)\n\n***\n\n\u003ca name=\"use\"\u003e\u003c/a\u003e\n\n# 使用 Marbles\n\n1. 如果您到达这一步，那么您应该已经设置了环境，创建了区块链网络，而且 Marbles 应用程序和链代码正在运行。对吧？如果不对，您可以寻求一些帮助（从网页中寻找，而不是从字面上寻找）。\n2. 打开最喜欢的浏览器，浏览到 [http://localhost:3001](http://localhost:3001) 或您的 IBM Cloud www 路径。\n    - 如果未加载该站点，请检查您的节点控制台日志，查看 Marbles 使用的主机名/IP 和端口。\n\n3. 最后，我们可以测试该应用程序。单击“United Marbles”部分中一个用户上的“+”图标\n\n\t![](/doc_images/use_marbles1.png)\n\n4. 填写所有字段，然后单击“CREATE”按钮\n5. 几秒过后，您的新 Marbles 应该就会出现。\n    - 如果未出现，请单击浏览器中的 Refresh 按钮或按 F5 来刷新该页面\n6. 接下来，让我们来交易一颗弹珠。  将一颗弹珠从一位所有者拖到另一位所有者。仅在您拥有多个弹珠公司时，才能与“United Marbles”内的所有者交易它。该弹珠应该会暂时消失，然后在新所有者中重新绘制出来。这意味着交易成功了！\n    - 如果未看见该弹珠，请刷新页面\n7. 现在将一颗弹珠拖放到垃圾桶来删除它。它应该在几秒后消失。\n\n\t![](/doc_images/use_marbles2.png)\n\n8. 刷新页面，以便再次确认您的操作“停顿了”。\n9. 使用搜索框过滤弹珠所有者或弹珠公司名称。  在有许多公司/所有者时，此方法很有帮助。\n    - 别针图标会阻止搜索框过滤掉该用户。\n10. 现在让我们来执行具体的演练。单击页面顶部附近的“Settings”按钮。\n\t- 这将打开一个对话框。\n\t- 单击“Enabled”按钮启用 Story Mode\n\t- 单击右上角的“x”关闭菜单。\n\t- 现在挑选另一颗弹珠，并将它拖到另一个用户。  您会看到交易流程的分解结构。希望这能让您更好地了解 Fabric 的工作原理。\n\t- 请记住，当故事的剧情不断重复，而且您对自己的过往已失去兴趣时，可以禁用 Story Mode。\n11. 恭喜您有了一个工作正常的 Marbles 应用程序 :)！\n\n\n# 区块链背景\n在介绍 Marbles 的工作原理之前，让我们讨论一下 Hyperledger Fabric 的流和拓扑结构。\n让我们先来了解一些定义。\n\n### 定义：\n\n**对等节点** - 对等节点是区块链的成员，运行着 Hyperledger Fabric。在 Marbles 的上下文中，对等节点归我的弹珠公司所有和操作。\n\n**CA** - CA（证书颁发机构）负责守卫我们的区块链网络。它将为客户端（比如我们的 Marbles node.js 应用程序）提供交易证书。\n\n**订购者** - 订购者或订购服务是区块链网络的成员，其主要职责是将交易打包到区块中。\n\n**用户** - 用户是经过授权能与区块链进行交互的实体。在 Marbles 的上下文中，用户是我们的管理员。用户可以查询和写入账本。\n\n**区块** - 区块包含交易和一个验证完整性的哈希值。\n\n**交易**或**提案** - 它们表示与区块链账本的交互。对账本的读取或写入请求是以交易/提案的形式发送的。\n\n**账本** - 这是区块链在一个对等节点上的存储区。它包含由交易参数和键值对组成的实际的区块数据。它由链代码编写。\n\n**链代码** - 链代码是代表智能合约的 Hyperledger Fabric。它定义资产和所有关于资产的规则。\n\n**资产** - 资产是存在于账本中的实体。它是一种键值对。在 Marbles 的上下文中，资产是一颗弹珠或弹珠所有者。\n\n让我们看看创建一颗新的弹珠时涉及的操作。\n\n1. Marbles 中发生的第一件事是向网络的 `CA` 注册我们的管理员`用户`。如果成功，`CA` 会向 Marbles 发送注册证书，SDK 将该证书存储在我们的本地文件系统中。\n2. 管理员从用户界面创建一颗新弹珠时，SDK 会创建一个调用事务。\n3. 创建弹珠的事务被构建为一个调用链代码函数 `init_marble()` 的`提案`。\n4. Marbles（通过 SDK）将此`提案`发送到一个`对等节点`进行背书。\n5. `对等节点`将运行 Go 函数 `init_marble()` 来模拟该事务，并记录它尝试写入账本中的所有更改。\n6. 如果该函数成功返回，`对等节点`会对该`提案`进行背书并将它发回给 Marbles。如果失败，错误也将发送回来，但不会对`提案`进行背书。\n7. 然后，Marbles（通过 SDK）将背书后的`提案`发送给`订购者`。\n8. `订购者`将组织来自整个网络的`提案`的序列。它将通过查找相互冲突的交易，检查该交易序列是否有效。任何由于冲突而无法添加到区块中的交易都被标记为错误。`订购者`将新区块广播到网络中的对等节点。\n9. 我们的`对等节点`将收到新区块，并通过查看各种签名和哈希值来验证它。最终将该区块提交到`对等节点`的`账本`。\n10. 此刻，我们的账本中会出现新的弹珠，并很快会出现在所有对等节点的账本中。\n\n\n# SDK 深入剖析\n现在让我们看看如何连接到 Fabric Client SDK。\n几乎所有配置选项都可以在“连接概要文件”（即 cp）中找到。\n您的连接概要文件可能来自诸如 `/config/blockchain_creds_tls.json` 之类的文件，也可能来自一个环境变量。\n如果您不确定它来自何处，可以在弹珠启动时检查日志。\n您会看到 `Loaded connection profile from an environmental variable` 或 `Loaded connection profile file \u003csome name here\u003e`。\ncp 为 JSON，它包含我们的区块链网络中的各种组件的主机名（或 IP）和端口。\n包含在 `./utils` 文件夹中的 `connection_profile_lib` 提供了为 SDK 检索数据的函数。\n\n### 配置 SDK：\n第一个操作是注册管理员。  查看用于注册的以下代码段。  代码下方有一些注释/操作说明。\n\n```js\n//enroll admin\nenrollment.enroll = function (options, cb) {\n// [Step 1]\n    var client = new FabricClient();\n    var channel = client.newChannel(options.channel_id);\n    logger.info('[fcw] Going to enroll for mspId ', options);\n\n// [Step 2]\n    // Make eCert kvs (Key Value Store)\n    FabricClient.newDefaultKeyValueStore({\n        path: path.join(os.homedir(), '.hfc-key-store/' + options.uuid) //store eCert in the kvs directory\n    }).then(function (store) {\n        client.setStateStore(store);\n\n// [Step 3]\n        return getSubmitter(client, options);              //do most of the work here\n    }).then(function (submitter) {\n\n// [Step 4]\n        channel.addOrderer(new Orderer(options.orderer_url, options.orderer_tls_opts));\n\n// [Step 5]\n        channel.addPeer(new Peer(options.peer_urls[0], options.peer_tls_opts));\n        logger.debug('added peer', options.peer_urls[0]);\n\n// [Step 6]\n        // --- Success --- //\n        logger.debug('[fcw] Successfully got enrollment ' + options.uuid);\n        if (cb) cb(null, { channel: channel, submitter: submitter });\n        return;\n\n    }).catch(\n\n        // --- Failure --- //\n        function (err) {\n            logger.error('[fcw] Failed to get enrollment ' + options.uuid, err.stack ? err.stack : err);\n            var formatted = common.format_error_msg(err);\n            if (cb) cb(formatted);\n            return;\n        }\n    );\n};\n```\n\n第 1 步.该代码做的第一件事是创建我们的 SDK 的一个实例。\n\n第 2 步.接下来，我们使用 `newDefaultKeyValueStore` 创建一个键值存储来存储我们的注册证书。\n\n第 3 步.接下来注册我们的管理员。我们在执行这一步时使用了我们的注册 ID 和注册密钥向 CA 执行身份验证。CA 将颁发注册证书，SDK 将该证书存储在键值存储中。因为我们使用的是默认的键值存储，所以它会存储在本地文件系统中。\n\n第 4 步.成功注册后，我们将设置订购者 URL。  暂时不需要订购者，但在我们尝试调用链代码时需要它。\n    - 仅在拥有自签名证书时，才需要包含 `ssl-target-name-override` 的业务。将此字段与您创建 PEM 文件时使用的`常用名`设置为相同。\n\n第 5 步.接下来设置对等节点 URL。这些 URL 也是暂时不需要的，但我们将会完整设置我们的 SDK 链对象。\n\n第 6 步.此刻，已对 SDK 进行全面配置并准备好与区块链进行交互。\n\n### 代码结构\n此应用程序有 3 个编码环境需要处理。\n\n1.链代码部分 - 这是在区块链网络上运行的/包含对等节点的 GoLang 代码。也称为 `cc`。所有弹珠/区块链交易最终都会在这里进行。这些文件位于 `/chaincode` 中。\n\n2.**客户端** JS 部分 - 这是在用户浏览器中运行的 JavaScript 代码。用户界面交互在这里执行。这些文件位于 `/public/js` 中。\n\n3.**服务器端** JS 部分 - 这是运行应用程序的后端的 JavaScript 代码，即为 Marbles 的核心的 `Node.js` 代码！有时该代码也称为我们的`节点`或`服务器`代码。它充当弹珠管理员与我们的区块链之间的连接器。这些文件位于 `/utils` 和 `/routes` 中。\n\n请记住，这 3 部分是相互隔离的。\n它们不共享变量和函数。\n它们将通过 gRPC、WebSockets 或 HTTP 等网络协议进行通信。\n\n# Marbles 深入剖析\n希望您已在用户之间成功交易了一两颗弹珠。\n让我们看看如何完成弹珠的转移，首先看看链代码。\n\n__/chaincode/marbles.go__\n\n```go\n    type Marble struct {\n        ObjectType string        `json:\"docType\"`\n        Id       string          `json:\"id\"`\n        Color      string        `json:\"color\"`\n        Size       int           `json:\"size\"`\n        Owner      OwnerRelation `json:\"owner\"`\n    }\n```\n\n__/chaincode/write_ledger.go__\n\n```go\n    func set_owner(stub shim.ChaincodeStubInterface, args []string) pb.Response {\n        var err error\n        fmt.Println(\"starting set_owner\")\n\n        // this is quirky\n        // todo - get the \"company that authed the transfer\" from the certificate instead of an argument\n        // should be possible since we can now add attributes to the enrollment cert\n        // as is.. this is a bit broken (security wise), but it's much much easier to demo! holding off for demos sake\n\n        if len(args) != 3 {\n            return shim.Error(\"Incorrect number of arguments.Expecting 3\")\n        }\n\n        // input sanitation\n        err = sanitize_arguments(args)\n        if err != nil {\n            return shim.Error(err.Error())\n        }\n\n        var marble_id = args[0]\n        var new_owner_id = args[1]\n        var authed_by_company = args[2]\n        fmt.Println(marble_id + \"-\u003e\" + new_owner_id + \" - |\" + authed_by_company)\n\n        // check if user already exists\n        owner, err := get_owner(stub, new_owner_id)\n        if err != nil {\n            return shim.Error(\"This owner does not exist - \" + new_owner_id)\n        }\n\n        // get marble's current state\n        marbleAsBytes, err := stub.GetState(marble_id)\n        if err != nil {\n            return shim.Error(\"Failed to get marble\")\n        }\n        res := Marble{}\n        json.Unmarshal(marbleAsBytes, \u0026res)           //un stringify it aka JSON.parse()\n\n        // check authorizing company\n        if res.Owner.Company != authed_by_company{\n            return shim.Error(\"The company '\" + authed_by_company + \"' cannot authorize transfers for '\" + res.Owner.Company + \"'.\")\n        }\n\n        // transfer the marble\n        res.Owner.Id = new_owner_id                   //change the owner\n        res.Owner.Username = owner.Username\n        res.Owner.Company = owner.Company\n        jsonAsBytes, _ := json.Marshal(res)           //convert to array of bytes\n        err = stub.PutState(args[0], jsonAsBytes)     //rewrite the marble with id as key\n        if err != nil {\n            return shim.Error(err.Error())\n        }\n\n        fmt.Println(\"- end set owner\")\n        return shim.Success(nil)\n    }\n```\n\n这个 `set_owner()` 函数将更改特定弹珠的所有者。\n它接受一个字符串输入参数数组，如果成功，则返回 `nil`。\n在该数组内，第一个索引应拥有一颗弹珠的 ID，该 ID 也是键/值对中的键。\n我们首先需要使用此 ID 检索当前的弹珠构造。\n这是使用 `stub.GetState(marble_id)` 完成的，然后使用 `json.Unmarshal(marbleAsBytes, \u0026res)` 将它解组为一种弹珠结构。\n从这里，我们可以使用 `res.Owner.Id` 检索该结构，并使用新所有者 ID 覆盖该弹珠的所有者。\n接下来，我们将对该结构进行编组，以便可以使用 `stub.PutState()` 通过新属性覆盖该弹珠。\n\n让我们更进一步，看看如何从我们的 node.js 应用程序中调用此链代码。\n\n__/utils/websocket_server_side.js__\n\n```js\n    //process web socket messages\n    ws_server.process_msg = function (ws, data) {\n        const channel = cp.getFirstChannelId();\n        const first_peer = cp.getFirstPeerName(channel);\n        var options = {\n            peer_urls: [cp.getPeersUrl(first_peer)],\n            ws: ws,\n            endorsed_hook: endorse_hook,\n            ordered_hook: orderer_hook\n        };\n        if (marbles_lib === null) {\n            logger.error('marbles lib is null...');             //can't run in this state\n            return;\n        }\n\n        // create a new marble\n        if (data.type == 'create') {\n            logger.info('[ws] create marbles req');\n            options.args = {\n                color: data.color,\n                size: data.size,\n                marble_owner: data.username,\n                owners_company: data.company,\n                owner_id: data.owner_id,\n                auth_company: process.env.marble_company,\n            };\n\n            marbles_lib.create_a_marble(options, function (err, resp) {\n                if (err != null) send_err(err, data);\n                else options.ws.send(JSON.stringify({ msg: 'tx_step', state: 'finished' }));\n            });\n        }\n\n        // transfer a marble\n        else if (data.type == 'transfer_marble') {\n            logger.info('[ws] transferring req');\n            options.args = {\n                marble_id: data.id,\n                owner_id: data.owner_id,\n                auth_company: process.env.marble_company\n            };\n\n            marbles_lib.set_marble_owner(options, function (err, resp) {\n                if (err != null) send_err(err, data);\n                else options.ws.send(JSON.stringify({ msg: 'tx_step', state: 'finished' }));\n            });\n        }\n        ...\n```\n\n这段 `process_msg()` 代码接收所有 Websocket 消息（该代码可在 app.js 中找到）。\n它将检测发送了何种类型的 ws (websocket) 消息。\n在我们的示例中，它会检测到一个 `transfer_marble` 类型。\n查看该代码，我们可以看到它将设置一个 `options` 变量，然后启动 `marbles_lib.set_marble_owner()`。\n该函数将告诉 SDK 构建提案并处理转移操作。\n\n接下来，让我们看看该函数。\n\n__/utils/marbles_cc_lib.js__\n\n```js\n    //-------------------------------------------------------------------\n    // Set Marble Owner\n    //-------------------------------------------------------------------\n    marbles_chaincode.set_marble_owner = function (options, cb) {\n        console.log('');\n        logger.info('Setting marble owner...');\n\n        var opts = {\n            peer_urls: g_options.peer_urls,\n            peer_tls_opts: g_options.peer_tls_opts,\n            channel_id: g_options.channel_id,\n            chaincode_id: g_options.chaincode_id,\n            chaincode_version: g_options.chaincode_version,\n            event_urls: g_options.event_urls,\n            endorsed_hook: options.endorsed_hook,\n            ordered_hook: options.ordered_hook,\n            cc_function: 'set_owner',\n            cc_args: [\n                options.args.marble_id,\n                options.args.owner_id,\n                options.args.auth_company\n            ],\n        };\n        fcw.invoke_chaincode(enrollObj, opts, cb);\n    };\n        ...\n```\n\n上面列出了 `set_marble_owner()` 函数。\n重要的部分是，它通过 `fcn: 'set_owner'` 行将提案的调用函数名称设置为 \"set_owner\"。\n请注意，我们在注册管理员时已经设置了对等节点和订购者 URL。\n默认情况下，SDK 将此交易发送到所有通过 `channel.addPeer` 添加的节点。\n在我们的示例中，SDK 仅发送到 1 个对等节点，因为我们只添加了 1 个对等节点。\n我记得此对等节点是在 `enrollment` 这一节中添加的。\n\n现在让我们更进一步，看看如何从用户界面发送此 Websocket 消息。\n\n__/public/js/ui_building.js__\n\n```js\n    $('.innerMarbleWrap').droppable({drop:\n        function( event, ui ) {\n            var marble_id = $(ui.draggable).attr('id');\n\n            //  ------------ Delete Marble ------------ //\n            if($(event.target).attr('id') === 'trashbin'){\n                // [removed code for brevity]\n            }\n\n            //  ------------ Transfer Marble ------------ //\n            else{\n                var dragged_owner_id = $(ui.draggable).attr('owner_id');\n                var dropped_owner_id = $(event.target).parents('.marblesWrap').attr('owner_id');\n\n                console.log('dropped a marble', dragged_owner_id, dropped_owner_id);\n                if (dragged_owner_id != dropped_owner_id) {\n                $(ui.draggable).addClass('invalid bounce');\n                    transfer_marble(marble_id, dropped_owner_id);\n                    return true;\n                }\n            }\n        }\n    });\n\n    ...\n\n    function transfer_marble(marbleName, to_username, to_company){\n        show_tx_step({ state: 'building_proposal' }, function () {\n            var obj = {\n                type: 'transfer_marble',\n                id: marbleId,\n                owner_id: to_owner_id,\n                v: 1\n            };\n            console.log(wsTxt + ' sending transfer marble msg', obj);\n            ws.send(JSON.stringify(obj));\n            refreshHomePanel();\n        });\n    }\n```\n\n在引用 `$('.innerMarbleWrap')` 的第一节中，可以看到我们使用了 jQuery 和 jQuery-UI 来实现拖放功能。\n通过此代码，我们获得了一个可拖放的事件触发器。\n我们用了大量代码来解析已拖入的弹珠和它拖放到的用户的细节。\n\n触发该事件时，我们首先检查这颗弹珠实际上是否已更换了所有者，或者是否只是将它选出又放回去。\n如果它的所有者改变了，我们将转到 `transfer_marble()` 函数。\n此函数创建一条包含所有需要的数据的 JSON 消息，并通过 `ws.send()` 使用我们的 Websocket 发送它。\n\n最后一个问题是 Marbles 如何认识到转移已完成。\nMarbles 将定期检查所有弹珠并将它们与最后已知状态进行比较。\n如果存在差异，则将新弹珠状态广播到所有连接的 JS 客户端。\n这些客户端会收到此 Websocket 消息并重新绘制该弹珠。\n\n现在您已知道了整个流程。\n管理员转移弹珠，JS 检测到拖/放操作，客户端发送一条 Websocket 消息，Marbles 收到该 Websocket 消息，SDK 构建/发送一个提案，对等节点对提案进行背书，SDK 将提案发送给订购服务，订购者订购并向对等节点发送一个区块，我们的对等节点提交该区块，Marbles node 代码定期获取新的弹珠状态，将弹珠 Websocket 消息发送到客户端，最后客户端会在弹珠的新所有者那里重新绘制它。\n\n大功告成！希望您能愉快地交易弹珠。\n\n# Marbles 常见问题\n您是否对 Marbles 中某项功能的运行方式存在疑问？  或者，对如何运行某项功能存在疑问？请查阅[常见问题](./docs/faq.md)。\n\n# 反馈\n非常期待收到您的反馈。\n这个演示专为与您类似的人而构建，而且会继续为了满足读者的需求而不断完善。\n老实说，您对它的评价如何？\n如果您有关于如何改进该演示/教程的想法，请告诉我！\n具体来讲：\n\n- 自述文件的格式是否适合您？\n- 您对哪些地方感到困惑？\n- 某项功能的运行是否中断！？\n- 学完该教程后，您的知识是否得到了扩充？\n- 是否存在特别难懂的地方？\n- 该演示是否给您造成了存在危机，让您突然不确定它的含义？\n\n请在 [GitHub 问题](https://github.com/IBM-Blockchain/marbles/issues) 部分交流任何改进/错误和难点！\n\n# 贡献\n如果您想要帮助改进该演示，请查阅[贡献指南](./CONTRIBUTING.md)\n\n# 许可\n[Apache 2.0](LICENSE)\n\n***\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FIBM-Blockchain-Archive%2Fmarbles","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FIBM-Blockchain-Archive%2Fmarbles","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FIBM-Blockchain-Archive%2Fmarbles/lists"}