Zhuang's Diary

言之有物,持之以恒

我相信每个人都会想,每次更新完代码,更新完配置文件后,就直接这么 ctrl+c 真的没问题吗,ctrl+c到底做了些什么事情呢?

本文我们讨论 ctrl+c 背后的信号以及如何优雅的重启服务,以及对 HTTP 服务进行热更新。

ctrl + c

在终端执行特定的组合键可以使系统发送特定的信号给此进程,完成一系列的动作

命令 信号 含义
ctrl + c SIGINT 强制进程结束
ctrl + z SIGTSTP 任务中断,进程挂起
ctrl + \ SIGQUIT 进程结束 和 dump core
ctrl + d EOF
常用于重启、重新加载进程 SIGHUP 若程序中没有捕捉该信号,当收到该信号时,进程就会退出
SIGPIPE 在进程往一个已经关闭的管道写数据时会产生

因此在我们执行ctrl + c关闭服务端时,会强制进程结束,导致正在访问的用户等出现问题

常见的 kill -9 pid 会发送 SIGKILL 信号给进程,也是类似的结果。

信号

本段中出现信号是什么呢?

信号是 Unix 、类 Unix 以及其他 POSIX 兼容的操作系统中进程间通讯的一种有限制的方式。

它是一种异步的通知机制,用来提醒进程一个事件(硬件异常、程序执行异常、外部发出信号)已经发生。当一个信号发送给一个进程,操作系统中断了进程正常的控制流程。此时,任何非原子操作都将被中断。如果进程定义了信号的处理函数,那么它将被执行,否则就执行默认的处理函数。

所有信号

从go语言 go root SDK中 zerrors_linux_amd64.go 来看,关于信号有如下常量:

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
SIGABRT   
SIGALRM
SIGBUS
SIGCHLD
SIGCLD
SIGCONT
SIGFPE
SIGHUP
SIGILL
SIGINT
SIGIO
SIGIOT
SIGKILL
SIGPIPE
SIGPOLL
SIGPROF
SIGPWR
SIGQUIT
SIGSEGV
SIGSTKFLT
SIGSTOP
SIGSYS
SIGTERM
SIGTRAP
SIGTSTP
SIGTTIN
SIGTTOU
SIGUNUSED
SIGURG
SIGUSR1
SIGUSR2
SIGVTALRM
SIGWINCH
SIGXCPU
SIGXFSZ

怎样算优雅

优雅地退出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// TrapSignal catches the SIGTERM/SIGINT/SIGKILL and executes cb function. 
// After that it exits with code 0.
func TrapSignal(cb func()) {
c := make(chan os.Signal, 1)
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM, syscall.SIGKILL)
go func() {
for sig := range c {
fmt.Sprintf("captured %v, exiting...", sig)
if cb != nil {
cb()
}
os.Exit(0)
}
}()
}

在接收退出信号SIGTERM/SIGINT/SIGKILL之后,系统退出os.Exit(0)之前,执行cb() callback函数。具体执行方法建议如下:

1
2
3
4
5
6
7
8
9
10
11
func Cmd(ctx *cli.Context) error {
// program of business logic
start()

TrapSignal(logger, func() {
// program of business exit here
stop()
})

return nil
}

gin退出

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
engine := gin.Default()
engine.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
TrapSignal(func() {
fmt.Println("before exit.")
})

engine.Run()
}

以下列表均为Ethereum token相关的提案:

EIP1080 这个是一种可以恢复的 token,其诞生的主要原因是因为以太坊的DAO之前的币被盗用才创建这个ip的,可以从合约层面恢复被盗或丢失的账户,这也减轻合理可证明的令牌或资产损失或盗窃的影响,并帮助解决其他冲突。不应该因为丢失、盗窃或冲突而修改Ethereum的协议,但是可以在智能契约层解决这些问题。其中定义的接口有一个抽象方法叫做claimLost,我在想这个东西是否可以利用在一些类似于财产险的场景中

