Akawa

ETY001的博客

如何删除sasl用户

1
2
3
4
5
db_dump -p /etc/sasldb2 > /tmp/sasldb2.dump
vim /tmp/sasldb2.dump # 找到你要删除的用户,删掉用户和密码即可,也可以修改密码
mv /etc/sasldb2 /etc/sasldb2.bak
db_load -f /tmp/sasldb2.dump /etc/sasldb2
rm -rf /tmp/sasldb2.dump

如何让postfix以TLS方式连接远端服务器发信

/etc/postfix/main.cf 最后增加下面的配置。配置后再给 Gmail 发信,就不会提示不安全连接了。

1
2
smtpd_tls_security_level = may
smtp_tls_security_level = may

另外推一下之前写的《如何快速搭建邮件服务器用来发邮件》https://akawa.ink/2018/01/08/create-a-simple-smtp-server-by-postfix-and-sasl.html

引子

最近真的是跟 Webpack 作上了,又在 Webpack 上花费了宝贵的两天的工作时间。

上次好不容易把 iview-admin 集成进 Laravel,本来以为可以开开心心的开发了,结果在收尾的功能中需要加入富文本编辑器。由于 iview-admin 中集成了一个富文本编辑器,于是就把集成的这个 wangeditor 引入过来使用了。

结果,webpack 对于文件的编译速度瞬间从几秒变成了一分半钟。

WTF!

找原因

Laravel Mix默认的开发环境的编译命令如下:

1
cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js

但是这个坑爹的配置,根本看不出来编译慢的问题出在哪里好吗?!(如下图,加入 wangedit 编辑器后,每次在70%位置就会卡主大约70多秒,甚至更多)

怎么才能找到是具体哪个文件编译慢呢?

最先想到的是看看 webpack.js 的可用参数,最终找到了一个 --profile 参数,加上这个参数后,稍微多了点信息,但是完全不够啊。。。还是没法确认问题在哪啊。。。

接下来的大部分时间都花在了搜索引擎上,主要围绕的搜索关键词是 webpack build slow 之类的。但是完全没有什么用。

又切换了个思路,看看有没有什么好的调试工具。群里有人推荐我 speed-measure-webpack-plugin

看了下说明,是个好插件,可惜我这个项目的 webpack 套在 laravel-mix 里面,我只能通过 laravel-mix 提供的方法来配置 webpack 的参数,而 speed-measure-webpack-plugin 的使用方法是下面这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
把原来的
const webpackConfig = {
plugins: [
new MyPlugin(),
new MyOtherPlugin()
]
}
改为
const SpeedMeasurePlugin = require("speed-measure-webpack-plugin");
const smp = new SpeedMeasurePlugin();
const webpackConfig = smp.wrap({
plugins: [
new MyPlugin(),
new MyOtherPlugin()
]
});

如果我想要用 speed-measure-webpack-plugin 的话,需要在 node_modules 目录下找到 laravel-mix 包,然后修改里面的代码,让 laravel-mixmerge 完用户自定义的配置后,调用 speed-measure-webpack-plugin ,再传给 webpack

我看了下 laravel-mix 的代码,感觉就目前我的水平要想实现的话,需要耗费不止两天的时间吧,这样方向也有点偏。于是这个方案也抛弃了。

柳暗花明

虽然 speed-measure-webpack-plugin 不能用,但是给了我一个启发,那就是有没有可以直接写在 webpackplugins 配置中调用的调试插件呢?

功夫不负有心人,在大半天之后,偶然在 Stack Overflow 的一个问题的回复中看到了 simple-progress-webpack-plugin 这个插件。不过当时心是灰的,因为找了很久没找到,打开这个插件的文档,里面也没有个截图啥的,完全就是死马当活马医,试了试。按照文档提示,先安装 npm install simple-progress-webpack-plugin --save-dev,然后修改 laravel-mix 的配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const SimpleProgressWebpackPlugin = require( 'simple-progress-webpack-plugin' ); // 这是新增的配置

