区块链相关见解资料摘录

概念

区块链简史


区块链能做什么

数字货币

比特币、莱特币、以太币等

区块链应用

供应链管理、防伪、溯源、信息共享、社会诚信等

游戏

CryptoKitties(加密猫)、 ETHERBOTS(以太机器人)、 EtherCraft(以太飞船)等

早期应用

1990 年 8 月,Bellcore(1984 年由 AT&T 拆分而来的研究机构)的 Stuart Haber 和 W. Scott Stornetta 在论文《How to Time-Stamp a Digital Document》中就提出利用链式结构来解决防篡改问题,其中新生成的时间证明需要包括之前证明的 Hash 值。这可以被认为是区块链结构的最早雏形。

后来,2005 年 7 月,在 Git 等开源软件中,也使用了类似区块链结构的机制来记录提交历史。

区块链结构最早的大规模应用出现在 2009 年初上线的比特币项目中。

在无集中式管理的情况下,比特币网络持续稳定,支持了海量的交易记录,并且从未出现严重的漏洞,引发了广泛关注。这些都与区块链结构自身强校验的特性密切相关。

区块链是什么

区块链是分布式数据存储、点对点传输、共识机制、加密算法等计算机技术的新型应用模式。

  • 1.狭义来讲:区块链是一种按照时间顺序将数据区块以顺序相连的方式组合成的一种链式数据结构,并以密码学方式保证的不可篡改和不可伪造的分布式账本。
  • 2.广义来讲:区块链技术是利用块链式数据结构来验证与存储数据、利用分布式节点共识算法来生成和更新数据、利用密码学的方式保证数据传输和访问的安全、利用由自动化脚本代码组成的智能合约来编程和操作数据的一种全新的分布式基础架构与计算方式。

区块与交易的细节


基本原理

区块链的基本原理理解起来并不复杂。首先来看三个基本概念:

  • 交易(Transaction):一次对账本的操作,导致账本状态的一次改变,如添加一条转账记录;
  • 区块(Block):记录一段时间内发生的所有交易和状态结果等,是对当前账本状态的一次共识;
  • 链(Chain):由区块按照发生顺序串联而成,是整个账本状态变化的日志记录。

如果把区块链系统作为一个状态机,则每次交易意味着一次状态改变;生成的区块,就是参与者对其中交易导致状态改变结果的共识。

区块链的目标是实现一个分布的数据记录账本,这个账本只允许添加、不允许删除。

账本底层的基本结构是一个线性的链表。链表由一个个“区块”串联组成(如下图所示),后继区块中记录前导区块的哈希(Hash)值。

某个区块(以及块里的交易)是否合法,可通过计算哈希值的方式进行快速检验。

网络中节点可以提议添加一个新的区块,但必须经过共识机制来对区块达成确认。


区块链基本特征

1.去中心化

由大量节点共同组成的一个点对点网络,任一节点的权利和义务都是均等的,不存在中心化的硬件或管理机构。

2.基于共识建立信任

它运用一套基于共识的数学算法,在机器之间建立“信任”网络,从而通过技术背书而非中心化信用机构来建立信用。

3.信息不可篡改

区块链是基于时间戳形成不可篡改、不可伪造的数据库,存储了所有交易历史,并可追本溯源逐笔验证。

随之带来的业务特性将可能包括:

  • 可信任性:区块链技术可以提供天然可信的分布式账本平台,不需要额外第三方中介机构参与;
  • 降低成本:跟传统技术相比,区块链技术可能通过自动化合约执行带来更快的交易,同时降低维护成本;
  • 增强安全:区块链技术将有利于安全、可靠的审计管理和账目清算,减少犯罪风险。


区块链的基本名词

交易

区块


区块链的基本流程

交易

交易提交-验证-广播


区块

区块打包-验证-广播




SkyCoin解析

  • Coin:区块/未消费交易/事务核心逻辑
  • Cipher:加密相关工具
  • Consensus:共识
  • Daemon:核心处理器/引擎
  • Util:工具类
  • Api:http相关接口
  • Visor:所有可用操作,操作器

相关节点操作

1
2
3
4
5
6
7
8
9
10
11
M->Slave

Slave

Node#Slave用来接收验证区块和交易的节点,需要配置DefaultConnection可信任节点,从而获取到其他peers,否则为单个节点无法通信
Node#Master用来打包交易生产区块的节点
Node#Slave想要成为Master,必须有私钥

Slave -> 接收交易 -> 验证交易 -> Update(UnSpentPool,UnConfirmPool) -> 广播交易
Master -> 接收交易 -> 验证交易 -> 产区块打包交易 -> Update(UnSpentPool,UnConfirmPool) -> 广播区块
Slave -> 验证区块 -> 验证交易合法 -> Update(UnSpentPool,UnConfirmPool) -> 广播区块




网络相关:daeman/pool连接池

Config && New

connectionPool:连接池

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
// NewPoolConfig creates pool config
func NewPoolConfig() PoolConfig {
//defIdleLimit := time.Minute
return PoolConfig{
port: 6677,
address: "",
DialTimeout: time.Second * 30,
MessageHandlingRate: time.Millisecond * 50,
PingRate: 5 * time.Second,
IdleLimit: 60 * time.Second,
IdleCheckRate: 1 * time.Second,
ClearStaleRate: 1 * time.Second,
EventChannelSize: 4096,
MaxConnections: 128,
MaxDefaultPeerOutgoingConnections: 1,
DefaultPeerConnections: make(map[string]struct{}),
}
}

// Pool maintains config and pool
type Pool struct {
Config PoolConfig
Pool *gnet.ConnectionPool
}

// NewPool creates pool
func NewPool(cfg PoolConfig, d *Daemon) *Pool {
gnetCfg := gnet.NewConfig()
gnetCfg.DialTimeout = cfg.DialTimeout
gnetCfg.Port = uint16(cfg.port)
gnetCfg.Address = cfg.address
gnetCfg.ConnectCallback = d.onGnetConnect
gnetCfg.DisconnectCallback = d.onGnetDisconnect
gnetCfg.MaxConnections = cfg.MaxConnections
gnetCfg.MaxDefaultPeerOutgoingConnections = cfg.MaxDefaultPeerOutgoingConnections
gnetCfg.DefaultPeerConnections = cfg.DefaultPeerConnections

return &Pool{
Config: cfg,
Pool: gnet.NewConnectionPool(gnetCfg, d),
}
}

func NewConnectionPool(c Config, state interface{}) *ConnectionPool {
pool := &ConnectionPool{
Config: c,
pool: make(map[int]*Connection),
addresses: make(map[string]*Connection),
defaultPeerConnections: make(map[string]struct{}),
SendResults: make(chan SendResult, c.SendResultsSize),
messageState: state,
quit: make(chan struct{}),
done: make(chan struct{}),
strandDone: make(chan struct{}),
reqC: make(chan strand.Request),
}

return pool
}

func (pool *ConnectionPool) NewConnection(conn net.Conn, solicited bool) (*Connection, error) {
a := conn.RemoteAddr().String()
var nc *Connection
if err := pool.strand("NewConnection", func() error {
if _, ok := pool.addresses[a]; ok {
return fmt.Errorf("Already connected to %s", a)
}

if _, ok := pool.Config.DefaultPeerConnections[a]; ok {
if len(pool.defaultPeerConnections) >= pool.Config.MaxDefaultPeerOutgoingConnections && solicited {
return ErrMaxDefaultConnectionsReached
}

pool.defaultPeerConnections[a] = struct{}{}
}

pool.connID++
nc = NewConnection(pool, pool.connID, conn, pool.Config.ConnectionWriteQueueSize, solicited)

pool.pool[nc.ID] = nc
pool.addresses[a] = nc
return nil
}); err != nil {
return nil, err
}

return nc, nil
}

以上代码
由此新建相关的连接

Pool Run

ConnectionPool 主循环
loop中每个连接进来都会进行Connection accept相关的连接,并处理相关conn

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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
func (pool *ConnectionPool) Run() error {
defer close(pool.done)
defer logger.Info("Connection pool closed")

//配置相关的地址和端口
addr := fmt.Sprintf("%s:%v", pool.Config.Address, pool.Config.Port)

//监听
ln, err := net.Listen("tcp", addr)
if err != nil {
return err
}

pool.listener = ln

pool.wg.Add(1)
go func() {
defer pool.wg.Done()
//处理Strand
pool.processStrand()
}()

loop:
for {
conn, err := ln.Accept()
if err != nil {
select {
case <-pool.quit:
break loop
default:
logger.Error(err)
continue
}
}

pool.wg.Add(1)
go func() {
defer pool.wg.Done()
if err := pool.handleConnection(conn, false); err != nil {
logger.Errorf("pool.handleConnection error: %v", err)
}
}()
}
pool.wg.Wait()
return nil
}




func (pool *ConnectionPool) handleConnection(conn net.Conn, solicited bool) error {
defer logger.Debugf("Connection %s closed", conn.RemoteAddr())
addr := conn.RemoteAddr().String()

c, err := func() (c *Connection, err error) {
defer func() {
if err != nil {
if closeErr := conn.Close(); closeErr != nil {
logger.Errorf("conn.Close() %s error: %v", addr, closeErr)
}
}
}()

exist, err := pool.IsConnExist(addr)
if err != nil {
return
}
if exist {
err = fmt.Errorf("Connection %s already exists", addr)
return
}

c, err = pool.NewConnection(conn, solicited)
if err != nil {
err = fmt.Errorf("Create connection to %s failed: %v", addr, err)
return
}

return c, err
}()

if err != nil {
return err
}

if pool.Config.ConnectCallback != nil {
pool.Config.ConnectCallback(c.Addr(), solicited)
}

msgC := make(chan []byte, 32)
errC := make(chan error, 3)

var wg sync.WaitGroup
wg.Add(1)
qc := make(chan struct{})
go func() {
defer wg.Done()
if err := pool.readLoop(c, msgC, qc); err != nil {
errC <- err
}
}()

wg.Add(1)
go func() {
defer wg.Done()
if err := pool.sendLoop(c, pool.Config.WriteTimeout, qc); err != nil {
errC <- err
}
}()

wg.Add(1)
go func() {
defer wg.Done()
elapser := elapse.NewElapser(receiveMessageDurationThreshold, logger)
defer elapser.CheckForDone()

for {
select {
case msg, ok := <-msgC:
if !ok {
return
}
elapser.Register(fmt.Sprintf("pool.receiveMessage address=%s", addr))
if err := pool.receiveMessage(c, msg); err != nil {
errC <- err
return
}
elapser.CheckForDone()
}
}
}()

select {
case <-pool.quit:
if err := conn.Close(); err != nil {
logger.Errorf("conn.Close() %s error: %v", addr, err)
}
case err = <-errC:
if err := pool.Disconnect(c.Addr(), err); err != nil {
logger.Errorf("Disconnect %s failed: %v", addr, err)
} else {
logger.Debugf("Disconnected from %s", addr)
}
}
close(qc)

wg.Wait()

return err
}