EIP1132 可以对ERC20进行时间锁定,这就使得在一些交易过程中不需要将ERC20转给其他第三方的资金托管智能合约,因为这种托管的方式你需要额外的信任这个托管合约,而且在转账给托管合约的时候会造成gas的消耗

EIP998 是一种针对721和20以及223的扩展,使721token能够拥有其他721token和20token,使20和223能够由721令牌拥有,该规范涵盖了四种不同类型的可组合,998721(20)自顶向下的可组合token,用于接收、持有和传输721(20)token,998721(20)自底向上的可组合token,它们将自己附加到其他721token上,还包含一个rootowner的概念,这种token组合是一个树形的结构

EIP1175 是一种区块链电商的解决方案,通过提供一个center,这个center面向buyer能够快速建立一个钱包,面向saler可以快速建立一个商店,而且能够创建自己商店的结算erc20token。经分析可以使用在积分,代金券,优惠券等场景中

EIP902 token validator 提供token所有权和传输验证的服务协议,服务于监管机构的KYC和AML验证,因为监管机构需要将诸如身份等的非链合规信息与链上服务联系起来,通过一个共享的白名单而不是每个token都建立自己的白名单

EIP1207 类似于OAuth经过第三方授权

EIP1261 成员资格验证令牌 MVT 依然属于KYC的范畴,比如一些许可证的签发,例如在一些区块链的反洗钱应用,需要把一些地址和成员资格关联起来,达到监管的目的,也存在一种场景,就是我们自己发行的erc20token我们希望只能在我们许可的会员间传递

EIP1450 给了我一个启发就是合规和监管对应的要求应该从合约层面解决,仅仅是对原有的ERC20加了一些modifier就可以满足监管和法律的要求,ERC-1450必须防止任何人执行转移、允许和批准功能和/或实现这些功能,否则总是会失败,ERC-1450更新了transferFrom、mint和burnFrom函数。转让方、铸币方和burnFrom只能由RTA执行,且受限于唯一发布的转让方修改人。此外,ERC-1450还定义了transferOwnership、setTransferAgent、setPhysicalAddressOfOperation和isTransferAgent等函数。只有发行者可以调用transferOwnership、setTransferAgent和setPhysicalAddressOfOperation函数。任何人都可以调用isTransferAgent函数。

EIP1462 基本的安全token 这些要求包括KYC(了解您的客户)和AML(反洗钱)规则,以及为账户锁定令牌并限制其因法律纠纷而转移的能力。还可以附加附加的法律文档,以便在令牌和off-chain法律实体之间建立双重绑定关系。这个草案对EIP1400和EIP1450都不太认同,因为这两个草案都包含一些特定的场景,是比较小众的需求,1411和1410无法支持777,1066将引入一个转移检查功能,这包含四个新函数,用于检查所提供的输入是否允许操作

EIP1523 保单721标准,提供了一个jsonSchema,这个jsonSchema类似一个Interface接口,包含符合该标准的一些属性和需要实现的方法

EIP1948 对721的一个升级,因为有需求希望NFT用例能够与一个动态数据进行关联,该token可以在其生命周期内更改这个动态数据,如果有了这个,区块链电子病历和电子保单就能拥有动态的可修改的数据了

EIP1996 可持有的token,这里指的是这个token可以被hold,而不是以前的token只能被own,这就意味着第三方可以hold同一笔token而不是own他,这位监管提供了一个很好的方案,持有指定了付款人、收款人、最高金额、公证员和到期日。在创建持有时,将暂停支付方的指定令牌余额。持有余额在执行或释放之前不能转移。持有只能由公证人执行,这将触发令牌从付款人向收款人的转移。如果在任何时候由公证员或在期满后由任何人解除持有,则不进行任何转让,并且支付人可以再次获得该金额。在一些业务下,必须在事先不知道确切金额的情况下保证付款,比如酒店可以暂停客人的账户,作为对任何可能的额外服务(如客房服务)的担保。当客人结账离开时,持有部分被执行,剩余的金额仍然在客人的账户上。而在其他一些业务情况下,在使用其服务之前必须保证支付。例如:当入住酒店时,酒店会hold客人的账户,以确保在交钥匙之前有足够的余额支付房间费用。