mix.js('resources/js/admin.js', 'public/static')
.sass('resources/sass/admin.scss', 'public/static')
.webpackConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'resources/js/'),
'_c': path.resolve(__dirname, 'resources/js/components'),
},
},
output: {
chunkFilename: 'static/chunks/[name].js',
},
plugins: [
new SimpleProgressWebpackPlugin() // 这是新增的配置
],
})
.babelConfig({
persets: ['@vue/app']
})
.version();

这次打印出来的信息有些用处了,如下图所示

这次知道是在 Optimize modules 阶段速度慢了,具体是在 Module and chunk tree optimization 这里。于是以 webpack optimize modules slow 为关键词搜索,结果第一条就解决了问题:

妈蛋的原来是在异步加载优化这里慢啊。按照 issue 里提到的 babel-plugin-dynamic-import-node 插件,搜索了下,找到安装部署方法 npm install babel-plugin-dynamic-import-node --save-dev,然后配置下 laravel-mix

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const SimpleProgressWebpackPlugin = require( 'simple-progress-webpack-plugin' );

mix.js('resources/js/admin.js', 'public/static')
.sass('resources/sass/admin.scss', 'public/static')
.webpackConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'resources/js/'),
'_c': path.resolve(__dirname, 'resources/js/components'),
},
},
output: {
chunkFilename: 'static/chunks/[name].js',
},
plugins: [
new SimpleProgressWebpackPlugin()
],
})
.babelConfig({
plugins: ['dynamic-import-node'], // 新增配置
})
.version();

再次编译,瞬间完成!!撒花!!

区别

但是看到有提到是在 dev 环境使用 babel-plugin-dynamic-import-node 插件,这是为啥呢?用 requireimport() 有啥区别呢?

初步探索了下发现使用 babel-plugin-dynamic-import-nodeimport() 替换成 require 编译的话,就没有 chunk files 了,相当于是跳过了 code split 之类的阶段吧(我猜是这样)。

可以看下下面的两个截图,第一个是不使用 babel-plugin-dynamic-import-node 进行生产环境 build,第二个是使用的情况进行 build

从最终的文件大小和编译所需要的时间来看,目前不用 chunk 功能,并没有什么问题,于是最终选择使用 babel-plugin-dynamic-import-node 以此来解决编译时间过长的原因。

具体深入进去还有什么区别没时间探索了,目前编译后,使用正常,那就先这样吧。。。

最近需要开发一套后台,挑来挑去,觉得 iview-admin 不错,决定要使用这个后台前端框架。上面就是最终的效果图。

但是由于我们的后台是 Laravel 框架用的 Laravel Mix, 与 iview-admin 用的 vue-cli-service 不一样,于是整合起来有些麻烦,虽然最简单的方法是开两个库独立开来,但是这会让部署什么的增加一些环节,毕竟这个项目开发就我自己,没必要折腾那么多,于是就想要整合。

从周二开始折腾,折腾来折腾去,将近一周过去了,终于搞定了(虽然解决了最后一个问题,但是原理是啥我也不知道),现在已经可以使用 Laravel Mix 来直接替代 vue-cli-service 编译啦。

为了方便自己以后再次使用,所以记录下关键的几个环节。

首先需要的就是调整下 package.json,我的代码最终版如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
{
"private": true,
"scripts": {
"dev": "npm run development",
"development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js",
"watch": "npm run development -- --watch",
"watch-poll": "npm run watch -- --watch-poll",
"hot": "cross-env NODE_ENV=development node_modules/webpack-dev-server/bin/webpack-dev-server.js --inline --hot --config=node_modules/laravel-mix/setup/webpack.config.js",
"prod": "npm run production",
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js"
},
"devDependencies": {
"@vue/babel-preset-app": "^3.5.0",
"axios": "^0.18",
"bootstrap": "^4.0.0",
"chai": "^4.1.2",
"clipboard": "^2.0.0",
"codemirror": "^5.38.0",
"countup": "^1.8.2",
"cropperjs": "^1.2.2",
"cross-env": "^5.1",
"dayjs": "^1.7.7",
"echarts": "^4.0.4",
"html2canvas": "^1.0.0-alpha.12",
"iview": "^3.3.0",
"iview-area": "^1.5.17",
"jquery": "^3.2",
"js-cookie": "^2.2.0",
"laravel-mix": "^4.0.14",
"less": "^2.7.3",
"less-loader": "^4.0.5",
"lodash": "^4.17.5",
"mockjs": "^1.0.1-beta3",
"popper.js": "^1.12",
"resolve-url-loader": "2.3.1",
"sass": "^1.17.2",
"sass-loader": "7.*",
"simplemde": "^1.11.2",
"sortablejs": "^1.7.0",
"tree-table-vue": "^1.1.0",
"v-org-tree": "^1.0.6",
"vue": "^2.5.17",
"vue-i18n": "^7.8.0",
"vue-router": "^3.0.2",
"vue-template-compiler": "^2.5.13",
"vuedraggable": "^2.16.0",
"vuex": "^3.0.1",
"wangeditor": "^3.1.1",
"xlsx": "^0.13.3"
}
}