以上代码引出以下几类循环loop

  • processStrand 循环信道消息处理
  • sendLoop 循环发送消息信道处理
  • readLoop 循环读取信息信道处理
  • pool.receiveMessage 循环消息接受机制

processStrand

处理Pool级别的Strand信道处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func (pool *ConnectionPool) processStrand() {
defer close(pool.strandDone)
for {
select {
case <-pool.quit:
return
case req := <-pool.reqC:
if err := req.Func(); err != nil {
logger.Errorf("req.Func %s failed: %v", req.Name, err)
}
}
}
}


//其中pool.reqC为reqC chan strand.Request
type Request struct {
Name string
Func func() error
}
//用于strand.Request<- 处理pool级别的req.Func()

sendLoop

监听conn.WriteQueue的chan,如果有线程往conn.WriteQueue丢入msg,则直接被取出并sendMessage
然后通过网络net.write信息后封装newSendResult将发送结果丢入chan,由daemon的loop中处理

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
func (pool *ConnectionPool) sendLoop(conn *Connection, timeout time.Duration, qc chan struct{}) error {
elapser := elapse.NewElapser(sendLoopDurationThreshold, logger)
defer elapser.CheckForDone()

for {
elapser.CheckForDone()
select {
case <-pool.quit:
return nil
case <-qc:
return nil
case m := <-conn.WriteQueue:
elapser.Register(fmt.Sprintf("conn.WriteQueue address=%s", conn.Addr()))
if m == nil {
continue
}

err := sendMessage(conn.Conn, m, timeout)

if err == nil {
if err := pool.updateLastSent(conn.Addr(), Now()); err != nil {
logger.Warningf("updateLastSent(%s) failed", conn.Addr())
}
}

sr := newSendResult(conn.Addr(), m, err)
select {
case <-qc:
return nil
case pool.SendResults <- sr:
default:
logger.Warningf("SendResults queue full address=%s", conn.Addr())
}

if err != nil {
return err
}
}
}
}

readLoop

监听网络上的数据,一旦有数据就会丢入msgChan,msgChan又是一个chan,后期转交给pool.receiveMessage处理

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
func (pool *ConnectionPool) readLoop(conn *Connection, msgChan chan []byte, qc chan struct{}) error {
defer close(msgChan)
// read data from connection
reader := bufio.NewReader(conn.Conn)
buf := make([]byte, 1024)

elapser := elapse.NewElapser(readLoopDurationThreshold, logger)
sendInMsgChanElapser := elapse.NewElapser(sendInMsgChanDurationThreshold, logger)

defer elapser.CheckForDone()
defer sendInMsgChanElapser.CheckForDone()

for {
elapser.Register(fmt.Sprintf("readLoop address=%s", conn.Addr()))
deadline := time.Time{}
if pool.Config.ReadTimeout != 0 {
deadline = time.Now().Add(pool.Config.ReadTimeout)
}
if err := conn.Conn.SetReadDeadline(deadline); err != nil {
return ErrDisconnectSetReadDeadlineFailed
}
data, err := readData(reader, buf)
if err != nil {
return err
}

if data == nil {
continue
}

// write data to buffer
if _, err := conn.Buffer.Write(data); err != nil {
return err
}
// decode data
datas, err := decodeData(conn.Buffer, pool.Config.MaxMessageLength)
if err != nil {
return err
}
for _, d := range datas {
select {
case <-qc:
return nil
case <-pool.quit:
return nil
case msgChan <- d:
default:
return errors.New("readLoop msgChan is closed or full")
}
}
sendInMsgChanElapser.CheckForDone()
}
}

receiveMessage

它会一直在chan监听msg到来,一旦有msg到来就会丢入receiveMessage,
至此convertToMessage会将msg转换成专业的Message信息去处理,
每个msg数据前都有msgID,通过msssageIdReverseMap映射相关的Message种类,
最后再根据反射Reflect.new(种类)实例相关的Message,
最后使用Message#Handler方法,例如PingMessage

1
2
3
4
5
6
7
8
func (ping *PingMessage) Handle(mc *gnet.MessageContext, daemon interface{}) error {
ping.c = mc
return daemon.(*Daemon).recordMessageEvent(ping, mc)
}
func (dm *Daemon) recordMessageEvent(m AsyncMessage, c *gnet.MessageContext) error {
dm.messageEvents <- MessageEvent{m, c}
return nil
}

直接通过Daemon制造MessageEvent,Message被包裹在MessageEvent内,
在Daemon的loop主循环中处理MessageEvent,最终实则使用了Message#Process

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
case m := <-dm.messageEvents:
elapser.Register("dm.messageEvents")
if dm.Config.DisableNetworking {
logger.Error("There should be no message events")
return nil
}
dm.processMessageEvent(m)
......


func (dm *Daemon) processMessageEvent(e MessageEvent) {
if dm.needsIntro(e.Context.Addr) {
_, isIntro := e.Message.(*IntroductionMessage)
if !isIntro {
dm.Pool.Pool.Disconnect(e.Context.Addr, ErrDisconnectNoIntroduction)
}
}
e.Message.Process(dm)
}

以下为Message大致种类

以下为主体代码

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
go func() {
defer wg.Done()
elapser := elapse.NewElapser(receiveMessageDurationThreshold, logger)
defer elapser.CheckForDone()

for {
select {
case msg, ok := <-msgC:
if !ok {
return
}
elapser.Register(fmt.Sprintf("pool.receiveMessage address=%s", addr))
if err := pool.receiveMessage(c, msg); err != nil {
errC <- err
return
}
elapser.CheckForDone()
}
}
}()

func (pool *ConnectionPool) receiveMessage(c *Connection, msg []byte) error {
m, err := convertToMessage(c.ID, msg, pool.Config.DebugPrint)
if err != nil {
return err
}
if err := pool.updateLastRecv(c.Addr(), Now()); err != nil {
return err
}
return m.Handle(NewMessageContext(c), pool.messageState)
}

区块相关: coin/Block

区块数据结构

  • Block 总区块体
  • HashPair 哈希对
  • BlockHeader 区块头
  • BlockBody 区块体
  • SignedBlock 加签区块
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
// Block represents the block struct
type Block struct {
Head BlockHeader
Body BlockBody
}
// HashPair including current block hash and previous block hash.
type HashPair struct {
Hash cipher.SHA256
PreHash cipher.SHA256
}

// BlockHeader records the block header
type BlockHeader struct {
Version uint32

Time uint64
BkSeq uint64 // Increment every block
Fee uint64 // Fee in block

PrevHash cipher.SHA256 // Hash of header of previous block
BodyHash cipher.SHA256 // 默克尔树hash

UxHash cipher.SHA256 // XOR of sha256 of elements in unspent output set
}

// BlockBody represents the block body
type BlockBody struct {
Transactions Transactions
}

// SignedBlock signed block
type SignedBlock struct {
Block
Sig cipher.Sig //cipher.SignHash(Block.HashHeader(), genSecret) 以上是Sig的由来
}

默克尔树的hash给予BodyHash

1
2
3
4
5
6
7
8
9
10

// Hash returns the merkle hash of contained transactions
func (bb BlockBody) Hash() cipher.SHA256 {
hashes := make([]cipher.SHA256, len(bb.Transactions))
for i := range bb.Transactions {
hashes[i] = bb.Transactions[i].Hash()
}
// Merkle hash of transactions
return cipher.Merkle(hashes)
}

Block新建过程

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
//prev := Block{Head: BlockHeader{Version: 0x02, Time: 100, BkSeq: 98}}
//currentTime := uint64(133)
//uxHash := testutil.RandSHA256(t)
//txns := Transactions{Transaction{}}
//t := func(t *Transaction) (uint64, error) {return fee, nil}
//NewBlock(prev, currentTime, uxHash, txns, func(t *Transaction) (uint64, error) {


// NewBlock creates new block.
func NewBlock(prev Block, currentTime uint64, uxHash cipher.SHA256, txns Transactions, calc FeeCalculator) (*Block, error) {
if len(txns) == 0 {
return nil, fmt.Errorf("Refusing to create block with no transactions")
}

fee, err := txns.Fees(calc)
if err != nil {
// This should have been caught earlier
return nil, fmt.Errorf("Invalid transaction fees: %v", err)
}

body := BlockBody{txns}
return &Block{
Head: NewBlockHeader(prev.Head, uxHash, currentTime, fee, body),
Body: body,
}, nil
}

NewBlockHeader

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// NewBlockHeader creates block header
func NewBlockHeader(prev BlockHeader, uxHash cipher.SHA256, currentTime, fee uint64, body BlockBody) BlockHeader {
if currentTime <= prev.Time {
logger.Panic("Time can only move forward")
}
prevHash := prev.Hash() //SHA256
return BlockHeader{
BodyHash: body.Hash(), //默克尔
Version: prev.Version,
PrevHash: prevHash,
Time: currentTime,
BkSeq: prev.BkSeq + 1,
Fee: fee,
UxHash: uxHash,
}
}

区块链相关结构

区块链的数据结构主要为了存储,数据库利用了bolt

并且在bolt中的bucket为桶结构,是为一系列key的集合,类似Mysql#Table结构

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
type Blockchain struct {
db *dbutil.DB
cfg BlockchainConfig
store chainStore
}