EIP2018 可清除的token,其角色包含一个清算代理,操作员,开证申请人,清算过程将资金转移的承诺转化为资金从一个账户到另一个账户的实际流动。清算代理决定是否可以执行转移。应当转让的数额不能从支付人的余额中扣除,也不能用于其他转让,因此,保证了转让的执行是成功的。一个受管制的令牌需要符合所有的法律要求,特别是KYC和AML。其中一些检查可能无法在链上完成,因此传输可能无法在一个步骤中完成。目前还没有EIP来实现这样的外链检查。这个提议允许用户订购一个转移,可以由一个链外清算代理进行检查。根据它的结果,清算代理将执行或取消转移。要提供有关为何取消转账的更多信息,清算代理可以添加未执行转账的原因。

EIP2019 可资助的令牌,ERC-20标准令牌的扩展,允许令牌钱包所有者通过调用智能契约并附加一个资金指令字符串来请求为钱包提供资金。其角色包含一个令牌钱包的主人,象征性的合约所有者/代理人,开证申请人,令牌钱包所有者(或经过批准的地址)可以通过区块链订购令牌化请求。这是通过调用orderFund或orderFundFrom方法来完成的,这些方法启动令牌契约操作符的工作流,以支持或拒绝资金请求。在这种情况下,在提交请求时提供资金指令,操作人员使用这些指令来确定要借记的资金来源,以便(通过minting)为令牌钱包提供资金。一般而言,在区块链上逐字逐句地放置用于借记资金的显式路由指令是不可取的,建议使用私有通信替代方法,如私有通道、加密存储或类似的方法(在区块链分类帐之外)来这样做。另一种(不太理想的)可能性是将这些指令以加密的形式放在指令字段中。现在,大多数的令牌发行/资金请求,基于任何基于菲亚特的支付方法,需要一个以前的集中交易,能够获得所需的令牌发行请求者的钱包。为了尝试将所有需要的步骤引入到分散化中,公开令牌生命周期和支付事务的所有需要的步骤,一个资金请求可以允许钱包所有者通过区块链发起资金请求。关键好处:资金和付款的可追溯性得到了加强,从而使入账成为可能。所有的付款状态可以存储在链。几乎所有的货币/令牌生命周期都是通过一种分散的方法来覆盖的,并辅之以在生态系统中普遍使用的私有通信。

EIP2020 电子货币标准,如今,金融机构使用电子系统,这些系统在核心银行系统的数据库中保存账户余额。为了让一家机构能够保留客户账户余额的记录,并将其提供给客户,该机构必须在一个已知的法律框架下进行监管,而且必须拥有这样做的许可证。在监管监督下维持许可证,需要确保合规(即对所有客户执行KYC,并在允许交易前确保良好的“反洗钱”操作),并通过定期审计证明其技术和运营偿付能力,以便向机构存放资金的客户可以放心,他们的资金是安全的。

EIP2021 可支付令牌,令牌钱包的所有者(或批准的地址)可以通过区块链订购支付请求。这是通过调用orderPayoutFrom或orderPayoutFrom方法来完成的,这些方法启动令牌契约操作符的工作流,以支持或拒绝支付请求。在这种情况下,在提交请求时提供支付指令,操作人员使用这些指令来确定资金的目的地。通常,不建议在区块链上逐字逐句地为支出设置显式路由指令,建议使用私有通信替代方法,例如私有通道、加密存储或类似的方法(在区块链分类账之外)。另一种(不太理想的)可能性是将这些指令以加密的形式放在指令字段中。

IP地址介绍

IP地址由两个部分组成,net-id和host-id,即网络号和主机号。
net-id:表示ip地址所在的网络号。
host-id:表示ip地址所在网络中的某个主机号码。

即:

1
IP-address ::=  { <Network-number>, <Host-number> }

IP地址分类