这里是在 Laravel 原有的 package.json 基础上,把 iview-admin 需要的 依赖 一一手动添加进去的。

其中命令中的 node_modules/webpack/bin/webpack.js 应该是调用的 laravel-mix 包中的依赖。

完成 package.json 之后,我们使用 npm install 安装一下所有的依赖,然后把 iview-admin 项目克隆到本地,把其中的 src 目录下的所有文件复制到 Laravel 项目的 resources/js 目录下。如果你想变更入口名称,可以把 main.js 改成你自己想要的名字,这里我改为了 admin.js

之后修改一下 Laravel 项目根目录下的 webpack.mix.js 文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const mix = require('laravel-mix');

mix.js('resources/js/admin.js', 'public/static')
.sass('resources/sass/admin.scss', 'public/static')
.webpackConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'resources/js/'),
'_c': path.resolve(__dirname, 'resources/js/components'),
},
},
output: {
chunkFilename: 'static/chunks/[name].js',
},
})
.babelConfig({
"presets": [
"@vue/app",
],
})
.version();

在原有的基础上增加了 resolve 配置,为了能够正确解析 @_c 开头的引入包路径。增加了 output 配置,让所有的 chunk 文件与主文件同目录(这条配置可不加)。增加了 babelConfig 配置,为了解决 动态import 语法解析的问题(这个就是目前还没有弄明白的那个地方)。

完成上面的步骤后,就完成了大半了。

之后,在 Laravel 项目中增加一个后台的路由,我是直接加在 routers/web.php 中的,如下

1
Route::get('admin', 'AdminController@index');

创建 AdminController,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class AdminController extends Controller
{
public function index(Request $request) {
return view('admin/index');
}
}

创建视图 resources/view/admin/index.blade.php,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>BACC</title>
<meta name="csrf-token" content="{{ csrf_token() }}" api_token="{{ \Auth::check() ? 'Bearer ' . \Auth::user()->api_token : 'Bearer ' }}">
<link rel="stylesheet" href="{{ mix('static/admin.css') }}">
<!-- <script src="{{ asset('/js/tinymce.min.js') }}"></script> -->
</head>
<body>
<div id="app"></div>

<script src="{{ mix('static/admin.js') }}"></script>
</body>
</html>

最后要修改的就是 iview-admin 的路由模式了。iview-admin 默认使用的是 history,而这个配置将会让 iview-admin 的路由与 Laravel 的路由冲突,因此需要去掉这个配置。这个配置项在 resources/js/router/index.js 中,把下面的代码中的 mode: 'history'去掉即可。

1
2
3
4
const router = new Router({
routes,
mode: 'history'
})

所有配置完成,执行 npm run watch,如果一切顺利的话,所有的前端文件会编译完成,直接访问 http://localhost:8000/admin 就能看到 iview-admin 已经成功整合完成了!

撒花!

最近新开了一个钱包的App项目,直接把之前的代码从 Github 上拖回来,但是执行 cordova prepare -d 一直不成功,显示 current working directory is not a cordova-based project.

经过查看源码,发现在 https://github.com/apache/cordova-lib/blob/master/src/cordova/util.js#L102 这个地方的代码有问题。

正常情况下,这个函数返回 2 就可以了,由于我的 .gitignore 中把 wwwplatforms 两个目录都给忽略了,这就导致了一直返回 0,进而导致另外一个位置获取到的项目目录一直是 /,这显示不是项目根目录。。。