type chainStore interface {
Head(*dbutil.Tx) (*coin.SignedBlock, error)
HeadSeq(*dbutil.Tx) (uint64, bool, error)
Len(*dbutil.Tx) (uint64, error)
AddBlock(*dbutil.Tx, *coin.SignedBlock) error
GetBlockByHash(*dbutil.Tx, cipher.SHA256) (*coin.Block, error)
GetSignedBlockByHash(*dbutil.Tx, cipher.SHA256) (*coin.SignedBlock, error)
GetSignedBlockBySeq(*dbutil.Tx, uint64) (*coin.SignedBlock, error)
UnspentPool() blockdb.UnspentPool
GetGenesisBlock(*dbutil.Tx) (*coin.SignedBlock, error)
GetBlockSignature(*dbutil.Tx, *coin.Block) (cipher.Sig, bool, error)
ForEachBlock(*dbutil.Tx, func(*coin.Block) error) error
}

// BlockchainConfig configures Blockchain options
type BlockchainConfig struct {
// Arbitrating mode: if in arbitrating mode, when master node execute blocks,
// the invalid transaction will be skipped and continue the next; otherwise,
// node will throw the error and return.
Arbitrating bool
Pubkey cipher.PubKey
}

交易事务相关 coin/transaction

交易结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Transaction transaction struct
type Transaction struct {
Length uint32 //length prefix
Type uint8 //transaction type
InnerHash cipher.SHA256 //inner hash SHA256 of In[],Out[]

Sigs []cipher.Sig //list of signatures, 64+1 bytes each
In []cipher.SHA256 //ouputs being spent
Out []TransactionOutput //ouputs being created
}

// TransactionOutput hash output/name is function of Hash
type TransactionOutput struct {
Address cipher.Address //address to send to
Coins uint64 //amount to be sent in coins
Hours uint64 //amount to be sent in coin hours 币时
}

关键方法

交易事务的Hash

1
2
3
4
5
// Hash an entire Transaction struct, including the TransactionHeader
func (txn *Transaction) Hash() cipher.SHA256 {
b := txn.Serialize()
return cipher.SumSHA256(b)
}

获取交易长度

1
2
3
4
// Size returns the encoded byte size of the transaction
func (txn *Transaction) Size() int {
return len(txn.Serialize())
}

对所有Input进行私钥签名

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
// SignInputs signs all inputs in the transaction
func (txn *Transaction) SignInputs(keys []cipher.SecKey) {
txn.InnerHash = txn.HashInner() // update hash

if len(txn.Sigs) != 0 {
logger.Panic("Transaction has been signed")
}
if len(keys) != len(txn.In) {
logger.Panic("Invalid number of keys")
}
if len(keys) > math.MaxUint16 {
logger.Panic("Too many keys")
}
if len(keys) == 0 {
logger.Panic("No keys")
}

sigs := make([]cipher.Sig, len(txn.In))
innerHash := txn.HashInner()
for i, k := range keys {
h := cipher.AddSHA256(innerHash, txn.In[i]) // hash to sign
sigs[i] = cipher.SignHash(h, k)
}
txn.Sigs = sigs
}

未花费的交易UTXO coin/outputs

UTXO结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//Unspent Outputs
// UxOut represents uxout
type UxOut struct {
Head UxHead
Body UxBody //hashed part
//Meta UxMeta
}

// UxHead metadata (not hashed)
type UxHead struct {
Time uint64 //time of block it was created in
BkSeq uint64 //block it was created in, used to calculate depth
// SpSeq uint64 //block it was spent in
}

// UxBody uxbody
type UxBody struct {
SrcTransaction cipher.SHA256 // Inner Hash of Transaction
Address cipher.Address // Address of receiver
Coins uint64 // Number of coins
Hours uint64 // Coin hours
}

Address地址 cipher/address

正常情况用户地址生成过程

1
2
3
4
5
通过传入的seed生成私钥

通过传入私钥生成公钥

通过传入公钥生成地址

当然address也可以直接从私钥上产生或者直接一些byte数组上产生

  • AddressFromSecKey
  • AddressFromPubKey
  • AddressFromBytes

基本结构

1
2
3
4
5
6
7
// Address version is after Key to enable better vanity address generation
// Address stuct is a 25 byte with a 20 byte publickey hash, 1 byte address
// type and 4 byte checksum.
type Address struct {
Version byte //1 byte
Key Ripemd160 //20 byte pubkey hash
}

Address制造过程

1
2
3
4
5
6
7
8
9
10
11
12
13
// BitcoinAddressFromPubkey prints the bitcoin address for a seckey
func BitcoinAddressFromPubkey(pubkey PubKey) string {
b1 := SumSHA256(pubkey[:])
b2 := HashRipemd160(b1[:])
b3 := append([]byte{byte(0)}, b2[:]...)
b4 := DoubleSHA256(b3)
b5 := append(b3, b4[0:4]...)
return string(base58.Hex2Base58(b5))
// return Address{
// Version: 0,
// Key: b2,
// }
}




过程分析

创建交易过程

首先在Api的package里面造一个http端口访问/spend的url,用来进行发起交易
不可缺少以下参数

  • 发起方Address
  • 接收方Address
  • 交易金额

接下来无非就是以下结果

1
2
3
4
5
6
Slave -> 接收交易 -> 验证交易 -> Update(UnSpentPool,UnConfirmPool) -> 广播交易
Master -> 接收交易 -> 验证交易 -> 产区块打包交易 -> Update(UnSpentPool,UnConfirmPool) -> 广播区块

代码会执行以下流程
GateWay#CreateTransaction //创建交易
GateWay#InjectBroadcastTransaction //广播交易

大体流程如下

1
2
3
4
5
6
7
8
9
10
// Get unspent outputs, while checking that there are no unconfirmed outputs
auxs, err = gw.getUnspentsForSpending(addrs)

// Create and sign transaction
txn, inputs, err = gw.v.Wallets.CreateAndSignTransactionAdvanced(params, auxs, head.Time())

// The wallet can create transactions that would not pass all validation, such as the decimal restriction
// because the wallet is not aware of visor-level constraints.
// Check that the transaction is valid before returning it to the caller.
err = gw.v.VerifySingleTxnAllConstraints(txn)

验证流程

  • 交易金额不能为0
  • 交易金额不能为空
  • 从交易发起方获取Address
    • 根据Address取Mysql数据库相关私钥,判断私钥

组装流程

  • 根据交易事务的Input来拿Unspent Pool池中相关此Address的UTXO

    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
    type Transaction struct {
    Length uint32 //length prefix
    Type uint8 //transaction type
    InnerHash cipher.SHA256 //inner hash SHA256 of In[],Out[]

    Sigs []cipher.Sig //list of signatures, 64+1 bytes each
    In []cipher.SHA256 //ouputs being spent
    Out []TransactionOutput //ouputs being created
    }

    // UxOut represents uxout
    type UxOut struct {
    Head UxHead
    Body UxBody //hashed part
    //Meta UxMeta
    }

    // UxHead metadata (not hashed)
    type UxHead struct {
    Time uint64 //time of block it was created in
    BkSeq uint64 //block it was created in, used to calculate depth
    // SpSeq uint64 //block it was spent in
    }

    // UxBody uxbody
    type UxBody struct {
    SrcTransaction cipher.SHA256 // Inner Hash of Transaction
    Address cipher.Address // Address of receiver
    Coins uint64 // Number of coins
    Hours uint64 // Coin hours
    }

    //拿取Transaction.In
    for _, v := range Transaction.In{
    unspent.Get(v)
    .......

    }
  • 对取出所有的utxo按照交易金额由小到大排序,尽可能多的消费utxo

    1
    spendOutputs, err := chooseSpends(uxouts, totalCoins)
  • 查看是否需要找零,如果需要找零,则新增一项receiveAddresses为自己,并设置相关的coin剩余币

  • 组装Transaction

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // Transaction transaction struct
    type Transaction struct {
    Length uint32 //length prefix
    Type uint8 //transaction type
    InnerHash cipher.SHA256 //inner hash SHA256 of In[],Out[]

    Sigs []cipher.Sig //list of signatures, 64+1 bytes each
    In []cipher.SHA256 //ouputs being spent
    Out []TransactionOutput //ouputs being created
    }

    // TransactionOutput hash output/name is function of Hash
    type TransactionOutput struct {
    Address cipher.Address //address to send to
    Coins uint64 //amount to be sent in coins
    Hours uint64 //amount to be sent in coin hours
    }
    • TransactionOutput
      • Address:此为receiveAddresses(如果有剩余币可能还会指向自己)
      • Coins:此为每个receiveAddresses的coin(付费的金币)
      • Hourse:币时,呈2次递减
    • In

      1
      2
      首先查找有几个发送者的UTXO
      后遍历每个发送者的UTXO并且进行utxo.Body.Hash()然后每个都append形成最终的In
    • Sigs:对交易中的所有input进行签名

    • InnerHash:内部SHA256 of In[],Out[]




广播交易流程

广播交易

1
2
Slave -> 接收交易 -> 验证交易 -> Update(UnSpentPool,UnConfirmPool) -> 广播交易
Master -> 接收交易 -> 验证交易 -> 产区块打包交易 -> Update(UnSpentPool,UnConfirmPool) -> 广播区块

以上代码执行了以下创建交易流程
GateWay#CreateTransaction