IP地址一共分为5类,即A~E,它们分类的依据是其net-id所占的字节长度以及网络号前几位。

  • A类地址:网络号占1个字节。

    网络号的第一位固定为0。

  • B类地址:

    网络号占2个字节。

    网络号的前两位固定为10。

  • C类地址:

    网络号占3个字节。

    网络号的前三位固定位110。

  • D类地址:

    前四位是1110,用于多播(multicast),即一对多通信。

  • E类地址:

    前四位是1111,保留为以后使用。

其中,ABC三类地址为单播地址(unicast),用于一对一通信,是最常用的。

特殊IP地址

就是用来做一些特殊的事情。RFC1700中定义了以下特殊IP地址。

  1. {0,0}:网络号和主机号都全部为0,表示“本网络上的本主机”,只能用作源地址。
  2. {0,host-id}:本网络上的某台主机。只能用作源地址。
  3. {-1,-1}:表示网络号和主机号的所有位上都是1(二进制),用于本网络上的广播,只能用作目的地址,发到该地址的数据包不能转发到源地址所在网络之外。
  4. {net-id,-1}:直接广播到指定的网络上。只能用作目的地址。
  5. {net-id,subnet-id,-1}:直接广播到指定网络的指定子网络上。只用作目的地址。
  6. {net-id,-1,-1}:直接广播到指定网络的所有子网络上。只能用作目的地址。
  7. {127,}:即网络号为127的任意ip地址。都是内部主机回环地址(loopback),永远都不能出现在主机外部的网络中。

127.0.0.1和0.0.0.0地址的区别是什么?

我们先来看下共同点:

  1. 都属于特殊地址。
  2. 都属于A类地址。
  3. 都是IPV4地址。

0.0.0.0

IPV4中,0.0.0.0地址被用于表示一个无效的,未知的或者不可用的目标。

  • 在服务器中,0.0.0.0指的是本机上的所有IPV4地址,如果一个主机有两个IP地址,192.168.1.1 和 10.1.2.1,并且该主机上的一个服务监听的地址是0.0.0.0,那么通过两个ip地址都能够访问该服务。
  • 在路由中,0.0.0.0表示的是默认路由,即当路由表中没有找到完全匹配的路由的时候所对应的路由。

用途

  • 当一台主机还没有被分配一个IP地址的时候,用于表示主机本身。(DHCP分配IP地址的时候)
  • 用作默认路由,表示”任意IPV4主机”。用来表示目标机器不可用。
  • 用作服务端,表示本机上的任意IPV4地址。

127.0.0.1

127.0.0.1属于{127,}集合中的一个,而所有网络号为127的地址都被称之为回环地址,它们是包含关系,即回环地址包含127.0.0.1。
回环地址:所有发往该类地址的数据包都应该被loop back。

用途

  • 回环测试,通过使用ping 127.0.0.1 测试某台机器上的网络设备,操作系统或者TCP/IP实现是否工作正常。
  • DDos攻击防御:网站收到DDos攻击之后,将域名A记录到127.0.0.1,即让攻击者自己攻击自己。
  • 大部分Web容器测试的时候绑定的本机地址。

localhost

相比127.0.0.1,localhost具有更多的意义。localhost是个域名,而不是一个ip地址。之所以我们经常把localhost与127.0.0.1认为是同一个是因为我们使用的大多数电脑上都讲localhost指向了127.0.0.1这个地址。
在ubuntu系统中,/ets/hosts文件中都会有如下内容:

1
2
3
4
5
6
7
8
127.0.0.1   localhost
127.0.1.1 jason-Lenovo-V3000
# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters

上面第一行是几乎每台电脑上都会有的默认配置。
但是localhost的意义并不局限于127.0.0.1。

localhost是一个域名,用于指代this computer或者this host,可以用它来获取运行在本机上的网络服务。
在大多数系统中,localhost被指向了IPV4的127.0.0.1和IPV6的::1。

1
2
127.0.0.1    localhost
::1 localhost

所以,在使用的时候要注意确认IPV4还是IPV6

总结