于是手动创建了 wwwplatforms 两个空目录后,一切就正常了。。。。。坑爹。。。。。。

在开发 SteemTools 服务号的文章单独页面的时候,遇到的一个问题就是有些人的图片存放在了被墙的服务器上了,导致文章中的图片无法正常显示。

从网上搜索了下,找到了一个用 node 写的图片代理,我已经 fork 到了自己的库中,https://github.com/ety001/node-image-proxy

为了方便部署和管理,把这个服务封装进了 Docker 中。

运行命令如下:

1
docker run -itd --name node-image-proxy -p 9091:9091 -v /data/node-image-cache:/app/cache --restart always ety001/node-image-proxy

其中 /data/node-image-cache 目录是用来存储缓存的,自己手动建立一个目录就好了。

启动成功后,可以用 nginx 的反向代理来实现 https,也可以直接使用,只需要把要代理的图片地址放到 url 后面就好了,例如这样:

1
https://img.steemtools.top/http://newappaz.oss-cn-hongkong.aliyuncs.com/wherein_images/post/20190224/89a2802d622c4940b2ece51eea04aecd.jpg

这样只需要把服务部署在国外服务器上,然后在解析 markdown 的时候把原来的图片地址加上代理地址后,就可以正常的访问文章中的图片了。

起因

由于最近在开发恒星币的钱包,其中有部分数据,如果直接通过恒星的 API 获取则效率太低,影响页面展示体验。
于是用 hapijs 来开发一些定制的接口,批量获取并缓存数据,来提升页面访问效果。

之前在 《使用 hapijs 快速构建自己的 api 服务》 这篇文章中有写到 hapijs 默认使用 catboxcatbox-memory 组件来缓存数据。

我这次还是按照上次的方法来使用缓存,但是却没有成功,每次提交请求,都是重新去获取数据,而没有走缓存。

祸起文档和配置

翻了好几遍 hapijs 的文档,也使用搜索引擎查了,折腾了3个多小时无果,最终决定一边读源码一边在代码加标记输出。

在每个关键位置都打了输出,但是没有看到任何报错和问题。无意中看到了 hapijs 文档中的一个配置参数 generateOnReadError,作用是当在读取缓存时遇到错误时是否报错,而默认则是关掉的!!!WTF!!!!

配置这个参数为 false 后,得再配合 try {} catch () {} 才显示了错误信息,原来是缓存的 key 非法。

比较这次和上次使用 cache 功能的代码,找到了一个不同点,就是上次是使用的 string 作为 key,而这次使用的是 object 作为 key

不过在 hapijs 关于 cache文档 中,关于 generateFunc 参数的解释里有一句 the \'id\' string or object provided to the \'get()\' method.,看上去 idobject 也没有问题啊。

于是我又去读了 catbox 的源码,最终看到 catbox 的一段关于获取缓存信息的 代码

在第88行,原来特么的只接受 {id: id} 这种样子的对象啊?!!!!并且上面82行的注释也列出来了。。。

又去看了下 catbox 的文档中关于 get() 方法的介绍,有这么一句 id - the unique item identifier (within the policy segment). Can be a string or an object with the required 'id' key.

这就恍然大悟了。。。。。。。。。

原来对象中必须包含 id 这个字段啊,原来要是提交对象的话,需要自己来定义索引啊?!!!

修改下代码,这个坑过了。

不容易不容易啊。。

虽然平时主要用 Shadowsocks,但是架不住有时候没法安装 Shadowsocks 的客户端,那么就还是需要 PPTP VPN 或者 L2TP VPN

最早的时候,是使用的各种一键安装脚本,但是由于系统版本差异,每次需要安装的时候,都要现找可用的一键脚本,太费劲了。于是从网上找了别人封装好的 Docker 镜像,这篇文章总结下,基本上就是一条语句就搞定了。

PPTP VPN

使用的镜像是 mobtitude/vpn-pptp,首先需要把用户名和密码配置一下,打开 /etc/ppp/chap-secrets

1
2
3
# Secrets for authentication using CHAP
# client server secret IP addresses
ety001 * 123456 *

