Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
whqwjb
go-ethereum
提交
a87089fd
G
go-ethereum
项目概览
whqwjb
/
go-ethereum
与 Fork 源项目一致
从无法访问的项目Fork
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
G
go-ethereum
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
a87089fd
编写于
7月 08, 2016
作者:
P
Péter Szilágyi
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
cmd, core, miner: add extradata validation to consensus rules
上级
1e24c2e4
变更
7
隐藏空白更改
内联
并排
Showing
7 changed file
with
275 addition
and
216 deletion
+275
-216
cmd/geth/dao_test.go
cmd/geth/dao_test.go
+110
-184
cmd/geth/genesis_test.go
cmd/geth/genesis_test.go
+2
-1
cmd/utils/flags.go
cmd/utils/flags.go
+26
-27
core/block_validator.go
core/block_validator.go
+21
-0
core/block_validator_test.go
core/block_validator_test.go
+105
-0
core/config.go
core/config.go
+3
-2
miner/worker.go
miner/worker.go
+8
-2
未找到文件。
cmd/geth/dao_test.go
浏览文件 @
a87089fd
...
...
@@ -29,7 +29,8 @@ import (
"github.com/ethereum/go-ethereum/params"
)
var
daoNoForkGenesis
=
`{
// Genesis block for nodes which don't care about the DAO fork (i.e. not configured)
var
daoOldGenesis
=
`{
"alloc" : {},
"coinbase" : "0x0000000000000000000000000000000000000000",
"difficulty" : "0x20000",
...
...
@@ -38,214 +39,140 @@ var daoNoForkGenesis = `{
"nonce" : "0x0000000000000042",
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp" : "0x00"
"timestamp" : "0x00",
"config" : {}
}`
var
daoNoForkGenesisHash
=
common
.
HexToHash
(
"5e1fc79cb4ffa4739177b5408045cd5d51c6cf766133f23f7cd72ee1f8d790e0"
)
var
daoProForkGenesis
=
`{
// Genesis block for nodes which actively oppose the DAO fork
var
daoNoForkGenesis
=
`{
"alloc" : {},
"coinbase" : "0x0000000000000000000000000000000000000000",
"difficulty" : "0x20000",
"extraData" : "",
"gasLimit" : "0x2fefd8",
"nonce" : "0x000000000000004
3
",
"nonce" : "0x000000000000004
2
",
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp" : "0x00",
"config" : {
"daoForkBlock": 314
"daoForkBlock" : 314,
"daoForkSupport" : false
}
}`
var
daoProForkGenesisHash
=
common
.
HexToHash
(
"c80f3c1c3d81ae6d8ea59edf35d3e4b723e4c8684ec71fdb6d4715e3f8add237"
)
var
daoProForkBlock
=
big
.
NewInt
(
314
)
// Tests that creating a new node to with or without the DAO fork flag will correctly
// set the genesis block but with DAO support explicitly set or unset in the chain
// config in the database.
func
TestDAOSupportMainnet
(
t
*
testing
.
T
)
{
testDAOForkBlockNewChain
(
t
,
false
,
""
,
true
,
params
.
MainNetDAOForkBlock
)
}
func
TestDAOSupportTestnet
(
t
*
testing
.
T
)
{
testDAOForkBlockNewChain
(
t
,
true
,
""
,
true
,
params
.
TestNetDAOForkBlock
)
}
func
TestDAOSupportPrivnet
(
t
*
testing
.
T
)
{
testDAOForkBlockNewChain
(
t
,
false
,
daoProForkGenesis
,
false
,
daoProForkBlock
)
}
func
TestDAONoSupportMainnet
(
t
*
testing
.
T
)
{
testDAOForkBlockNewChain
(
t
,
false
,
""
,
false
,
nil
)
}
func
TestDAONoSupportTestnet
(
t
*
testing
.
T
)
{
testDAOForkBlockNewChain
(
t
,
true
,
""
,
false
,
nil
)
}
func
TestDAONoSupportPrivnet
(
t
*
testing
.
T
)
{
testDAOForkBlockNewChain
(
t
,
false
,
daoNoForkGenesis
,
false
,
nil
)
}
func
testDAOForkBlockNewChain
(
t
*
testing
.
T
,
testnet
bool
,
genesis
string
,
fork
bool
,
expect
*
big
.
Int
)
{
// Create a temporary data directory to use and inspect later
datadir
:=
tmpdir
(
t
)
defer
os
.
RemoveAll
(
datadir
)
// Start a Geth instance with the requested flags set and immediately terminate
if
genesis
!=
""
{
json
:=
filepath
.
Join
(
datadir
,
"genesis.json"
)
if
err
:=
ioutil
.
WriteFile
(
json
,
[]
byte
(
genesis
),
0600
);
err
!=
nil
{
t
.
Fatalf
(
"failed to write genesis file: %v"
,
err
)
}
runGeth
(
t
,
"--datadir"
,
datadir
,
"init"
,
json
)
.
cmd
.
Wait
()
}
execDAOGeth
(
t
,
datadir
,
testnet
,
fork
,
false
)
// Retrieve the DAO config flag from the database
path
:=
filepath
.
Join
(
datadir
,
"chaindata"
)
if
testnet
{
path
=
filepath
.
Join
(
datadir
,
"testnet"
,
"chaindata"
)
}
db
,
err
:=
ethdb
.
NewLDBDatabase
(
path
,
0
,
0
)
if
err
!=
nil
{
t
.
Fatalf
(
"failed to open test database: %v"
,
err
)
// Genesis block for nodes which actively support the DAO fork
var
daoProForkGenesis
=
`{
"alloc" : {},
"coinbase" : "0x0000000000000000000000000000000000000000",
"difficulty" : "0x20000",
"extraData" : "",
"gasLimit" : "0x2fefd8",
"nonce" : "0x0000000000000042",
"mixhash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"parentHash" : "0x0000000000000000000000000000000000000000000000000000000000000000",
"timestamp" : "0x00",
"config" : {
"daoForkBlock" : 314,
"daoForkSupport" : true
}
defer
db
.
Close
()
}`
genesisHash
:=
common
.
HexToHash
(
"0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"
)
if
testnet
{
genesisHash
=
common
.
HexToHash
(
"0x0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303"
)
}
else
if
genesis
==
daoNoForkGenesis
{
genesisHash
=
daoNoForkGenesisHash
}
else
if
genesis
==
daoProForkGenesis
{
genesisHash
=
daoProForkGenesisHash
}
config
,
err
:=
core
.
GetChainConfig
(
db
,
genesisHash
)
if
err
!=
nil
{
t
.
Fatalf
(
"failed to retrieve chain config: %v"
,
err
)
}
// Validate the DAO hard-fork block number against the expected value
if
config
.
DAOForkBlock
==
nil
{
if
expect
!=
nil
{
t
.
Fatalf
(
"dao hard-fork block mismatch: have nil, want %v"
,
expect
)
}
}
else
if
config
.
DAOForkBlock
.
Cmp
(
expect
)
!=
0
{
t
.
Fatalf
(
"dao hard-fork block mismatch: have %v, want %v"
,
config
.
DAOForkBlock
,
expect
)
}
}
var
daoGenesisHash
=
common
.
HexToHash
(
"5e1fc79cb4ffa4739177b5408045cd5d51c6cf766133f23f7cd72ee1f8d790e0"
)
var
daoGenesisForkBlock
=
big
.
NewInt
(
314
)
// Tests that
starting up an already existing node with various DAO fork override
//
flags correctly changes the chain configs in the database
.
// Tests that
the DAO hard-fork number and the nodes support/opposition is correctly
//
set in the database after various initialization procedures and invocations
.
func
TestDAODefaultMainnet
(
t
*
testing
.
T
)
{
testDAOForkBlockOldChain
(
t
,
false
,
""
,
false
,
false
,
false
,
false
,
nil
)
}
func
TestDAOStartSupportMainnet
(
t
*
testing
.
T
)
{
testDAOForkBlockOldChain
(
t
,
false
,
""
,
false
,
true
,
false
,
false
,
params
.
MainNetDAOForkBlock
)
}
func
TestDAOContinueExplicitSupportMainnet
(
t
*
testing
.
T
)
{
testDAOForkBlockOldChain
(
t
,
false
,
""
,
true
,
true
,
false
,
false
,
params
.
MainNetDAOForkBlock
)
}
func
TestDAOContinueImplicitSupportMainnet
(
t
*
testing
.
T
)
{
testDAOForkBlockOldChain
(
t
,
false
,
""
,
true
,
false
,
false
,
false
,
params
.
MainNetDAOForkBlock
)
testDAOForkBlockNewChain
(
t
,
false
,
""
,
[][
2
]
bool
{{
false
,
false
}},
params
.
MainNetDAOForkBlock
,
false
)
}
func
TestDAOSwitchSupportMainnet
(
t
*
testing
.
T
)
{
testDAOForkBlockOldChain
(
t
,
false
,
""
,
false
,
true
,
true
,
false
,
params
.
MainNetDAOForkBlock
)
}
func
TestDAOStartOpposeMainnet
(
t
*
testing
.
T
)
{
testDAOForkBlockOldChain
(
t
,
false
,
""
,
false
,
false
,
false
,
true
,
nil
)
func
TestDAOSupportMainnet
(
t
*
testing
.
T
)
{
testDAOForkBlockNewChain
(
t
,
false
,
""
,
[][
2
]
bool
{{
true
,
false
}},
params
.
MainNetDAOForkBlock
,
true
)
}
func
TestDAO
ContinueExplicit
OpposeMainnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
false
,
""
,
false
,
false
,
true
,
true
,
nil
)
func
TestDAOOpposeMainnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
false
,
""
,
[][
2
]
bool
{{
false
,
true
}},
params
.
MainNetDAOForkBlock
,
false
)
}
func
TestDAO
ContinueImplicitOppose
Mainnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
false
,
""
,
false
,
false
,
true
,
false
,
nil
)
func
TestDAO
SwitchToSupport
Mainnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
false
,
""
,
[][
2
]
bool
{{
false
,
true
},
{
true
,
false
}},
params
.
MainNetDAOForkBlock
,
true
)
}
func
TestDAOSwitchOpposeMainnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
false
,
""
,
true
,
false
,
false
,
true
,
nil
)
func
TestDAOSwitch
To
OpposeMainnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
false
,
""
,
[][
2
]
bool
{{
true
,
false
},
{
false
,
true
}},
params
.
MainNetDAOForkBlock
,
false
)
}
func
TestDAODefaultTestnet
(
t
*
testing
.
T
)
{
testDAOForkBlockOldChain
(
t
,
true
,
""
,
false
,
false
,
false
,
false
,
nil
)
}
func
TestDAOStartSupportTestnet
(
t
*
testing
.
T
)
{
testDAOForkBlockOldChain
(
t
,
true
,
""
,
false
,
true
,
false
,
false
,
params
.
TestNetDAOForkBlock
)
}
func
TestDAOContinueExplicitSupportTestnet
(
t
*
testing
.
T
)
{
testDAOForkBlockOldChain
(
t
,
true
,
""
,
true
,
true
,
false
,
false
,
params
.
TestNetDAOForkBlock
)
}
func
TestDAOContinueImplicitSupportTestnet
(
t
*
testing
.
T
)
{
testDAOForkBlockOldChain
(
t
,
true
,
""
,
true
,
false
,
false
,
false
,
params
.
TestNetDAOForkBlock
)
}
func
TestDAOSwitchSupportTestnet
(
t
*
testing
.
T
)
{
testDAOForkBlockOldChain
(
t
,
true
,
""
,
false
,
true
,
true
,
false
,
params
.
TestNetDAOForkBlock
)
testDAOForkBlockNewChain
(
t
,
true
,
""
,
[][
2
]
bool
{{
false
,
false
}},
params
.
TestNetDAOForkBlock
,
false
)
}
func
TestDAOS
tartOppose
Testnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
true
,
""
,
false
,
false
,
false
,
true
,
nil
)
func
TestDAOS
upport
Testnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
true
,
""
,
[][
2
]
bool
{{
true
,
false
}},
params
.
TestNetDAOForkBlock
,
true
)
}
func
TestDAO
ContinueExplicit
OpposeTestnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
true
,
""
,
false
,
false
,
true
,
true
,
nil
)
func
TestDAOOpposeTestnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
true
,
""
,
[][
2
]
bool
{{
false
,
true
}},
params
.
TestNetDAOForkBlock
,
false
)
}
func
TestDAO
ContinueImplicitOppose
Testnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
true
,
""
,
false
,
false
,
true
,
false
,
nil
)
func
TestDAO
SwitchToSupport
Testnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
true
,
""
,
[][
2
]
bool
{{
false
,
true
},
{
true
,
false
}},
params
.
TestNetDAOForkBlock
,
true
)
}
func
TestDAOSwitchOpposeTestnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
true
,
""
,
true
,
false
,
false
,
true
,
nil
)
func
TestDAOSwitch
To
OpposeTestnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
true
,
""
,
[][
2
]
bool
{{
true
,
false
},
{
false
,
true
}},
params
.
TestNetDAOForkBlock
,
false
)
}
func
TestDAO
Default
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
false
,
daoNoForkGenesis
,
false
,
false
,
false
,
false
,
nil
)
func
TestDAO
InitOld
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
false
,
daoOldGenesis
,
[][
2
]
bool
{},
nil
,
false
)
}
func
TestDAO
StartSupportCon
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
false
,
daoNoForkGenesis
,
false
,
true
,
false
,
false
,
params
.
MainNetDAOForkBlock
)
func
TestDAO
DefaultOld
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
false
,
daoOldGenesis
,
[][
2
]
bool
{{
false
,
false
}},
params
.
MainNetDAOForkBlock
,
false
)
}
func
TestDAO
ContinueExplicitSupportCon
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
false
,
daoNoForkGenesis
,
true
,
true
,
false
,
false
,
params
.
MainNetDAOForkBlock
)
func
TestDAO
SupportOld
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
false
,
daoOldGenesis
,
[][
2
]
bool
{{
true
,
false
}},
params
.
MainNetDAOForkBlock
,
true
)
}
func
TestDAO
ContinueImplicitSupportCon
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
false
,
daoNoForkGenesis
,
true
,
false
,
false
,
false
,
params
.
MainNetDAOForkBlock
)
func
TestDAO
OpposeOld
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
false
,
daoOldGenesis
,
[][
2
]
bool
{{
false
,
true
}},
params
.
MainNetDAOForkBlock
,
false
)
}
func
TestDAOSwitch
SupportCon
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
false
,
daoNoForkGenesis
,
false
,
true
,
true
,
false
,
params
.
MainNetDAOForkBlock
)
func
TestDAOSwitch
ToSupportOld
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
false
,
daoOldGenesis
,
[][
2
]
bool
{{
false
,
true
},
{
true
,
false
}},
params
.
MainNetDAOForkBlock
,
true
)
}
func
TestDAOS
tartOpposeCon
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
false
,
daoNoForkGenesis
,
false
,
false
,
false
,
true
,
nil
)
func
TestDAOS
witchToOpposeOld
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
false
,
daoOldGenesis
,
[][
2
]
bool
{{
true
,
false
},
{
false
,
true
}},
params
.
MainNetDAOForkBlock
,
false
)
}
func
TestDAO
ContinueExplicitOpposeCon
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
false
,
daoNoForkGenesis
,
false
,
false
,
true
,
true
,
nil
)
func
TestDAO
InitNoFork
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
false
,
daoNoForkGenesis
,
[][
2
]
bool
{},
daoGenesisForkBlock
,
false
)
}
func
TestDAO
ContinueImplicitOpposeCon
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
false
,
daoNoForkGenesis
,
false
,
false
,
true
,
false
,
nil
)
func
TestDAO
DefaultNoFork
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
false
,
daoNoForkGenesis
,
[][
2
]
bool
{{
false
,
false
}},
daoGenesisForkBlock
,
false
)
}
func
TestDAOS
witchOpposeCon
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
false
,
daoNoForkGenesis
,
true
,
false
,
false
,
true
,
nil
)
func
TestDAOS
upportNoFork
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
false
,
daoNoForkGenesis
,
[][
2
]
bool
{{
true
,
false
}},
daoGenesisForkBlock
,
true
)
}
func
TestDAO
DefaultPro
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
false
,
daoProForkGenesis
,
false
,
false
,
false
,
false
,
daoProForkBlock
)
func
TestDAO
OpposeNoFork
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
false
,
daoNoForkGenesis
,
[][
2
]
bool
{{
false
,
true
}},
daoGenesisForkBlock
,
false
)
}
func
TestDAOS
tartSupportPro
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
false
,
daoProForkGenesis
,
false
,
true
,
false
,
false
,
daoProForkBlock
)
func
TestDAOS
witchToSupportNoFork
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
false
,
daoNoForkGenesis
,
[][
2
]
bool
{{
false
,
true
},
{
true
,
false
}},
daoGenesisForkBlock
,
true
)
}
func
TestDAO
ContinueExplicitSupportPro
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
false
,
daoProForkGenesis
,
true
,
true
,
false
,
false
,
daoProForkBlock
)
func
TestDAO
SwitchToOpposeNoFork
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
false
,
daoNoForkGenesis
,
[][
2
]
bool
{{
true
,
false
},
{
false
,
true
}},
daoGenesisForkBlock
,
false
)
}
func
TestDAO
ContinueImplicitSupportPro
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
false
,
daoProForkGenesis
,
true
,
false
,
false
,
false
,
daoProForkBlock
)
func
TestDAO
InitProFork
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
false
,
daoProForkGenesis
,
[][
2
]
bool
{},
daoGenesisForkBlock
,
true
)
}
func
TestDAO
SwitchSupportPro
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
false
,
daoProForkGenesis
,
false
,
true
,
true
,
false
,
params
.
MainNetDAOForkBlock
)
func
TestDAO
DefaultProFork
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
false
,
daoProForkGenesis
,
[][
2
]
bool
{{
false
,
false
}},
daoGenesisForkBlock
,
true
)
}
func
TestDAOS
tartOpposePro
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
false
,
daoProForkGenesis
,
false
,
false
,
false
,
true
,
nil
)
func
TestDAOS
upportProFork
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
false
,
daoProForkGenesis
,
[][
2
]
bool
{{
true
,
false
}},
daoGenesisForkBlock
,
true
)
}
func
TestDAO
ContinueExplicitOpposePro
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
false
,
daoProForkGenesis
,
false
,
false
,
true
,
true
,
nil
)
func
TestDAO
OpposeProFork
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
false
,
daoProForkGenesis
,
[][
2
]
bool
{{
false
,
true
}},
daoGenesisForkBlock
,
false
)
}
func
TestDAO
ContinueImplicitOpposePro
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
false
,
daoProForkGenesis
,
false
,
false
,
true
,
false
,
nil
)
func
TestDAO
SwitchToSupportProFork
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
false
,
daoProForkGenesis
,
[][
2
]
bool
{{
false
,
true
},
{
true
,
false
}},
daoGenesisForkBlock
,
true
)
}
func
TestDAOSwitch
OpposePro
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
OldChain
(
t
,
false
,
daoProForkGenesis
,
true
,
false
,
false
,
true
,
nil
)
func
TestDAOSwitch
ToOpposeProFork
Privnet
(
t
*
testing
.
T
)
{
testDAOForkBlock
NewChain
(
t
,
false
,
daoProForkGenesis
,
[][
2
]
bool
{{
true
,
false
},
{
false
,
true
}},
daoGenesisForkBlock
,
false
)
}
func
testDAOForkBlock
OldChain
(
t
*
testing
.
T
,
testnet
bool
,
genesis
string
,
oldSupport
,
newSupport
,
oldOppose
,
newOppose
bool
,
expect
*
big
.
Int
)
{
func
testDAOForkBlock
NewChain
(
t
*
testing
.
T
,
testnet
bool
,
genesis
string
,
votes
[][
2
]
bool
,
expectBlock
*
big
.
Int
,
expectVote
bool
)
{
// Create a temporary data directory to use and inspect later
datadir
:=
tmpdir
(
t
)
defer
os
.
RemoveAll
(
datadir
)
//
Cycle two Geth instances, possibly changing fork support in between
//
Start a Geth instance with the requested flags set and immediately terminate
if
genesis
!=
""
{
json
:=
filepath
.
Join
(
datadir
,
"genesis.json"
)
if
err
:=
ioutil
.
WriteFile
(
json
,
[]
byte
(
genesis
),
0600
);
err
!=
nil
{
...
...
@@ -253,12 +180,23 @@ func testDAOForkBlockOldChain(t *testing.T, testnet bool, genesis string, oldSup
}
runGeth
(
t
,
"--datadir"
,
datadir
,
"init"
,
json
)
.
cmd
.
Wait
()
}
execDAOGeth
(
t
,
datadir
,
testnet
,
oldSupport
,
oldOppose
)
execDAOGeth
(
t
,
datadir
,
testnet
,
newSupport
,
newOppose
)
for
_
,
vote
:=
range
votes
{
args
:=
[]
string
{
"--port"
,
"0"
,
"--maxpeers"
,
"0"
,
"--nodiscover"
,
"--nat"
,
"none"
,
"--ipcdisable"
,
"--datadir"
,
datadir
}
if
testnet
{
args
=
append
(
args
,
"--testnet"
)
}
if
vote
[
0
]
{
args
=
append
(
args
,
"--support-dao-fork"
)
}
if
vote
[
1
]
{
args
=
append
(
args
,
"--oppose-dao-fork"
)
}
geth
:=
runGeth
(
t
,
append
(
args
,
[]
string
{
"--exec"
,
"2+2"
,
"console"
}
...
)
...
)
geth
.
cmd
.
Wait
()
}
// Retrieve the DAO config flag from the database
path
:=
filepath
.
Join
(
datadir
,
"chaindata"
)
if
testnet
{
if
testnet
&&
genesis
==
""
{
path
=
filepath
.
Join
(
datadir
,
"testnet"
,
"chaindata"
)
}
db
,
err
:=
ethdb
.
NewLDBDatabase
(
path
,
0
,
0
)
...
...
@@ -270,10 +208,9 @@ func testDAOForkBlockOldChain(t *testing.T, testnet bool, genesis string, oldSup
genesisHash
:=
common
.
HexToHash
(
"0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"
)
if
testnet
{
genesisHash
=
common
.
HexToHash
(
"0x0cd786a2425d16f152c658316c423e6ce1181e15c3295826d7c9904cba9ce303"
)
}
else
if
genesis
==
daoNoForkGenesis
{
genesisHash
=
daoNoForkGenesisHash
}
else
if
genesis
==
daoProForkGenesis
{
genesisHash
=
daoProForkGenesisHash
}
if
genesis
!=
""
{
genesisHash
=
daoGenesisHash
}
config
,
err
:=
core
.
GetChainConfig
(
db
,
genesisHash
)
if
err
!=
nil
{
...
...
@@ -281,26 +218,15 @@ func testDAOForkBlockOldChain(t *testing.T, testnet bool, genesis string, oldSup
}
// Validate the DAO hard-fork block number against the expected value
if
config
.
DAOForkBlock
==
nil
{
if
expect
!=
nil
{
t
.
Fatalf
(
"dao hard-fork block mismatch: have nil, want %v"
,
expect
)
if
expect
Block
!=
nil
{
t
.
Errorf
(
"dao hard-fork block mismatch: have nil, want %v"
,
expectBlock
)
}
}
else
if
config
.
DAOForkBlock
.
Cmp
(
expect
)
!=
0
{
t
.
Fatalf
(
"dao hard-fork block mismatch: have %v, want %v"
,
config
.
DAOForkBlock
,
expect
)
}
}
// execDAOGeth starts a Geth instance with some DAO forks set and terminates.
func
execDAOGeth
(
t
*
testing
.
T
,
datadir
string
,
testnet
bool
,
supportFork
bool
,
opposeFork
bool
)
{
args
:=
[]
string
{
"--port"
,
"0"
,
"--maxpeers"
,
"0"
,
"--nodiscover"
,
"--nat"
,
"none"
,
"--ipcdisable"
,
"--datadir"
,
datadir
}
if
testnet
{
args
=
append
(
args
,
"--testnet"
)
}
if
supportFork
{
args
=
append
(
args
,
"--support-dao-fork"
)
}
else
if
expectBlock
==
nil
{
t
.
Errorf
(
"dao hard-fork block mismatch: have %v, want nil"
,
config
.
DAOForkBlock
)
}
else
if
config
.
DAOForkBlock
.
Cmp
(
expectBlock
)
!=
0
{
t
.
Errorf
(
"dao hard-fork block mismatch: have %v, want %v"
,
config
.
DAOForkBlock
,
expectBlock
)
}
if
opposeFork
{
args
=
append
(
args
,
"--oppose-dao-fork"
)
if
config
.
DAOForkSupport
!=
expectVote
{
t
.
Errorf
(
"dao hard-fork support mismatch: have %v, want %v"
,
config
.
DAOForkSupport
,
expectVote
)
}
geth
:=
runGeth
(
t
,
append
(
args
,
[]
string
{
"--exec"
,
"2+2"
,
"console"
}
...
)
...
)
geth
.
cmd
.
Wait
()
}
cmd/geth/genesis_test.go
浏览文件 @
a87089fd
...
...
@@ -75,7 +75,8 @@ var customGenesisTests = []struct {
"timestamp" : "0x00",
"config" : {
"homesteadBlock" : 314,
"daoForkBlock" : 141
"daoForkBlock" : 141,
"daoForkSupport" : true
},
}`
,
query
:
"eth.getBlock(0).nonce"
,
...
...
cmd/utils/flags.go
浏览文件 @
a87089fd
...
...
@@ -798,43 +798,42 @@ func MustMakeChainConfig(ctx *cli.Context) *core.ChainConfig {
// MustMakeChainConfigFromDb reads the chain configuration from the given database.
func
MustMakeChainConfigFromDb
(
ctx
*
cli
.
Context
,
db
ethdb
.
Database
)
*
core
.
ChainConfig
{
// If the chain is already initialized, use any existing chain configs
config
:=
new
(
core
.
ChainConfig
)
if
genesis
:=
core
.
GetBlock
(
db
,
core
.
GetCanonicalHash
(
db
,
0
),
0
);
genesis
!=
nil
{
storedConfig
,
err
:=
core
.
GetChainConfig
(
db
,
genesis
.
Hash
())
if
err
==
nil
{
// Force override any existing configs if explicitly requested
switch
{
case
storedConfig
.
DAOForkBlock
==
nil
&&
ctx
.
GlobalBool
(
SupportDAOFork
.
Name
)
&&
ctx
.
GlobalBool
(
TestNetFlag
.
Name
)
:
storedConfig
.
DAOForkBlock
=
params
.
TestNetDAOForkBlock
case
storedConfig
.
DAOForkBlock
==
nil
&&
ctx
.
GlobalBool
(
SupportDAOFork
.
Name
)
:
storedConfig
.
DAOForkBlock
=
params
.
MainNetDAOForkBlock
case
ctx
.
GlobalBool
(
OpposeDAOFork
.
Name
)
:
storedConfig
.
DAOForkBlock
=
nil
}
return
storedConfig
}
else
if
err
!=
core
.
ChainConfigNotFoundErr
{
switch
err
{
case
nil
:
config
=
storedConfig
case
core
.
ChainConfigNotFoundErr
:
// No configs found, use empty, will populate below
default
:
Fatalf
(
"Could not make chain configuration: %v"
,
err
)
}
}
// If the chain is uninitialized nor no configs are present, create one
var
homesteadBlock
*
big
.
Int
if
ctx
.
GlobalBool
(
TestNetFlag
.
Name
)
{
homesteadBlock
=
params
.
TestNetHomesteadBlock
}
else
{
homesteadBlock
=
params
.
MainNetHomesteadBlock
// Set any missing fields due to them being unset or system upgrade
if
config
.
HomesteadBlock
==
nil
{
if
ctx
.
GlobalBool
(
TestNetFlag
.
Name
)
{
config
.
HomesteadBlock
=
new
(
big
.
Int
)
.
Set
(
params
.
TestNetHomesteadBlock
)
}
else
{
config
.
HomesteadBlock
=
new
(
big
.
Int
)
.
Set
(
params
.
MainNetHomesteadBlock
)
}
}
var
daoForkBlock
*
big
.
Int
if
config
.
DAOForkBlock
==
nil
{
if
ctx
.
GlobalBool
(
TestNetFlag
.
Name
)
{
config
.
DAOForkBlock
=
new
(
big
.
Int
)
.
Set
(
params
.
TestNetDAOForkBlock
)
}
else
{
config
.
DAOForkBlock
=
new
(
big
.
Int
)
.
Set
(
params
.
MainNetDAOForkBlock
)
}
}
// Force override any existing configs if explicitly requested
switch
{
case
ctx
.
GlobalBool
(
SupportDAOFork
.
Name
)
&&
ctx
.
GlobalBool
(
TestNetFlag
.
Name
)
:
daoForkBlock
=
params
.
TestNetDAOForkBlock
case
ctx
.
GlobalBool
(
SupportDAOFork
.
Name
)
:
daoForkBlock
=
params
.
MainNetDAOForkBlock
config
.
DAOForkSupport
=
true
case
ctx
.
GlobalBool
(
OpposeDAOFork
.
Name
)
:
daoForkBlock
=
nil
}
return
&
core
.
ChainConfig
{
HomesteadBlock
:
homesteadBlock
,
DAOForkBlock
:
daoForkBlock
,
config
.
DAOForkSupport
=
false
}
return
config
}
// MakeChainDatabase open an LevelDB using the flags passed to the client and will hard crash if it fails.
...
...
core/block_validator.go
浏览文件 @
a87089fd
...
...
@@ -17,6 +17,7 @@
package
core
import
(
"bytes"
"fmt"
"math/big"
"time"
...
...
@@ -247,6 +248,26 @@ func ValidateHeader(config *ChainConfig, pow pow.PoW, header *types.Header, pare
return
&
BlockNonceErr
{
header
.
Number
,
header
.
Hash
(),
header
.
Nonce
.
Uint64
()}
}
}
// DAO hard-fork extension to the header validity: a) if the node is no-fork,
// do not accept blocks in the [fork, fork+10) range with the fork specific
// extra-data set; b) if the node is pro-fork, require blocks in the specific
// range to have the unique extra-data set.
if
daoBlock
:=
config
.
DAOForkBlock
;
daoBlock
!=
nil
{
// Check whether the block is among the fork extra-override range
limit
:=
new
(
big
.
Int
)
.
Add
(
daoBlock
,
params
.
DAOForkExtraRange
)
if
daoBlock
.
Cmp
(
header
.
Number
)
<=
0
&&
header
.
Number
.
Cmp
(
limit
)
<
0
{
// Depending whether we support or oppose the fork, verrift the extra-data contents
if
config
.
DAOForkSupport
{
if
bytes
.
Compare
(
header
.
Extra
,
params
.
DAOForkBlockExtra
)
!=
0
{
return
ValidationError
(
"DAO pro-fork bad block extra-data: 0x%x"
,
header
.
Extra
)
}
}
else
{
if
bytes
.
Compare
(
header
.
Extra
,
params
.
DAOForkBlockExtra
)
==
0
{
return
ValidationError
(
"DAO no-fork bad block extra-data: 0x%x"
,
header
.
Extra
)
}
}
}
}
return
nil
}
...
...
core/block_validator_test.go
浏览文件 @
a87089fd
...
...
@@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/pow/ezp"
)
...
...
@@ -92,3 +93,107 @@ func TestPutReceipt(t *testing.T) {
t
.
Error
(
"expected to get 1 receipt, got none."
)
}
}
// Tests that DAO-fork enabled clients can properly filter out fork-commencing
// blocks based on their extradata fields.
func
TestDAOForkRangeExtradata
(
t
*
testing
.
T
)
{
forkBlock
:=
big
.
NewInt
(
32
)
// Generate a common prefix for both pro-forkers and non-forkers
db
,
_
:=
ethdb
.
NewMemDatabase
()
genesis
:=
WriteGenesisBlockForTesting
(
db
)
prefix
,
_
:=
GenerateChain
(
genesis
,
db
,
int
(
forkBlock
.
Int64
()
-
1
),
func
(
i
int
,
gen
*
BlockGen
)
{})
// Create the concurrent, conflicting two nodes
proDb
,
_
:=
ethdb
.
NewMemDatabase
()
WriteGenesisBlockForTesting
(
proDb
)
proBc
,
_
:=
NewBlockChain
(
proDb
,
&
ChainConfig
{
HomesteadBlock
:
big
.
NewInt
(
0
),
DAOForkBlock
:
forkBlock
,
DAOForkSupport
:
true
},
new
(
FakePow
),
new
(
event
.
TypeMux
))
conDb
,
_
:=
ethdb
.
NewMemDatabase
()
WriteGenesisBlockForTesting
(
conDb
)
conBc
,
_
:=
NewBlockChain
(
conDb
,
&
ChainConfig
{
HomesteadBlock
:
big
.
NewInt
(
0
),
DAOForkBlock
:
forkBlock
,
DAOForkSupport
:
false
},
new
(
FakePow
),
new
(
event
.
TypeMux
))
if
_
,
err
:=
proBc
.
InsertChain
(
prefix
);
err
!=
nil
{
t
.
Fatalf
(
"pro-fork: failed to import chain prefix: %v"
,
err
)
}
if
_
,
err
:=
conBc
.
InsertChain
(
prefix
);
err
!=
nil
{
t
.
Fatalf
(
"con-fork: failed to import chain prefix: %v"
,
err
)
}
// Try to expand both pro-fork and non-fork chains iteratively with other camp's blocks
for
i
:=
int64
(
0
);
i
<
params
.
DAOForkExtraRange
.
Int64
();
i
++
{
// Create a pro-fork block, and try to feed into the no-fork chain
db
,
_
=
ethdb
.
NewMemDatabase
()
WriteGenesisBlockForTesting
(
db
)
bc
,
_
:=
NewBlockChain
(
db
,
&
ChainConfig
{
HomesteadBlock
:
big
.
NewInt
(
0
)},
new
(
FakePow
),
new
(
event
.
TypeMux
))
blocks
:=
conBc
.
GetBlocksFromHash
(
conBc
.
CurrentBlock
()
.
Hash
(),
int
(
conBc
.
CurrentBlock
()
.
NumberU64
()
+
1
))
for
j
:=
0
;
j
<
len
(
blocks
)
/
2
;
j
++
{
blocks
[
j
],
blocks
[
len
(
blocks
)
-
1
-
j
]
=
blocks
[
len
(
blocks
)
-
1
-
j
],
blocks
[
j
]
}
if
_
,
err
:=
bc
.
InsertChain
(
blocks
);
err
!=
nil
{
t
.
Fatalf
(
"failed to import contra-fork chain for expansion: %v"
,
err
)
}
blocks
,
_
=
GenerateChain
(
conBc
.
CurrentBlock
(),
db
,
1
,
func
(
i
int
,
gen
*
BlockGen
)
{
gen
.
SetExtra
(
params
.
DAOForkBlockExtra
)
})
if
_
,
err
:=
conBc
.
InsertChain
(
blocks
);
err
==
nil
{
t
.
Fatalf
(
"contra-fork chain accepted pro-fork block: %v"
,
blocks
[
0
])
}
// Create a proper no-fork block for the contra-forker
blocks
,
_
=
GenerateChain
(
conBc
.
CurrentBlock
(),
db
,
1
,
func
(
i
int
,
gen
*
BlockGen
)
{})
if
_
,
err
:=
conBc
.
InsertChain
(
blocks
);
err
!=
nil
{
t
.
Fatalf
(
"contra-fork chain didn't accepted no-fork block: %v"
,
err
)
}
// Create a no-fork block, and try to feed into the pro-fork chain
db
,
_
=
ethdb
.
NewMemDatabase
()
WriteGenesisBlockForTesting
(
db
)
bc
,
_
=
NewBlockChain
(
db
,
&
ChainConfig
{
HomesteadBlock
:
big
.
NewInt
(
0
)},
new
(
FakePow
),
new
(
event
.
TypeMux
))
blocks
=
proBc
.
GetBlocksFromHash
(
proBc
.
CurrentBlock
()
.
Hash
(),
int
(
proBc
.
CurrentBlock
()
.
NumberU64
()
+
1
))
for
j
:=
0
;
j
<
len
(
blocks
)
/
2
;
j
++
{
blocks
[
j
],
blocks
[
len
(
blocks
)
-
1
-
j
]
=
blocks
[
len
(
blocks
)
-
1
-
j
],
blocks
[
j
]
}
if
_
,
err
:=
bc
.
InsertChain
(
blocks
);
err
!=
nil
{
t
.
Fatalf
(
"failed to import pro-fork chain for expansion: %v"
,
err
)
}
blocks
,
_
=
GenerateChain
(
proBc
.
CurrentBlock
(),
db
,
1
,
func
(
i
int
,
gen
*
BlockGen
)
{})
if
_
,
err
:=
proBc
.
InsertChain
(
blocks
);
err
==
nil
{
t
.
Fatalf
(
"pro-fork chain accepted contra-fork block: %v"
,
blocks
[
0
])
}
// Create a proper pro-fork block for the pro-forker
blocks
,
_
=
GenerateChain
(
proBc
.
CurrentBlock
(),
db
,
1
,
func
(
i
int
,
gen
*
BlockGen
)
{
gen
.
SetExtra
(
params
.
DAOForkBlockExtra
)
})
if
_
,
err
:=
proBc
.
InsertChain
(
blocks
);
err
!=
nil
{
t
.
Fatalf
(
"pro-fork chain didn't accepted pro-fork block: %v"
,
err
)
}
}
// Verify that contra-forkers accept pro-fork extra-datas after forking finishes
db
,
_
=
ethdb
.
NewMemDatabase
()
WriteGenesisBlockForTesting
(
db
)
bc
,
_
:=
NewBlockChain
(
db
,
&
ChainConfig
{
HomesteadBlock
:
big
.
NewInt
(
0
)},
new
(
FakePow
),
new
(
event
.
TypeMux
))
blocks
:=
conBc
.
GetBlocksFromHash
(
conBc
.
CurrentBlock
()
.
Hash
(),
int
(
conBc
.
CurrentBlock
()
.
NumberU64
()
+
1
))
for
j
:=
0
;
j
<
len
(
blocks
)
/
2
;
j
++
{
blocks
[
j
],
blocks
[
len
(
blocks
)
-
1
-
j
]
=
blocks
[
len
(
blocks
)
-
1
-
j
],
blocks
[
j
]
}
if
_
,
err
:=
bc
.
InsertChain
(
blocks
);
err
!=
nil
{
t
.
Fatalf
(
"failed to import contra-fork chain for expansion: %v"
,
err
)
}
blocks
,
_
=
GenerateChain
(
conBc
.
CurrentBlock
(),
db
,
1
,
func
(
i
int
,
gen
*
BlockGen
)
{
gen
.
SetExtra
(
params
.
DAOForkBlockExtra
)
})
if
_
,
err
:=
conBc
.
InsertChain
(
blocks
);
err
!=
nil
{
t
.
Fatalf
(
"contra-fork chain didn't accept pro-fork block post-fork: %v"
,
err
)
}
// Verify that pro-forkers accept contra-fork extra-datas after forking finishes
db
,
_
=
ethdb
.
NewMemDatabase
()
WriteGenesisBlockForTesting
(
db
)
bc
,
_
=
NewBlockChain
(
db
,
&
ChainConfig
{
HomesteadBlock
:
big
.
NewInt
(
0
)},
new
(
FakePow
),
new
(
event
.
TypeMux
))
blocks
=
proBc
.
GetBlocksFromHash
(
proBc
.
CurrentBlock
()
.
Hash
(),
int
(
proBc
.
CurrentBlock
()
.
NumberU64
()
+
1
))
for
j
:=
0
;
j
<
len
(
blocks
)
/
2
;
j
++
{
blocks
[
j
],
blocks
[
len
(
blocks
)
-
1
-
j
]
=
blocks
[
len
(
blocks
)
-
1
-
j
],
blocks
[
j
]
}
if
_
,
err
:=
bc
.
InsertChain
(
blocks
);
err
!=
nil
{
t
.
Fatalf
(
"failed to import pro-fork chain for expansion: %v"
,
err
)
}
blocks
,
_
=
GenerateChain
(
proBc
.
CurrentBlock
(),
db
,
1
,
func
(
i
int
,
gen
*
BlockGen
)
{})
if
_
,
err
:=
proBc
.
InsertChain
(
blocks
);
err
!=
nil
{
t
.
Fatalf
(
"pro-fork chain didn't accept contra-fork block post-fork: %v"
,
err
)
}
}
core/config.go
浏览文件 @
a87089fd
...
...
@@ -31,8 +31,9 @@ var ChainConfigNotFoundErr = errors.New("ChainConfig not found") // general conf
// that any network, identified by its genesis block, can have its own
// set of configuration options.
type
ChainConfig
struct
{
HomesteadBlock
*
big
.
Int
`json:"homesteadBlock"`
// homestead switch block (0 = already homestead)
DAOForkBlock
*
big
.
Int
`json:"daoForkBlock"`
// TheDAO hard-fork block (nil = no fork)
HomesteadBlock
*
big
.
Int
`json:"homesteadBlock"`
// Homestead switch block (nil = no fork, 0 = already homestead)
DAOForkBlock
*
big
.
Int
`json:"daoForkBlock"`
// TheDAO hard-fork switch block (nil = no fork)
DAOForkSupport
bool
`json:"daoForkSupport"`
// Whether the nodes supports or opposes the DAO hard-fork
VmConfig
vm
.
Config
`json:"-"`
}
...
...
miner/worker.go
浏览文件 @
a87089fd
...
...
@@ -17,6 +17,7 @@
package
miner
import
(
"bytes"
"fmt"
"math/big"
"sync"
...
...
@@ -469,12 +470,17 @@ func (self *worker) commitNewWork() {
Extra
:
self
.
extra
,
Time
:
big
.
NewInt
(
tstamp
),
}
// If we are
doing a
DAO hard-fork check whether to override the extra-data or not
// If we are
care about The
DAO hard-fork check whether to override the extra-data or not
if
daoBlock
:=
self
.
config
.
DAOForkBlock
;
daoBlock
!=
nil
{
// Check whether the block is among the fork extra-override range
limit
:=
new
(
big
.
Int
)
.
Add
(
daoBlock
,
params
.
DAOForkExtraRange
)
if
daoBlock
.
Cmp
(
header
.
Number
)
<=
0
&&
header
.
Number
.
Cmp
(
limit
)
<
0
{
header
.
Extra
=
common
.
CopyBytes
(
params
.
DAOForkBlockExtra
)
// Depending whether we support or oppose the fork, override differently
if
self
.
config
.
DAOForkSupport
{
header
.
Extra
=
common
.
CopyBytes
(
params
.
DAOForkBlockExtra
)
}
else
if
bytes
.
Compare
(
header
.
Extra
,
params
.
DAOForkBlockExtra
)
==
0
{
header
.
Extra
=
[]
byte
{}
// If miner opposes, don't let it use the reserved extra-data
}
}
}
previous
:=
self
.
current
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录