接下来是广播交易
GateWay#InjectBroadcastTransaction

  • 首先还是验证交易

    • 单个交易的软硬约束: VerifySingleTxnAllConstraints

      1
      2
      判断交易的utxo是否已存在
      判断未花费交易池是否已经存在对应的utxo,若存在,校验失败
    • 在验证交易的整个大小是否超过maxSize

      1
      len(txn.Serialize()) > maxSize
  • 更新本节点未确认交易池

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //UnconfirmedTxnPool
    func createUnconfirmedTxn(txn coin.Transaction) UnconfirmedTxn {
    now := utc.Now()
    return UnconfirmedTxn{
    Txn: txn,
    Received: now.UnixNano(),
    Checked: now.UnixNano(),
    Announced: time.Time{}.UnixNano(),
    }
    }
  • 再者才是广播交易

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // broadcastTransaction broadcasts a single transaction to all peers.
    func (vs *Visor) broadcastTransaction(t coin.Transaction, pool *Pool) error {
    if vs.Config.DisableNetworking {
    return nil
    }

    m := NewGiveTxnsMessage(coin.Transactions{t})
    l, err := pool.Pool.Size()
    if err != nil {
    return err
    }

    err = pool.Pool.BroadcastMessage(m)
    if err != nil {
    logger.Errorf("Broadcast GivenTxnsMessage failed: %v", err)
    }

    return err
    }
    • 创建GiveTxnsMessage
    • 通过pool.Pool.BroadcastMessage传递消息出去,通过writeQueue传递到Sendloop
      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
      // BroadcastMessage sends a Message to all connections in the Pool.
      func (pool *ConnectionPool) BroadcastMessage(msg Message) error {
      if pool.Config.DebugPrint {
      logger.Debugf("Broadcast, Msg Type: %s", reflect.TypeOf(msg))
      }

      fullWriteQueue := []string{}
      if err := pool.strand("BroadcastMessage", func() error {
      if len(pool.pool) == 0 {
      return errors.New("Connection pool is empty")
      }

      for _, conn := range pool.pool {
      select {
      case conn.WriteQueue <- msg:
      default:
      logger.Critical().Infof("Write queue full for address %s", conn.Addr())
      fullWriteQueue = append(fullWriteQueue, conn.Addr())
      }
      }

      if len(fullWriteQueue) == len(pool.pool) {
      return ErrNoReachableConnections
      }

      return nil
      }); err != nil {
      return err
      }

      return nil
      }




接受交易造区块流程

相关节点接收到GiveTxnsMessage的Message信息

  • 根据Message#Process准备丢入交易池
  • 进入InjectTransaction,往未确认交易池创建注入交易
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // UnconfirmedTxn unconfirmed transaction
    type UnconfirmedTxn struct {
    Txn coin.Transaction //交易结构
    // Time the txn was last received 上一次接收时间
    Received int64
    // Time the txn was last checked against the blockchain 检查时间
    Checked int64
    // Last time we announced this txn 广播时间
    Announced int64
    // If this txn is valid 是否验证
    IsValid int8
    }

更新bolt数据库相关未确认交易池

  • Daemon定时判断交易是否是合法的,如合法就标识这笔未确认交易有效
  • Daemon定时去查询所有未确认交易池
    • 判断打包交易的总和不能超过区块的最大容量
    • 根据这些交易开始打包区块NewBlock
    • 打包区块时,校验交易是否合法processTransactions
      • 校验输出utxo的唯一性
      • 判断输入的utxo是否在未花费池中
      • 再次校验,确保不存在相同的交易,已经交易中不存在相同的utxo输入
    • 交易数据生成一个新的区块
    • 创建签名区块:根据区块头哈希HashHeader+区块私钥BlockSecKey加签{Block,Sig}
    • 添加区块信息到数据库
    • 移除未确认交易




节点发现过程

  • pex:主要的peer运行容器类
  • peerlist:一个peer列表
1
2
3
4
5
6
7
8
9
10
11
12
type peerlist struct {
peers map[string]*Peer
}

type Peer struct {
Addr string // An address of the form ip:port
LastSeen int64 // Unix timestamp when this peer was last seen
Private bool // Whether it should omitted from public requests
Trusted bool // Whether this peer is trusted
HasIncomingPort bool // Whether this peer has accessable public port
RetryTimes int `json:"-"` // records the retry times
}

Daemon#Run即将会运行connectToTrustPeer

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
func (dm *Daemon) connectToTrustPeer() {
if dm.Config.DisableIncomingConnections {
return
}

logger.Info("Connect to trusted peers")
// Make connections to all trusted peers
peers := dm.Pex.TrustedPublic()
for _, p := range peers {
dm.connectToPeer(p)
}
}


|
|
|
|


go func() {
if err := dm.Pool.Pool.Connect(p.Addr); err != nil {
dm.connectionErrors <- ConnectionError{p.Addr, err}
}
}()


|
|
|
|


go func() {
defer pool.wg.Done()
if err := pool.handleConnection(conn, true); err != nil {
logger.Errorf("pool.handleConnection error: %v", err)
}
}()

至此pool.handleConnection会启动以下三个loop

  • receiveMessage
  • readLoop
  • sendLoop

跟Daemon#Run起线程去Pool#Run作为服务监听连接Accept相关连接一样的道理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
go func() {
defer wg.Done()
if dm.Config.DisableIncomingConnections {
if err := dm.Pool.RunOffline(); err != nil {
logger.WithError(err).Error("daemon.Pool.RunOffline failed")
errC <- err
}
} else {
if err := dm.Pool.Run(); err != nil {
logger.WithError(err).Error("daemon.Pool.Run failed")
errC <- err
}
}
}()

后期就进行Ping/PongMessage维持这些Connection




循环处理过程

大多情况下例如节点发现需要实时的不中断地跑

所以在Daemon#Run方法有这样的TimeTicker

1
2
3
4
5
6
7
8
9
idlePingTicker := time.Tick(time.Duration)

for {
select {
case <-idlePingTicker:
// 发送pings消息(必需)
........
}
}

加密过程

因为Master是出块节点可信任节点,所以并没有SignScript,PubKeyScript这类

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
//交易整体结构
type Transaction struct {
Length uint32 //长度
InnerHash cipher.SHA256 //内部hash
Signs []cipher.Sig //签名
Inputs []cipher.SHA256 //发送方UTXO
Outputs []TransactionOutput //接收方地址
Data string //额外数据
}

//交易输出结构
type TransactionOutput struct {
Address address.Address //地址
Coins uint64 //交易金额
}


//Inputs结构
for _, utxo := range spends {
senderUtxos = append(senderUtxos, utxo.Body.Hash())
spendingCoins += utxo.Body.Coins
toSigns = append(toSigns, toSign) //SignInputs的参数keys []cipher.SecKey
}


//对交易中的所有input进行签名
func (txn *Transaction) SignInputs(keys []cipher.SecKey) {
txn.InnerHash = txn.HashInner()
...
...
...
...
...
signs := make([]cipher.Sig, len(txn.Inputs))
innerHash := txn.HashInner()
for i, k := range keys {
//每个input的
h := cipher.AddSHA256(innerHash, txn.Inputs[i])
signs[i] = cipher.SignHash(h, k)
}
txn.Signs = signs
}

//根据交易的所有input和output计算出hash
func (txn *Transaction) HashInner() cipher.SHA256 {
input := encoder.Serialize(txn.Inputs)
output := encoder.Serialize(txn.Outputs)
txs := append(input, output...)
return cipher.SumSHA256(txs)
}

Eth

客户端

下载源码

https://github.com/ethereum/go-ethereum

1
2
make geth
//build geth 可执行文件

或直接下载安装客户端

1
2
3
4
brew tap ethereum/ethereum
brew install ethereum

https://ethereum.github.io/go-ethereum/downloads/

各类参数说明

img

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
➜  go-ethereum :
.
├── accounts // 以太坊的钱包和账户管理
├── abi // 以太坊合约的ABI代码
├── keystore // 支持 keystore 模式的钱包
└── usbwallet // 支持 USB 模式的钱包
├── bmt // 二进制的 Merkle 树的实现
├── build // 编译与构建的一些脚本和配置
├── cmd // 命令行工具集
├── abigen // ABI 生成器
├── bootnode // 启动一个仅仅实现网络发现的节点
├── ethkey
├── evm // 以太坊虚拟机的开发工具
├── faucet
├── geth // geth 命令行工具
├── p2psim // 提供了一个工具来模拟 HTTP 的 API
├── puppeth // 创建一个新的以太坊网络的向导
├── rlpdump // RLP 数据的格式化输出
├── swarm // swarm 网络的接入点
├── utils // 公共工具
└── wnode // 一个简单的 whisper 节点,可以用作独立的引导节点。此外,可以用于不同的测试和诊断目的
├── common // 提供了一些公共通用的工具类
├── compression // 压缩
├── consensus // 提供了以太坊的一些共识算法,比如:ethhash
├── console // 控制台
├── containers // 支持 docker 和 vagrant 等容器
├── contracts // 合约管理
├── core // 核心数据结构和算法(EVM,state,Blockchain,布隆过滤器等)
├── crypto // 加密相关
├── dashboard //
├── eth // 在其中实现了以太坊的协议
├── ethclient // geth 客户端入口
├── ethdb // eth 的数据库(包括生产环境的leveldb和供测试用的内存数据库)
├── ethstats // 提供以太坊网络状态的报告
├── event // 用于处理实时事件
├── les // 以太坊的轻量级协议子集(Light Ethereum Subprotocol)
├── light // 实现为以太坊轻量级客户端提供按需检索的功能
├── log // 日志模块
├── metrics // 度量和检测
├── miner // 提供以太坊的区块创建和挖矿
├── mobile // 移动端的一些 wrapper
├── node // 以太坊的多种类型的节点
├── p2p // P2P 网络协议
├── params // 参数管理
├── rlp // 以太坊的序列化处理(rlp 递归长度前缀编码)
├── rpc // rpc 远程方法调用
├── swarm // swarm 网络存储和处理
├── trie // 以太坊中的重要数据结构,Merkle Patricia Tries
└── whisper // whisper 节点协议

交互式控制台

  • eth:包含一些跟操作区块链相关的方法
  • net:包含以下查看p2p网络状态的方法
  • admin:包含一些与管理节点相关的方法
  • miner:包含启动&停止挖矿的一些方法
  • personal:主要包含一些管理账户的方法
  • txpool:包含一些查看交易内存池的方法
  • web3:包含了以上对象,还包含一些单位换算的方法