上面的就是配置了一个用户名 ety001 和 密码 123456 的用户,然后执行下面的命令就可以了,

1
docker run -d --name pptp --restart always  --privileged -p 1723:1723 -v /etc/ppp/chap-secrets:/etc/ppp/chap-secrets mobtitude/vpn-pptp

最后检查下 tcp 1723 端口在防火墙上是否打开就可以了。

L2TP + IPSEC VPN

使用的镜像是 hwdsl2/ipsec-vpn-server,需要先配置下用户名、密码和PSK,新建一个环境变量的文件 /etc/l2tp-env,内容如下

1
2
3
VPN_IPSEC_PSK=abcdef
VPN_USER=ety001
VPN_PASSWORD=123456

上面的就是配置了一个用户名 ety001,密码 123456,PSK 为 abcdef 的用户,然后执行下面的命令就可以了,

1
2
3
4
5
6
7
8
9
docker run --name ipsec-vpn-server \
--env-file /etc/l2tp-env \
--restart=always \
-p 500:500/udp \
-p 4500:4500/udp \
-p 1701:1701/udp \
-v /lib/modules:/lib/modules:ro \
-d --privileged \
hwdsl2/ipsec-vpn-server

最后检查下 udp 500udp 4500 端口在防火墙上是否打开就可以了。

Shadowsocks

最后再附带上一个一句话部署 Shadowsocks 的命令,先创建个配置文件 /etc/shadowsocks.json,内容如下

1
2
3
4
5
6
7
8
9
{
"server":"0.0.0.0",
"server_port": 10000,
"local_address":"127.0.0.1",
"local_port":1080,
"password":"ety001",
"timeout":60,
"method":"aes-256-cfb"
}

然后执行下面的命令部署

1
$ docker run -d -p 10000:10000 -v /etc/shadowsocks.json:/conf/shadowsocks.json --restart=always --name ss ety001/ss

最近开始在开发 Cordova + Webpack + Vuejs 架构的 App,开发 Demo 的时候,最麻烦的就是测试了。

由于我是先使用 cordova 的工具创建 cordova 项目,然后用 vue-cli 再在 cordova 中创建 vuejs 项目。
这样在开发的时候,需要先使用 npm run dev 调试 vuejs ,然后开发差不多了,需要调用 cordova 插件的时候,再执行 cordova prepare browsercordova run browser 调试 cordova 插件,每次改完代码还要再执行 npm run build && cordova prepare browser

这样就很烦啊!

为了在正式开发项目前解决这个问题,我研究了萤火钱包的实现和 webpack 的文档。

萤火钱包的实现方式是自己用 Node 写了一个 Web 服务,代码在这里:https://github.com/StellarCN/firefly/blob/master/build/dev-server.js

由于 Webpack 可以使用 require 的方式和 webpack-dev-server 两种方式对代码进行打包,萤火钱包就是在自己写的 Server 中,先引用 webpack 打包代码(24行–41行),然后用 expresscordovajs bridge 提供解析(66行–88行),最后在 package.json https://github.com/StellarCN/firefly/blob/master/package.json#L18 中加入一行代码,完成 cordova prepare 后再启动自己写的 Node 服务器。

按照萤火的思路尝试了大半天,一直都搞不定 webpack 的配置,一直给我报错,如下

1
2
3
4
WebpackOptionsValidationError: Invalid configuration object. Webpack has been initialised using a configuration object that does not match the API schema.
- configuration misses the property 'entry'.
object { <key>: non-empty string | [non-empty string] } | non-empty string | [non-empty string] | function
-> The entry point(s) of the compilation.

最后只得再换个思路,用 webpack-dev-server 再试试。

由于 vuejs 默认就是用 webpack-dev-server 作为测试环境,所以需要改动的文件还是比较少,只需要修改 build/webpack.dev.conf.js 中的配置和 package.json 就好了。

build/webpack.dev.conf.js 中的 devServer 配置项里加上下面的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
before(app){
// serve Cordova javascript and plugins
const cordovaPlatformPath = path.join(__dirname, '../platforms/browser/www')
app.use('/plugins', express.static(path.join(cordovaPlatformPath, 'plugins')))
app.get(
[
'/cordova.js',
'/cordova_plugins.js',
],
function (req, res) {
try {
res.sendFile(path.join(cordovaPlatformPath, req.path))
} catch(err) {
console.log(err)
}
})
// serve Cordova config.xml
app.get('/config.xml',
function(req,res){
try{
res.sendFile(path.join(__dirname, '../'+req.path))
} catch(err){
consol.log(err)
}
})
},