127.0.0.1 是一个环回地址。并不表示“本机”。0.0.0.0才是真正表示“本网络中的本机”。
在实际应用中,一般我们在服务端绑定端口的时候可以选择绑定到0.0.0.0,这样我的服务访问方就可以通过我的多个ip地址访问我的服务。

比如我有一台服务器,一个外网地址A,一个内网地址B,如果我绑定的端口指定了0.0.0.0,那么通过内网地址或外网地址都可以访问我的应用。但是如果我只绑定了内网地址,那么通过外网地址就不能访问。所以如果绑定0.0.0.0,也有一定安全隐患,对于只需要内网访问的服务,可以只绑定内网地址。

简介

ERC777保持与 ERC20 兼容。

操作员通常利用合约发送token,并具备发送 / 接收钩子,以使得持有者对其token拥有更多的控制权。

它利用 ERC1820 来了解在合同和用户地址收到token时是否以及在何处通知它们。

解决的问题(相对于ERC20的优点)

  1. 同样使用 send(dest, value, data) 发送Ether
  2. 使用 tokensToSend 钩子,合约和用户地址都可以控制和拒绝发送出去的token
  3. 使用 tokensReceived 钩子,合约和用户地址都可以控制和拒绝接收到的token
  4. tokensReceived 钩子允许发送token到合约地址,并在交易中心发出通知。而ERC20需要approve/transferFrom 两次调用才能达到这样的效果
  5. token持有人可以指定/去除操作员(合约)地址,这些操作员通常是已经验证过的合约contract,例如交易所,支票处理器或自动收费系统
  6. 每一笔交易含有 dataoperatorData ,它们是bytes字段,在token持有人和操作员之间传递信息
  7. tokensReceived 钩子函数可以通过代理合约来部署,所以ERC777是与ERC20钱包兼容的

构建ERC777合约

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pragma solidity ^0.5.0;

import "@openzeppelin/contracts/token/ERC777/ERC777.sol";

contract GLDToken is ERC777 {
constructor(
uint256 initialSupply,
address[] memory defaultOperators
)
ERC777("Gold", "GLD", defaultOperators)
public
{
_mint(msg.sender, msg.sender, initialSupply, "", "");
}
}

ERC777扩展了ERC20,对ERC20提供兼容性支持。使用 _mintinitialSupply 分配给部署者帐户。与ERC20的_mint不同,此参数包含一些额外的参数,但是可以放心地忽略它们。

initialSupply 没有设定小数。ERC777规定小数始终返回固定值18,因此无需自行设置。

最后,我们需要设置defaultOperators:操作员帐户(通常是智能合约地址),该帐户将能够代表其持有者转让token。如果不打算在token中使用操作员(合约),则只需传递一个空数组即可。

基本的ERC777合约就是这样! 现在,对其进行部署,并使用相同的balanceOf方法来查询部署者的余额:

1
2
> GLDToken.balanceOf(deployerAddress)
1000

要将token从一个帐户转移到另一个帐户,既可以使用ERC20的“transfer”方法,也可以使用新的ERC777的“send”方法,其作用非常相似,但是增加了一个可选的data字段:

1
2
3
4
5
6
> GLDToken.transfer(otherAddress, 300)
> GLDToken.send(otherAddress, 300, "")
> GLDToken.balanceOf(otherAddress)
600
> GLDToken.balanceOf(deployerAddress)
400