参数说明

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
--config value          TOML 配置文件
--datadir “xxx” 数据库和keystore密钥的数据目录
--keystore keystore存放目录(默认在datadir内)
--nousb 禁用监控和管理USB硬件钱包
--networkid value 网络标识符(整型, 1=Frontier, 2=Morden (弃用), 3=Ropsten, 4=Rinkeby) (默认: 1)
--testnet Ropsten网络:预先配置的POW(proof-of-work)测试网络
--rinkeby Rinkeby网络: 预先配置的POA(proof-of-authority)测试网络
--syncmode "fast" 同步模式 ("fast", "full", or "light")
--ethstats value 上报ethstats service URL (nodename:secret@host:port)
--identity value 自定义节点名
--lightserv value 允许LES请求时间最大百分比(0 – 90)(默认值:0)
--lightpeers value 最大LES client peers数量(默认值:20)
--lightkdf 在KDF强度消费时降低key-derivation RAM&CPU使用

//开发者专用
--dev 使用POA共识网络,默认预分配一个开发者账户并且会自动开启挖矿。
--dev.period value 开发者模式下挖矿周期 (0 = 仅在交易时) (默认: 0)
--vmdebug 记录VM及合约调试信息


--ethash.cachedir ethash验证缓存目录(默认 = datadir目录内)
--ethash.cachesinmem value 在内存保存的最近的ethash缓存个数 (每个缓存16MB ) (默认: 2)
--ethash.cachesondisk value 在磁盘保存的最近的ethash缓存个数 (每个缓存16MB) (默认: 3)
--ethash.dagdir "" 存ethash DAGs目录 (默认 = 用户hom目录)
--ethash.dagsinmem value 在内存保存的最近的ethash DAGs 个数 (每个1GB以上) (默认: 1)
--ethash.dagsondisk value 在磁盘保存的最近的ethash DAGs 个数 (每个1GB以上) (默认: 2)



--txpool.nolocals 为本地提交交易禁用价格豁免
--txpool.journal value 本地交易的磁盘日志:用于节点重启 (默认: "transactions.rlp")
--txpool.rejournal value 重新生成本地交易日志的时间间隔 (默认: 1小时)
--txpool.pricelimit value 加入交易池的最小的gas价格限制(默认: 1)
--txpool.pricebump value 价格波动百分比(相对之前已有交易) (默认: 10)
--txpool.accountslots value 每个帐户保证可执行的最少交易槽数量 (默认: 16)
--txpool.globalslots value 所有帐户可执行的最大交易槽数量 (默认: 4096)
--txpool.accountqueue value 每个帐户允许的最多非可执行交易槽数量 (默认: 64)
--txpool.globalqueue value 所有帐户非可执行交易最大槽数量 (默认: 1024)
--txpool.lifetime value 非可执行交易最大入队时间(默认: 3小时)

//性能调优的选项
--cache value 分配给内部缓存的内存MB数量,缓存值(最低16 mb /数据库强制要求)(默认:128)
--trie-cache-gens value 保持在内存中产生的trie node数量(默认:120)


//帐户选项
--unlock value 需解锁账户用逗号分隔
--password value 用于非交互式密码输入的密码文件


//API和控制台选项
--rpc 启用HTTP-RPC服务器
--rpcaddr value HTTP-RPC服务器接口地址(默认值:“localhost”)
--rpcport value HTTP-RPC服务器监听端口(默认值:8545)
--rpcapi value 基于HTTP-RPC接口提供的API(默认值:“eth,net,web3”)
--ws 启用WS-RPC服务器
--wsaddr value WS-RPC服务器监听接口地址(默认值:“localhost”)
--wsport value WS-RPC服务器监听端口(默认值:8546)
--wsapi value 基于WS-RPC的接口提供的API
--wsorigins value websockets请求允许的源
--ipcdisable 禁用IPC-RPC服务器
--ipcpath 包含在datadir里的IPC socket/pipe文件名(转义过的显式路径)
--rpccorsdomain value 允许跨域请求的域名列表(逗号分隔)(浏览器强制)
--jspath loadScript JavaScript加载脚本的根路径(默认值:“.”)
--exec value 执行JavaScript语句(只能结合console/attach使用)
--preload value 预加载到控制台的JavaScript文件列表(逗号分隔)


//网络选项
--bootnodes value 用于P2P发现引导的enode urls(逗号分隔)(对于light servers用v4+v5代替)
--bootnodesv4 value 用于P2P v4发现引导的enode urls(逗号分隔) (light server, 全节点)
--bootnodesv5 value 用于P2P v5发现引导的enode urls(逗号分隔) (light server, 轻节点)
--port value 网卡监听端口(默认值:30303)
--maxpeers value 最大的网络节点数量(如果设置为0,网络将被禁用)(默认值:25)
--maxpendpeers value 最大尝试连接的数量(如果设置为0,则将使用默认值)(默认值:0)
--nat value NAT端口映射机制 (any|none|upnp|pmp|extip:<IP>) (默认: “any”)
--nodiscover 禁用节点发现机制(手动添加节点)
--v5disc 启用实验性的RLPx V5(Topic发现)机制
--nodekey value P2P节点密钥文件
--nodekeyhex value 十六进制的P2P节点密钥(用于测试)


//矿工选项
--mine 打开挖矿
--minerthreads value 挖矿使用的CPU线程数量(默认值:8)
--etherbase value 挖矿奖励地址(默认=第一个创建的帐户)(默认值:“0”)
--targetgaslimit value 目标gas限制:设置最低gas限制(低于这个不会被挖) (默认值:“4712388”)
--gasprice value 挖矿接受交易的最低gas价格
--extradata value 矿工设置的额外块数据(默认=client version)


//GAS价格选项
--gpoblocks value 用于检查gas价格的最近块的个数 (默认: 10)
--gpopercentile value 建议gas价参考最近交易的gas价的百分位数,(默认: 50)