另外再在该文件头部加入 express 包的引用。

通过 devServer.before 加入 cordovajs bridge,这样就可以用 webpack-dev-server 来解析 js bridge 了。

最后在 package.json 中修改下原有的 dev 命令如下:

1
"dev": "cordova prepare browser && cross-env PORT=3000 webpack-dev-server --inline --progress --config build/webpack.dev.conf.js",

这样以后只需要执行 npm run dev 就可以了,只有当用 cordova 安装了新插件的时候,才需要重启下 npm run dev


再插入个小修改。我在 src/main.js 中加入了如下的代码,可以让 cordova js bridge 动态插入进 APP

1
2
3
4
5
6
7
// add cordova.js only if serving the app through file://
if (window.location.protocol === 'file:' || window.location.port === '3000') {
const cordovaScript = document.createElement('script');
cordovaScript.setAttribute('type', 'text/javascript');
cordovaScript.setAttribute('src', 'cordova.js');
document.body.appendChild(cordovaScript);
}

OVER!!

今天在进行demo的联调的时候,微信端的扫一扫相册里一直不显示app下载到本地的二维码。

这个二维码是服务端php生成的,最初以为微信相册的所有照片不显示,是因为格式的问题。

在尝试了更换格式后,发现问题仍然不能解决。

于是找了一张微信能够识别的图片,然后分析它跟我用php生成的图片的 EXIF 信息。

又折腾了大约1–2个小时,把我php生成的图片的 EXIF 调整的跟正常图片一样,发现还是不行。

不是格式的问题,不是 EXIF 的问题,感觉整个人都不好了。。。

想来想去,想了好久,不知道怎么突然想起来文件大小。

可能是我发现我用php生成的图片都是小于4kb的吧。

于是我分别找了一张 21kb+ 的,一张 16kb+ 的,一张 11kb+ 的,一张 6kb+ 的图片。

然后传到手机上,用微信扫一扫相册打开,发现除了那个 6kb+ 的图片识别不出来,其他的都可以。

尼玛啊!!!!!弄了半天原来是文件大小的原因!!!!!!

看来微信在所有图片那个分类下优先显示大于 10kb 的图片啊。。。。

前言

ETH的多签是依靠智能合约实现的,也就是说持币的是智能合约,通过在智能合约里设置条件,来使转账之类的权限通过多人签名来集体控制。

从网上找到了一段简单的多签智能合约,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
pragma solidity ^0.4.21;

contract MultiSigWallet{
address private owner;
mapping (address => uint8) private managers;

modifier isOwner{
require(owner == msg.sender);
_;
}

modifier isManager{
require(
msg.sender == owner || managers[msg.sender] == 1);
_;
}

uint constant MIN_SIGNATURES = 3;
uint private transactionIdx;

struct Transaction {
address from;
address to;
uint amount;
uint8 signatureCount;
mapping (address => uint8) signatures;
}

mapping (uint => Transaction) private transactions;
uint[] private pendingTransactions;

function MultiSigWallet() public{
owner = msg.sender;
}

event DepositFunds(address from, uint amount);
event TransferFunds(address to, uint amount);
event TransactionCreated(
address from,
address to,
uint amount,
uint transactionId
);

function addManager(address manager) public isOwner{
managers[manager] = 1;
}

function removeManager(address manager) public isOwner{
managers[manager] = 0;
}

function () public payable{
emit DepositFunds(msg.sender, msg.value);
}

function withdraw(uint amount) isManager public{
transferTo(msg.sender, amount);
}
function transferTo(address to, uint amount) isManager public{
require(address(this).balance >= amount);
uint transactionId = transactionIdx++;

Transaction memory transaction;
transaction.from = msg.sender;
transaction.to = to;
transaction.amount = amount;
transaction.signatureCount = 0;
transactions[transactionId] = transaction;
pendingTransactions.push(transactionId);
emit TransactionCreated(msg.sender, to, amount, transactionId);
}

function getPendingTransactions() public isManager view returns(uint[]){
return pendingTransactions;
}

function signTransaction(uint transactionId) public isManager{
Transaction storage transaction = transactions[transactionId];
require(0x0 != transaction.from);
require(msg.sender != transaction.from);
require(transaction.signatures[msg.sender]!=1);
transaction.signatures[msg.sender] = 1;
transaction.signatureCount++;

if(transaction.signatureCount >= MIN_SIGNATURES){
require(address(this).balance >= transaction.amount);
transaction.to.transfer(transaction.amount);
emit TransferFunds(transaction.to, transaction.amount);
deleteTransactions(transactionId);
}
}

function deleteTransactions(uint transacionId) public isManager{
uint8 replace = 0;
for(uint i = 0; i< pendingTransactions.length; i++){
if(1==replace){
pendingTransactions[i-1] = pendingTransactions[i];
}else if(transacionId == pendingTransactions[i]){
replace = 1;
}
}
delete pendingTransactions[pendingTransactions.length - 1];
pendingTransactions.length--;
delete transactions[transacionId];
}

function walletBalance() public isManager view returns(uint){
return address(this).balance;
}
}