ERC777接口定义如下:

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
interface ERC777Token {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function totalSupply() external view returns (uint256);
function balanceOf(address holder) external view returns (uint256);

// 定义代币最小的划分粒度。
// 要求必须在创建时设定,之后不可以更改,不管是在铸币、发送还是销毁操作的代币数量,必需是粒度的整数倍
function granularity() external view returns (uint256);

// 操作员 相关的操作(操作员是可以代表持有者发送和销毁代币的账号地址)
function defaultOperators() external view returns (address[] memory);
function isOperatorFor(
address operator,
address holder
) external view returns (bool);
// 设置一个地址作为 msg.sender 的操作员,需要触发 AuthorizedOperator 事件
function authorizeOperator(address operator) external;
// 移除 msg.sender 上 operator 操作员的权限, 需要触发 RevokedOperator 事件
function revokeOperator(address operator) external;

// 发送代币
function send(address to, uint256 amount, bytes calldata data) external;
function operatorSend(
address from,
address to,
uint256 amount,
bytes calldata data,
bytes calldata operatorData
) external;

// 销毁代币
function burn(uint256 amount, bytes calldata data) external;
function operatorBurn(
address from,
uint256 amount,
bytes calldata data,
bytes calldata operatorData
) external;

// 发送代币事件
event Sent(
address indexed operator,
address indexed from,
address indexed to,
uint256 amount,
bytes data,
bytes operatorData
);

// 铸币事件
event Minted(
address indexed operator,
address indexed to,
uint256 amount,
bytes data,
bytes operatorData
);

// 销毁代币事件
event Burned(
address indexed operator,
address indexed from,
uint256 amount,
bytes data,
bytes operatorData
);

// 授权操作员事件
event AuthorizedOperator(
address indexed operator,
address indexed holder
);

// 撤销操作员事件
event RevokedOperator(address indexed operator, address indexed holder);
}

钩子函数如何工作

首先来了解一下 ERC1820, ERC1820是一个全局的合约,在以太坊链上有一个唯一的合约地址,它总是 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24

ERC1820 合约提过了两个主要接口:

  • setInterfaceImplementer(address _addr, bytes32 _interfaceHash, address _implementer)
    用来设置地址(_addr)的接口(_interfaceHash 接口名称的 keccak256 )由哪个合约实现(_implementer)。
  • getInterfaceImplementer(address _addr, bytes32 _interfaceHash) external view returns (address)
    这个函数用来查询地址(_addr)的接口由哪个合约实现。

ERC777 使用 send 转账时,会分别在持有者和接收者地址上使用 ERC1820 的 getInterfaceImplementer 函数进行查询,查看是否有对应的实现合约,ERC777 标准规范里预定了接口及函数名称,如果有实现则进行相应的调用。

ERC777 使用 operatorSend 转账时,可以通过参数operatorData携带操作者的信息,发送代币除了执行对应账户的余额加减和触发事件之外,还有额外的规定

  1. 如果持有者有通过 ERC1820 注册 ERC777TokensSender 实现接口,代币合约必须调用其 tokensToSend 钩子函数。
  2. 如果接收者有通过 ERC1820 注册 ERC777TokensRecipient 实现接口, 代币合约必须调用其 tokensReceived 钩子函数。
  3. 如果有 tokensToSend 钩子函数,必须在修改余额状态之前调用。
  4. 如果有 tokensReceived 钩子函数,必须在修改余额状态之后调用。
  5. 调用钩子函数及触发事件时,dataoperatorData必须原样传递,因为 tokensToSend 和 tokensReceived 函数可能根据这个数据取消转账(触发 revert)。

从而保证了ERC777交易以及钩子函数的原子性。

参考链接 ==>

https://eips.ethereum.org/EIPS/eip-777

https://github.com/0xjac/ERC777

https://docs.openzeppelin.com/contracts/2.x/erc777

https://github.com/OpenZeppelin/openzeppelin-contracts

go1.5.2 version

step 1. init

image-20201015105955243

–home /home/zhuang/Documents/melody-bk/script/run/data init –melody_parameters=”–datadir /home/zhuang/Documents/melody-bk/script/run/data init /home/zhuang/Documents/melody-bk/script/etc/genesis.template”

step 1. start

–home
/home/zhuang/Documents/melody/script/run/data
–consensus.create_empty_blocks=false
node
–melody_parameters=”–datadir /home/zhuang/Documents/melody/script/run/data –http –http.addr=”0.0.0.0” –http.port=”8545” –ws –ws.addr=”0.0.0.0” –ws.port=”8546” –verbosity 3 –http.api eth,net,web3,personal,admin –tendermint_addr=tcp://0.0.0.0:26657 –abci_laddr=tcp://127.0.0.1”