//日志和调试选项
--metrics 启用metrics收集和报告
--fakepow 禁用proof-of-work验证
--verbosity value 日志详细度:0=silent, 1=error, 2=warn, 3=info, 4=debug, 5=detail (default: 3)
--vmodule value 每个模块详细度:以 <pattern>=<level>的逗号分隔列表 (比如 eth/*=6,p2p=5)
--backtrace value 请求特定日志记录堆栈跟踪 (比如 “block.go:271”)
--debug 突出显示调用位置日志(文件名及行号)
--pprof 启用pprof HTTP服务器
--pprofaddr value pprof HTTP服务器监听接口(默认值:127.0.0.1)
--pprofport value pprof HTTP服务器监听端口(默认值:6060)
--memprofilerate value 按指定频率打开memory profiling (默认:524288)
--blockprofilerate value 按指定频率打开block profiling (默认值:0)
--cpuprofile value 将CPU profile写入指定文件
--trace value 将execution trace写入指定文件

geth dumpconfig导出相关配置

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
[Eth]
NetworkId = 1
SyncMode = "full"
LightPeers = 100
DatabaseCache = 768
GasPrice = 18000000000
EnablePreimageRecording = false

[Eth.Ethash]
CacheDir = "ethash"
CachesInMem = 2
CachesOnDisk = 3
DatasetDir = "/root/.ethash"
DatasetsInMem = 1
DatasetsOnDisk = 2
PowMode = 0

[Eth.TxPool]
NoLocals = false
Journal = "transactions.rlp"
Rejournal = 3600000000000
PriceLimit = 1
PriceBump = 10
AccountSlots = 16
GlobalSlots = 4096
AccountQueue = 64
GlobalQueue = 1024
Lifetime = 10800000000000

[Eth.GPO]
Blocks = 20
Percentile = 60

[Shh]
MaxMessageSize = 1048576
MinimumAcceptedPOW = 2e-01

[Node]
DataDir = "/root/.ethereum"
IPCPath = "geth.ipc"
HTTPPort = 8545
HTTPVirtualHosts = ["localhost"]
HTTPModules = ["net", "web3", "eth", "shh"]
WSPort = 8546
WSModules = ["net", "web3", "eth", "shh"]

[Node.P2P]
MaxPeers = 25
NoDiscovery = false
BootstrapNodes = ["enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303", "enode://3f1d12044546b76342d59d4a05532c14b85aa669704bfe1f864fe079415aa2c02d743e03218e57a33fb94523adb54032871a6c51b2cc5514cb7c7e35b3ed0a99@13.93.211.84:30303", "enode://78de8a0916848093c73790ead81d1928bec737d565119932b98c6b100d944b7a95e94f847f689fc723399d2e31129d182f7ef3863f2b4c820abbf3ab2722344d@191.235.84.50:30303", "enode://158f8aab45f6d19c6cbf4a089c2670541a8da11978a2f90dbf6a502a4a3bab80d288afdbeb7ec0ef6d92de563767f3b1ea9e8e334ca711e9f8e2df5a0385e8e6@13.75.154.138:30303", "enode://1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082@52.74.57.123:30303", "enode://979b7fa28feeb35a4741660a16076f1943202cb72b6af70d327f053e248bab9ba81760f39d0701ef1d8f89cc1fbd2cacba0710a12cd5314d5e0c9021aa3637f9@5.1.83.226:30303"]
BootstrapNodesV5 = ["enode://06051a5573c81934c9554ef2898eb13b33a34b94cf36b202b69fde139ca17a85051979867720d4bdae4323d4943ddf9aeeb6643633aa656e0be843659795007a@35.177.226.168:30303", "enode://0cc5f5ffb5d9098c8b8c62325f3797f56509bff942704687b6530992ac706e2cb946b90a34f1f19548cd3c7baccbcaea354531e5983c7d1bc0dee16ce4b6440b@40.118.3.223:30304", "enode://1c7a64d76c0334b0418c004af2f67c50e36a3be60b5e4790bdac0439d21603469a85fad36f2473c9a80eb043ae60936df905fa28f1ff614c3e5dc34f15dcd2dc@40.118.3.223:30306", "enode://85c85d7143ae8bb96924f2b54f1b3e70d8c4d367af305325d30a61385a432f247d2c75c45c6b4a60335060d072d7f5b35dd1d4c45f76941f62a4f83b6e75daaf@40.118.3.223:30307"]
StaticNodes = []
TrustedNodes = []
ListenAddr = ":30303"
EnableMsgEvents = false

[Dashboard]
Host = "localhost"
Port = 8080
Refresh = 5000000000

配置说明

配置项 对应参数 说明
Eth NetworkId –networkid value Network标识符(integer类型,1=Frontier,2=Morden(disused),3=Ropsten,4=Rinkeby),默认为1。如果建立在私网上,使用另外的任意值,比如:3369
Eth DatabaseCache null (个人理解)为database申请的系统内存,单位为MB,最小值和默认值是16MB
Eth GasPrice –gasprice “18000000000” 接受挖掘事务的最低gas价格。可能指miner的报酬
Eth.Ethash CacheDir –ethash.cachedir 存储ethash证明缓存的目录(默认在 datadir 目录里)
Eth.Ethash CachesInMem –ethash.cachesinmem value 保留在内存中的最新ethash缓存的数目(每16MB)(默认:2)。
Eth.Ethash CachesOnDisk –ethash.cachesondisk value 保留在磁盘中的最新ethash缓存的数目(每16MB)(默认:3)。
Eth.Ethash DatasetDir –ethash.dagdir “/home/karalabe/.ethash” 存储ethash挖掘DAGs的目录(默认在home目录里)
Eth.Ethash DatasetsInMem –ethash.dagsinmem value 保留在内存中的最新ethash挖掘DAGs(每1+GB)(默认:1)。
Eth.Ethash DatasetsOnDisk –ethash.dagsondisk value 保留在磁盘中的最新ethash挖掘DAGs(每1+GB)(默认:2)。
Eth.TxPool NoLocals –txpool.nolocals 免除本地提交事务的费用
Eth.TxPool Journal –txpool.journal value 用于节点重启的本地事务磁盘日志(默认:”transactions.rlp”)
Eth.TxPool Rejournal –txpool.rejournal value 重新生成本地事务日志的时间间隔(默认:1h0m0s)
Eth.TxPool PriceLimit –txpool.pricelimit value 强制接纳入池的最小gas价格限制(默认:1)
Eth.TxPool PriceBump –txpool.pricebump value 替代一个已经存在的事务的价格碰撞百分比(默认:10)
Eth.TxPool AccountSlots –txpool.accountslots value 每个账户担保的可执行事务时隙的最小数目(默认:16)
Eth.TxPool GlobalSlots –txpool.globalslots value 所有账户的可执行事务时隙的最大数目(默认:4096)
Eth.TxPool AccountQueue –txpool.accountqueue value 每个账户许可的非可执行事务时隙的最大数目(默认:64)
Eth.TxPool GlobalQueue –txpool.globalqueue 所有账户的非可执行事务时隙的最大数目(默认:1024)
Eth.TxPool Lifetime –txpool.lifetime value 非可执行事务的排队最大时间(默认:3h0m0s)
Eth.GPO Blocks –gpoblocks value 检查gas价格的最新区块的数目(默认:10)
Eth.GPO Percentile –gpopercentile value 建议的gas价格是一组最新事务gas价格的百分位(默认:50)
Shh MaxMessageSize –shh.maxmessagesize value 可接受的最大信息大小(默认:1048576)
Shh MinimumAcceptedPOW –shh.pow value 可接受的最小POW(默认:0.2)
Node DataDir –datadir “/home/karalabe/.ethereum” databases和keystore的数据目录
Node IPCPath –ipcpath datadir里的IPC socket/pipe的文件名
Node HTTPPort –rpcport value HTTP-RPC服务监听端口(默认:8545)
Node HTTPVirtualHosts –rpcaddr value HTTP-RPC服务监听接口(默认:”localhost”)
Node HTTPModules null 经由HTTP RPC接口暴露的API modules列表
Node WSPort –wsport value WS-RPC 服务监听端口(默认:8546)
Node WSModules null 经由websocket RPC接口暴露的API modules列表,如果modules是空的,所有指向public的RPC API端点将会被暴露
Node.P2P MaxPeers –maxpeers value network peers的最大数目(如果设置为0,network失效)(默认:25)
Node.P2P NoDiscovery –nodiscover 使peer发现机制无效(手动peer添加)。这里设置为false,以便使用这个配置文件的新节点可以被发现
Node.P2P BootstrapNodes –bootnodes value 逗号分割的P2P discovery bootstrap enode URLs(对于 light servers,设置 v4+v5 代替)。将上面启动bootnodes时获取的enode URL替换IP后添加到这里
Node.P2P BootstrapNodesV5 –bootnodesv5 value 逗号分割的P2P v5 discovery bootstrap enode URLs(light server,light nodes)
Node.P2P StaticNodes null 配置作为static nodes的节点enode URLs列表
Node.P2P TrustedNodes null 配置作为trusted nodes的节点enode URLs列表
Node.P2P ListenAddr –port network监听端口(默认:30303)
Node.P2P EnableMsgEvents null 如果EnableMsgEvents被设置,服务器将发出PeerEvents,无论一个peer何时发送或接收一条信息
Dashboard Host null 启动dashboard服务的主机接口,如果这个域为空,则没有dashboard将被启动
Dashboard Port null 启动dashboard服务的TCP端口数字。默认0值是有效的,并将使用一个随机端口数字(用于临时节点)
Dashboard Refresh null 数据更新的刷新速率,chartEntry将被经常收集

在你的操作目录创建config文件夹,将写好的配置文件config.toml

同步

私链

操作

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
//创世区块配置文件
//genesis.json
{
"nonce": "0x0000000000000042",//nonce就是一个64位随机数用于挖矿
"difficulty": "0x020000", //设置当前区块的难度,如果难度过大,cpu挖矿就很难,这里设置较小难度
"mixhash": "0x0000000000000000000000000000000000000000000000000000000000000000",//与nonce配合用于挖矿
"coinbase": "0x0000000000000000000000000000000000000000",//矿工的账号
"timestamp": "0x00", //创世块的时间戳
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",//上一个区块的hash值,因创世块所以是0
"extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa",//附加信息
"gasLimit": "0x45c1e20beebc000",//该值设置对GAS的消耗总量限制
"config": {
"homesteadBlock": 0,
"eip155Block": 0,
"eip158Block": 0
},
"alloc": {
"9a926d9f8ee4a6a0853385ade430d61eb95cfc78":{
"balance": "1050000000030000000000000000"
},
"00a9f34ab4976053b996fb5bb3c56a77579bebbe":{
"balance": "30000000000000000000000000"
},
"d4df96d1bd86bf0c976aee416d182b590e60c44e":{
"balance": "30000000000000000000000000"
}
}
}

启动命令

1
2
3
4
5
6
7
8
9
10
11
geth init genesis.json

//默认为/用户目录/.ethereum目录
geth --rpc --rpcaddr 0.0.0.0 --rpcapi "net,web3,eth,personal" --dev


--dev开发者模式
//使用POA共识网络,默认预分配一个开发者账户并且会自动开启挖矿
//开发者账号余额:1.15792089237316195423570985008687907853269984665640564039457584007913129639927e+77
//--dev.period value 开发者模式下挖矿周期 (0 = 仅在交易时) (默认: 0,在有交易时才会挖矿)
//内存上存储,不持久化磁盘
  • --identity:指定节点 ID;
  • --rpc:表示开启 HTTP-RPC 服务;
  • --rpcaddr:HTTP-RPC 服务ip地址;
  • --rpcport:指定 HTTP-RPC 服务监听端口号(默认为 8545);
  • --datadir:指定区块链数据的存储位置;
  • --port:指定和其他节点连接所用的端口号(默认为 30303);
  • --nodiscover:关闭节点发现机制,防止加入有同样初始配置的陌生节点。
  • --networkid:设置当前区块链的网络ID,用于区分不同的网络,是一个数字

全量同步

获取区块的header&body

从一开始的创始块开始校验每一个元素,需要下载所有区块数据信息。

操作

  • 修改config.tomlSyncMode = "full",后执行geth --config config.toml
  • 或者命令参数覆盖配置,geth --syncmode full

可适当附带以下参数

--cache 1024 --targetgaslimit 1500000 --gasprice 20000000000

cache默认16M,建议设置为1G(1024)~2G(2048)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
这种模式通过下载区块头和区块体来构建区块链
Imported new chain segment blocks=2 txs=323

//insertChain 插入区块
同步的过程就和普通的区块插入的过程一样
包括区块头的验证,交易的验证,交易执行,账户状态的改变等操作维护出一系列的状态和收据


// 验证区块头是有效的.
abort, results := bc.engine.VerifyHeaders(bc, headers, seals)

//验证区块体
err = bc.Validator().ValidateBody(block)

// 处理区块,生成交易,收据,日志等信息.
// 实际上调用了state_processor.go 里面的 Process方法.
receipts, logs, usedGas, err := bc.processor.Process(block, state, bc.vmConfig)

// 再次验证,验证状态是否合法
err = bc.Validator().ValidateState(block, parent, state, receipts, usedGas)

// 写入区块和状态还有收据
status, err := bc.WriteBlockAndState(block, receipts, state)

轻量级同步

light模式,仅获取当前状态。验证元素需要向full节点发起相应的请求。

操作

  • 修改config.tomlSyncMode = "light",后执行geth --config config.toml
  • 或者命令参数覆盖配置,geth --syncmode light,其他配置则按照默认属性来
1
2
3
4
5
6
7
8
轻量级同步操作导入区块头部
Imported new block headers


//InsertHeaderChain 插入头部区块链
后期请求转向fullnode之后
收据和交易,fullnode只会返回Merkle树的路径和路径其他节点的信息
lightnode会根据这些路径信息对比之前下载的header中root验证数据的准确性

快速同步

获取区块的header&body在同步到当前块-1024之前不处理任何事务。

此后full节点一样进行后面的同步操作。

操作

  • 修改config.tomlSyncMode = "fast",后执行geth --config config.toml
  • 或者命令参数覆盖配置,geth --syncmode fast,其他配置则按照默认属性来
1
2
3
4
5
6
7
8
9
10
快速同步操作导入区块头部,区块收据Merkle树和区块状态MPT树
Imported new block headers
Imported new block receipts
Imported new state entries


//经过一系列的fetch函数:fetch header、fetch body、 fetch receipt、process header、syncState
//InsertHeaderChain 插入头部区块链
//InsertReceiptChain 插入收据链
//rawdb.WriteFastTrieProgress(stateDB,syncStatsState) 写入stateDb

https://github.com/ethereum/go-ethereum/pull/1889

概要:

  • 与原有的同步类似,下载组成区块链的区块头和区块体

  • 类似于原有的同步,验证区块头的一致性(POW,总难度等)

  • 下载由区块头定义的交易收据而非处理区块。

  • 存储下载的区块链,沿着交易收据链启用所有历史查询

  • 当链条达到最近的状态(头部 - 1024个块)时,暂停状态同步:

    • 获取由中心节点定义的区块的完整的MPT状态树
    • 对于MPT状态树里面的每个账户,获取他的合约代码和中间存储的Trie
  • MPT状态树下载成功后,将中心节点定义的区块作为当前的区块头
  • 通过像原有的同步一样对其进行完全处理,导入所有剩余的块(1024)
1
2
通过下载和验证整个头部链,我们可以保证传统同步的所有安全性,头部中包含的哈希(收据,状态尝试等)是有效的。 基于这些哈希,我们可以自信地下载交易收据和整个状态树。 
另外,通过将中心节点(快速同步切换到传统区块同步)放置在当前区块头(1024块)的下方一点

以上为2015年10月22日的merge提交

1
2
3
4
5
minCheckedHeaders    = 1024
minFullBlocks = 1024
头部控制1024个块于新版已砍掉

fsMinFullBlocks = 64

后期最新的变成到同步当前的区块节点blockNumber(低于任何快速同步节点block - 64个块)

Web3

事件

blocks 2.4 million to 2.7 million

以太坊(ETH)网络遭受垃圾交易攻击属于DDoS (Transaction spam attak)。矿工和节点需要花费很长的时间(20-60秒)来处理一些区块。因为处理事务需要大约50,000个磁盘提取

造成这次攻击的原因是一个EXTCODESIZE 操作码,它具有相当低的gas价格,需要节点从磁盘中读取状态信息。攻击交易调用此操作码的频率大约是50000次每区块。

防攻击方案

  • 缓存调整 --cache 1024
  • 设置挖矿最低gas限制--targetgaslimit
  • 挖矿接受交易的最低gas价格gasprice
  • 增加需要读取帐户状态(SLOAD,EXTCODESIZE,CALL等)的操作码的气体成本,至少500可能就足够了

数据结构

基础结构

Trie树,字典树

Patricia Trie树

又被称为Radix Tree或紧凑前缀树(compact prefix tree)

Merkle树

也叫Hash Tree

image

Merkle Patricia Tree树

  • 空节点:空节点用来表示空串。

  • 叶子节点(leaf):表示为[key,value]的一个键值对,其中key是key的一种特殊十六进制编码,value是value的RLP编码。

  • 扩展节点(extension);也是[key,value]的一个键值对,但是这里的value是其他节点的hash值,这个hash可以被用来查询数据库中的节点。也就是说通过hash链接到其他节点。
  • 分支节点(branch);因为MPT树中的key被编码成一种特殊的16进制的表示,再加上最后的value,所以分支节点是一个长度为17的list,前16个元素对应着key中的16个可能的十六进制字符,如果有一个[key,value]对在这个分支节点终止,最后一个元素代表一个值,即分支节点既可以搜索路径的终止也可以是路径的中间节点。

image-20180902162458572

key编码

三种编码方式分别为:

  1. Raw编码(原生的字符)
  2. Hex编码(扩展的16进制编码)
  3. Hex-Prefix编码(16进制前缀编码)
Raw编码

Raw编码就是原生的key值,不做任何改变。这种编码方式的key,是MPT对外提供接口的默认编码方式。

例如一条key为“cat”,value为“dog”的数据项,其Raw编码就是[‘c’, ‘a’, ‘t’],换成ASCII表示方式就是[63, 61, 74]

Hex编码

在介绍分支节点的时候,我们介绍了,为了减少分支节点孩子的个数,需要将key的编码进行转换,将原key的高低四位分拆成两个字节进行存储。这种转换后的key的编码方式,就是Hex编码。

从Raw编码向Hex编码的转换规则是:

  • 将Raw编码的每个字符,根据高4位低4位拆成两个字节;
  • 若该Key对应的节点存储的是真实的数据项内容(即该节点是叶子节点),则在末位添加一个ASCII值为16的字符作为终止标志符;
  • 若该key对应的节点存储的是另外一个节点的哈希索引(即该节点是扩展节点),则不加任何字符;

key为“cat”, value为“dog”的数据项,其Hex编码为[3, 15, 3, 13, 4, 10, 16]

Hex编码用于对内存中MPT树节点key进行编码

区块结构

img

img

以太坊的区块头包含三颗MPT树

1
2
3
4
三棵树求取根哈希,可以得到 区块头中的StateRoot,TransactionsRoot,ReceiptsRoot三个字段。
这样就建立了交易和区块头字段的映射。
当其他用户收到块根据块里的交易可以计算出收据和状态,
计算三个根哈希后和区块头的三个字段进行验证,判断这是否为合法的块。

其中交易树和收据树是Merkle树,状态树是Merkle Patricia Tree

交易树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
交易为:外部账户可以创建交易,用自己的私钥进行签名之发送消息给另一个外部账户或合约账户。
两个外部账户之间传送的消息即为转账操作。
从外部账户到合约账户的消息会激活合约账户的代码,执行各种操作,也就是我们常说的调用智能合约。
可以通过向0地址发起交易来创建合约账户。
交易包含以下主要字段:
Type:交易的类型,
ContractCreation(创建合约)还是MessageCall(调用合约或转账)
Nonce: 发送地址的交易计数
Value: 向目标账户发送的金额
ReceiveAddress:接受方地址
GasPrice:为交易付出的Gas价格
Gas:为交易付出的Gas
Data:交易的附加数据
VRS:交易签名结构体

收据树

收据树(交易执行过程中的一些数据)

1
2
3
4
5
6
7
8
9
10
11
12
收据为:账户创建交易并向其它节点广播后,会被其它节点执行并放入准备打包的区块。在这个过程中会生成一个收据。
收据的主要字段有:
blockHash: 交易所在块的哈希值
blockNumber: 交易在块的序号
transactionHash: 交易的哈希值
transactionIndex: 交易在块中的序号
from: 发送者地址
to: 接受者地址,为空时候表示创建合约
cumulativeGasUsed: 执行完此交易时候,块内消耗的总的gas值
gasUsed:本交易所消耗的gas
contractAddress: 当此交易为创建合约时,表示所创建合约的地址,否则为空
logs: 此交易的日志

状态树

状态树(账号信息, 合约账户和用户账户)

statetransition.png

1
2
3
4
5
6
7
以太坊是基于状态的。多个账户的状态共同组成了以太坊的全局状态。
账户分为两种:外部账户(Externally owned account),被私钥控制且没有任何代码与之关联。
一个外部账户可以创建交易,来发送消息给另一个外部账户或合约账户
以此来触发转账交易和智能合约的调用、创建

合约账户(Contract account)被它们的合约代码控制且有代码与之关联。
合约账户不可以自己发起一个交易,只能被外部账户调用

img

1
2
3
4
5
6
7
每个账户包含了以下的字段:
Balance:该账户的余额Nonce:该账户为外部账户时候,表示该账户创建的交易序号,每做一次交易都会加1。
该账户为合约账户时候,表示该账户创建的合约序号,每创建一次会加1。

CodeHash:该账户为合约账户时候,表示合约的哈希值,否则为空字符串的哈希

StorageRoot:该账户的存储内容组成Merkle树后求得的根哈希值

img

其中交易树和收据树是Merkle树,如上图所示。状态树是Merkle Patricia Tree

以上状态树最新状态会呈现在最近区块中,状态不变的则在以往区块中存储

网络

1
2
3
4
var (
MainnetGenesisHash = common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3") // Mainnet genesis hash to enforce below configs on
TestnetGenesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d") // Testnet genesis hash to enforce below configs on
)

绝大多数人在使用的网络被称为主网络(Mainnet),用户在其上交易、构建智能合约,矿工在其上挖矿。

由于使用的人数众多,主网络的鲁棒性很强,能够对抗攻击,区块链也不易被篡改,因此主网络是具有功能的,其上的以太币是有价值的。

于是出于测试和学习的目的,便会有一小部分节点,使用与主网络不同的创世区块,开启一条全新的区块链,并在上面挖矿和测试,这就是测试网络(Testnet)

以太坊公开的测试网络共有4个,目前仍在运行的有3个。

每个网络都有自己的创世区块和名字,按开始运行时间的早晚

  • Morden(已退役)

    自2015年7月开始运行。到2016年11月时,由于难度炸弹已经严重影响出块速度,不得不退役,重新开启一条新的区块链。Morden的共识机制为PoW

  • Ropsten(区块链浏览器)

目前仍在运行,共识机制为PoW。测试网络上的以太币并无实际价值,因此Ropsten的挖矿难度很低,目前在755M左右,仅仅只有主网络的0.07%。这样低的难度一方面使一台普通笔记本电脑的CPU也可以挖出区块,获得测试网络上的以太币,方便开发人员测试软件,但是却不能阻止攻击。

2017年2月,Ropsten便遭到了一次利用测试网络的低难度进行的攻击,攻击者发送了千万级的垃圾交易,并逐渐把区块Gas上限从正常的4,700,000提高到了90,000,000,000,

  • Kovan(区块链浏览器)

    为了解决测试网络中PoW共识机制的问题,以太坊钱包Parity的开发团队发起了一个新的测试网络Kovan。Kovan使用了权威证明(Proof-of-Authority)的共识机制,简称PoA。

  • Rinkeby(区块链浏览器)

Rinkeby也是以太坊官方提供的测试网络,使用PoA共识机制。与Kovan不同,以太坊团队提供了Rinkeby的PoA共识机制说明文档,理论上任何以太坊钱包都可以根据这个说明文档,支持Rinkeby测试网络,目前Rinkeby已经开始运行。

Rinkeby可通过水龙头获取以太币,有github账号+时间期限限制

网络映射

nat是网络地址转换,nat下面有upnp和pmp两种网络协议。

以上协议可以把内网的IP+端口 映射为路由器的IP+端口,等于内网的程序有了外网的IP

公网的用户可以直接进行访问了。

但是这类场景限制在你的上层网关有运营商协商好的公网IP分配,否则你上层网络可能还在某一局域网络内

UDP打洞

natp是内部机器通过路由器也就是网关向外部发送网络请求时,路由器记住内部机器的ip和端口

同时跟真正发送数据的外网端口绑定,产生一个临时映射表,当收到外网数据以后通过这个映射表将数据转发给内部机器。

这个临时映射表时效性在几分钟到几个小时不等,看路由器而决定,natp必须先有内部机器向外部发起请求才会产生

image

NAT设备的类型对于TCP穿越NAT,有着十分重要的影响,根据端口映射方式,NAT可分为如下4类,前3种NAT类型可统称为cone类型。

  (1)全克隆( Full Cone) : NAT把所有来自相同内部IP地址和端口的请求映射到相同的外部IP地址和端口。任何一个外部主机均可通过该映射发送IP包到该内部主机。

  (2)限制性克隆(Restricted Cone) : NAT把所有来自相同内部IP地址和端口的请求映射到相同的外部IP地址和端口。但是,只有当内部主机先给IP地址为X的外部主机发送IP包,该外部主机才能向该内部主机发送IP包。

  (3)端口限制性克隆( Port Restricted Cone) :端口限制性克隆与限制性克隆类似,只是多了端口号的限制,即只有内部主机先向IP地址为X,端口号为P的外部主机发送1个IP包,该外部主机才能够把源端口号为P的IP包发送给该内部主机。

  (4)对称式NAT ( Symmetric NAT) :这种类型的NAT与上述3种类型的不同,在于当同一内部主机使用相同的端口与不同地址的外部主机进行通信时, NAT对该内部主机的映射会有所不同。对称式NAT不保证所有会话中的私有地址和公开IP之间绑定的一致性。相反,它为每个新的会话分配一个新的端口号。

节点协作

在 P2P 文件共享的发展史上,出现过3种不同的技术路线(三代)。   

第1代   

采用【中央服务器】的模式——每个节点都需要先连接到中央服务器,然后才能查找到自己想要的在哪里。

这种技术的最大缺点是——中央服务器成为整个 P2P 网络的【单点故障】

第2代   

采用【广播】的模式——要找资料的时候,每个节点都向自己相连的【所有节点】进行询问;

被询问的节点如果不知道这个文件在哪里,就再次进行“广播”……如此往复,直至找到所需文件。   

这种技术的最大缺点是——会引发“广播风暴”并严重占用网络带宽,也会严重消耗节点的系统资源。

第3代   

DHT 不但避免了第一代技术的【单点故障】,也避免了第二代技术的【广播风暴】。

1
2
3
4
5
DHT拿数据的散列值+节点ID作为 key,数据本身作为value。
为防止hash碰撞,DHT都会采用大于等于 128 比特的散列值


拓扑结构+路由算法+距离算法以及数据定位(put,get)

Kad协议

实际应用的 DHT 大部分都采用 Kad 及其变种

拓扑结构

一颗二叉树,所有key节点信息都被当作一颗二叉树的叶子

并且每一个节点的位置都由其 ID 值的最短前缀唯一的确定。

Kad 使用 160 比特的散列算法如SHA1

1
2
3
4
5
即使有几百万个节点,相比keyspace(2160)也只是很小很小很小的一个子集,key 的分布是【高度随机】的。

因此也是【高度离散】的——任何两个 key都【不会】非常临近。   

所以使用“最短唯一前缀”来处理 key 的二进制形式,得到的结果就会很短(远远小于 160 个数位)

距离算法

节点 0011 通过连续查询来找到节点 1110

判断两个节点 x,y 的距离远近是基于数学上的异或的二进制运算,d(x,y) = x⊕y,既 对应位相同时结果为 0,不同时结果为 1。

1
2
3
4
5
6
	010101 

XOR 110001

-------------------
100100

则这两个节点的距离为 32+4=36。

路由算法

对每一个节点,都可以【按照自己的视角】对整个二叉树进行拆分。 拆分的规则是:先从根节点开始,把【不包含】自己的那个子树拆分出来;然后在剩下的子树再拆分不包含自己的下一层子树;以此类推,直到最后只剩下自己。

image

K桶

每个节点在完成子树拆分后,只需要知道每个子树里面的一个节点,在每个网络节点上存储每个子树的一个节点就足以实现全遍历

但是考虑分布式系统的节点是动态变化,光知道一个显然是不够滴,需要知道多个才比较保险。所以形成了K桶结构

image

以以太坊为例,有256位的散列所以共256个K桶,而且每个K桶包含16个节点。

K-桶(K-bucket)的刷新机制

    1. 主动收集节点   
    1
    任何节点都可以主动发起“查询节点”的请求(对应于协议类型 FIND_NODE),从而刷新 K 桶中的节点信息(下面聊“节点的加入”时,会提及这种)
    1. 被动收集节点   
      1
      如果收到其它节点发来的请求(协议类型 FIND_NODE 或 FIND_VALUE),会把对方的 ID 加入自己的某个 K 桶中。
    1. 探测失效节点   
      1
      Kad 还是支持一种探测机制(协议类型 PING),可以判断某个 ID 的节点是否在线。因此就可以定期探测路由表中的每一个节点,然后把下线的节点从路由表中干掉。
节点搜索
  • 1:任何一个新来的节点(假设叫 A),需要先跟 DHT 中已有的任一节点(假设叫 B)建立连接。   
  • 2:A 随机生成一个散列值作为自己的 ID(对于足够大的散列值空间,ID 相同的概率忽略不计)   
  • 3:A 向 B 发起一个查询请求(协议类型 FIND_NODE),请求的 ID 是自己(通俗地说,就是查询自己)   
  • 4:B 收到该请求之后,(如前面所说)会先把 A 的 ID 加入自己的某个 K 桶中。   然后,根据 FIND_NODE 协议的约定,B 会找到【K个】最接近 A 的节点,并返回给 A。   (B 怎么知道哪些节点接近 A 捏?这时候,【用 XOR 表示距离】的算法就发挥作用啦)   
  • 5:A 收到这 K 个节点的 ID 之后,(仅仅根据这批 ID 的值)就可以开始初始化自己的 K 桶。   
  • 6:然后 A 会继续向刚刚拿到的这批节点发送查询请求(协议类型 FIND_NODE),如此往复(递归),直至 A 建立了足够详细的路由表。




共识算法

共识算法的选择与应用场景高度相关

可信环境使用paxos 或者raft
带许可的联盟可使用pbft
非许可链可以是pow,pos,ripple共识等

根据对手方信任度分级,自由选择共识机制,这样才是真的最优

共识相关具体问题如下?

  • 具体使用哪种算法选择出块节点(PoW与PoS之争)?
  • 哪些节点在接收到数据块时该如何验证(PoS与DPoS之争)?
  • 节点之间的数据以什么方式进行传播(DAG与链式结构之争)?
  • 以及如何确保一条交易被大多数参与节点所接受(PBFT、Paxos、RAFT)?
  • 以及各种分叉解决方案等算法之争?

POW

工作量证明(Proof of Work)

  • 矿工解答谜题以“挖出”一个区块并加入到区块链上。
  • 这一个过程要求大量的电力和运算。在系统中,这些谜题已经被设计成艰难而又繁重。
  • 当一个矿工解决一个谜题的时候,他们发布他们的区块到网络上接受验证。
  • 验证一个区块是否属于一条链是一个非常简单的过程。

解决谜题是困难的,但检查答案是否正确则是容易的。
它也是比特币(Bitcoin)和Ethereum(直到现在)一直在用的系统。

矿池可以联合起来并在比特币网络上发动51%攻击。
所以以太坊开始关注权益证明

POS

权益证明:将让整个挖矿过程虚拟化,并以验证者作为出块节点。

1
2
3
4
5
PoS更倾向于类似Raft投票机制,通过固定时间协调所有节点参与投票
根据某种规则(例如持有代币数量、或提供存储空间大小等)
判断每个节点的权重,最后选取权重最高的节点作为检查点节点。
而在数据库一致性选择的Raft算法中,普遍会根据最新事务号作为权重
多个节点之间优先选择包含最新事务记录的节点作为主节点。

流程

  • 验证者必须锁定一些他们拥有的币作为保证金。
  • 在此之后,他们将开始验证区块。同时,当他们发现一个他们认为可以被加到链上的区块时,他们会通过下赌注来验证它。
  • 如果该区块成功上链,验证者就将得到一个与他们的赌注成比例的奖励。

缺点

如果出现分叉,红蓝两条链
如果你是一个验证者,你可以简单地把钱投到红蓝两条链上
完全无需担心间接的不良后果。不管发生什么事,你都总不会失去任何东西,不管你的行为有多恶意

所以引出Casper

Casper

一种基于保证金的经济激励共识协议
Csaper是以太坊选择实行的PoS协议

  • 协议中的节点,作为“锁定保证金的验证人(bonded validators)”

必须先缴纳保证金(这一步叫做锁定保证金,”bonding”)才可以参与出块和共识形成

  • 如果一个验证人作出了任何Casper认为“无效”的事情,他的保证金将被罚没,出块和参与共识的权利也会被取消。

如何工作?

权益证明在Casper下是如何工作的

  • 验证者押下一定比例的他们拥有的以太币作为保证金。
  • 然后他们将开始验证区块。也就是说,当他们发现一个可以他们认为可以被加到链上的区块的时候,他们将以通过押下赌注来验证它。
  • 如果该区块被加到链上,然后验证者们将得到一个跟他们的赌注成比例的奖励。
  • 但是如果一个验证者采用一种恶意的方式行动、试图做“无利害关系”的事,他们将立即遭到惩罚,他们所有的权益都会被砍掉。

参考