部署合约

我本地的测试网络是使用 Ganache 搭建的,如下图

我修改了我的 Ganache 的端口为 8545

然后打开 Remix,在右边栏选择 Run,在 Environment 中选择 Web3 Provider,如下图

在弹出的层中,输入本地地址和刚才配置的8545端口,就可以连接到 Ganache 的环境了,如下图

连接成功后,会看到在 Environment 下面的 Account 栏中会列出所有 Ganache 中的测试账号。

我们选择第一个账号作为部署合约的主账号,即合约的 owner

在编辑器中创建一个新的合约文件,把上面的合约代码复制进编辑器,右边栏选择 Compile
Select new compiler version 中选择 0.4.21 版本的编译器,等待编译完成。
完成后,最下面出现绿色的提示信息,如下图

再切换回 Run 标签,点击 Deploy 就可以部署了。
部署成功后,在 Deployed Contracts 中就能看到新部署的合约了,下拉打开可以看到合约的各个接口,如图

增加合约管理员

在右边栏找到合约的 addManager 接口,下拉打开输入一个以太钱包地址,然后点击 transact 即完成添加。
这里我把 Ganache 的第 2 到 4 的地址加入进管理员中。

给合约内转账

切换回 Ganache,在 Transactions 中找到我们刚才部署的合约地址,0x08ea5409922D4204C0E03d17243E77c3810C0CCe

使用任意的其他账号转账任意个 ETH 进合约里。
我用的是 MyEtherWallet进行的转账,只需要把节点设置成本地网络,
然后用私钥模式登陆转账即可,这里就略过不说了。

转完账后,在 RemixRun 标签下,找到合约的 walletBalance 接口,
点击调用即可显示当前合约的余额,注意单位!

多签从合约内转出金额

回到 Remix,在右边栏的 Run 标签下,先确认 Account 选择的是合约的 owner
这个例子里就是第一个测试地址了。

在合约中找到 transferTo,下拉打开,输入一个本地测试网络中的以太钱包地址,输入待转账的金额,点击 transact。这里注意单位,下图为转 5 ETH,

成功执行后,就会有一个等待多签的 transactionpendingTransactions 这个数组里。
点击 getPendingTransactions 可以得到当前等待多签的 transaction 在数组中的索引值。
下图则代表当前等待多签的 transaction 的索引值是 0 。

这时,在 Run 标签页的顶部,把 Account 依次切换为其他几个管理员,分别执行合约的 signTransaction 接口即可完成多签,其中 transactionId 就是上一步中的索引值,如下图

目前这个合约代码中,是当签名管理员的数量大于等于3个,就正式完成多签并发送交易。
所以当第三个管理执行完 signTransaction 后,查一下合约的余额就发现已经转出去 5ETH了。

总结

目前这个例子中的合约代码还是比较简单的,没有各个用户的权重关系。这个就可以根据自己的需要来写合约了。

Over!

0%