Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
P
plugin
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
JIRA
JIRA
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
link33
plugin
Commits
aa055772
Commit
aa055772
authored
Aug 12, 2019
by
kingwang
Committed by
33cn
Aug 12, 2019
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
update chain33 08/12
parent
34c30c13
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
17 changed files
with
397 additions
and
165 deletions
+397
-165
blockstore.go
vendor/github.com/33cn/chain33/blockchain/blockstore.go
+21
-23
chain_test.go
vendor/github.com/33cn/chain33/blockchain/chain_test.go
+1
-1
filter_paratx.go
vendor/github.com/33cn/chain33/blockchain/filter_paratx.go
+40
-18
filter_paratx_test.go
.../github.com/33cn/chain33/blockchain/filter_paratx_test.go
+22
-3
proc.go
vendor/github.com/33cn/chain33/blockchain/proc.go
+8
-0
query_block.go
vendor/github.com/33cn/chain33/blockchain/query_block.go
+22
-0
rollback.go
vendor/github.com/33cn/chain33/blockchain/rollback.go
+10
-11
task_test.go
vendor/github.com/33cn/chain33/blockchain/task_test.go
+11
-17
stat.go
vendor/github.com/33cn/chain33/system/dapp/commands/stat.go
+193
-85
wallet.go
...or/github.com/33cn/chain33/system/dapp/commands/wallet.go
+2
-2
tree.go
vendor/github.com/33cn/chain33/system/store/mavl/db/tree.go
+3
-0
block.go
vendor/github.com/33cn/chain33/types/block.go
+5
-0
blockchain.pb.go
vendor/github.com/33cn/chain33/types/blockchain.pb.go
+0
-0
const.go
vendor/github.com/33cn/chain33/types/const.go
+4
-1
blockchain.proto
vendor/github.com/33cn/chain33/types/proto/blockchain.proto
+3
-0
store.go
vendor/github.com/33cn/chain33/wallet/common/store.go
+8
-2
wallet_test.go
vendor/github.com/33cn/chain33/wallet/wallet_test.go
+44
-2
No files found.
vendor/github.com/33cn/chain33/blockchain/blockstore.go
View file @
aa055772
...
...
@@ -24,24 +24,22 @@ import (
//var
var
(
blockLastHeight
=
[]
byte
(
"blockLastHeight"
)
bodyPrefix
=
[]
byte
(
"Body:"
)
LastSequence
=
[]
byte
(
"LastSequence"
)
headerPrefix
=
[]
byte
(
"Header:"
)
heightToHeaderPrefix
=
[]
byte
(
"HH:"
)
hashPrefix
=
[]
byte
(
"Hash:"
)
tdPrefix
=
[]
byte
(
"TD:"
)
heightToHashKeyPrefix
=
[]
byte
(
"Height:"
)
seqToHashKey
=
[]
byte
(
"Seq:"
)
HashToSeqPrefix
=
[]
byte
(
"HashToSeq:"
)
seqCBPrefix
=
[]
byte
(
"SCB:"
)
seqCBLastNumPrefix
=
[]
byte
(
"SCBL:"
)
paraSeqToHashKey
=
[]
byte
(
"ParaSeq:"
)
HashToParaSeqPrefix
=
[]
byte
(
"HashToParaSeq:"
)
LastParaSequence
=
[]
byte
(
"LastParaSequence"
)
storeLog
=
chainlog
.
New
(
"submodule"
,
"store"
)
AddBlock
int64
=
1
DelBlock
int64
=
2
blockLastHeight
=
[]
byte
(
"blockLastHeight"
)
bodyPrefix
=
[]
byte
(
"Body:"
)
LastSequence
=
[]
byte
(
"LastSequence"
)
headerPrefix
=
[]
byte
(
"Header:"
)
heightToHeaderPrefix
=
[]
byte
(
"HH:"
)
hashPrefix
=
[]
byte
(
"Hash:"
)
tdPrefix
=
[]
byte
(
"TD:"
)
heightToHashKeyPrefix
=
[]
byte
(
"Height:"
)
seqToHashKey
=
[]
byte
(
"Seq:"
)
HashToSeqPrefix
=
[]
byte
(
"HashToSeq:"
)
seqCBPrefix
=
[]
byte
(
"SCB:"
)
seqCBLastNumPrefix
=
[]
byte
(
"SCBL:"
)
paraSeqToHashKey
=
[]
byte
(
"ParaSeq:"
)
HashToParaSeqPrefix
=
[]
byte
(
"HashToParaSeq:"
)
LastParaSequence
=
[]
byte
(
"LastParaSequence"
)
storeLog
=
chainlog
.
New
(
"submodule"
,
"store"
)
)
//GetLocalDBKeyList 获取本地键值列表
...
...
@@ -590,7 +588,7 @@ func (bs *BlockStore) SaveBlock(storeBatch dbm.Batch, blockdetail *types.BlockDe
if
bs
.
saveSequence
||
bs
.
isParaChain
{
//存储记录block序列执行的type add
lastSequence
,
err
=
bs
.
saveBlockSequence
(
storeBatch
,
hash
,
height
,
AddBlock
,
sequence
)
lastSequence
,
err
=
bs
.
saveBlockSequence
(
storeBatch
,
hash
,
height
,
types
.
AddBlock
,
sequence
)
if
err
!=
nil
{
storeLog
.
Error
(
"SaveBlock SaveBlockSequence"
,
"height"
,
height
,
"hash"
,
common
.
ToHex
(
hash
),
"error"
,
err
)
return
lastSequence
,
err
...
...
@@ -619,7 +617,7 @@ func (bs *BlockStore) DelBlock(storeBatch dbm.Batch, blockdetail *types.BlockDet
if
bs
.
saveSequence
||
bs
.
isParaChain
{
//存储记录block序列执行的type del
lastSequence
,
err
:=
bs
.
saveBlockSequence
(
storeBatch
,
hash
,
height
,
DelBlock
,
sequence
)
lastSequence
,
err
:=
bs
.
saveBlockSequence
(
storeBatch
,
hash
,
height
,
types
.
DelBlock
,
sequence
)
if
err
!=
nil
{
storeLog
.
Error
(
"DelBlock SaveBlockSequence"
,
"height"
,
height
,
"hash"
,
common
.
ToHex
(
hash
),
"error"
,
err
)
return
lastSequence
,
err
...
...
@@ -1029,7 +1027,7 @@ func (bs *BlockStore) saveBlockSequence(storeBatch dbm.Batch, hash []byte, heigh
sequenceBytes
:=
types
.
Encode
(
&
types
.
Int64
{
Data
:
newSequence
})
// hash->seq 只记录add block时的hash和seq对应关系
if
Type
==
AddBlock
{
if
Type
==
types
.
AddBlock
{
storeBatch
.
Set
(
calcHashToSequenceKey
(
hash
,
bs
.
isParaChain
),
sequenceBytes
)
}
...
...
@@ -1054,7 +1052,7 @@ func (bs *BlockStore) saveBlockSequence(storeBatch dbm.Batch, hash []byte, heigh
// hash->seq 只记录add block时的hash和seq对应关系
sequenceBytes
:=
types
.
Encode
(
&
types
.
Int64
{
Data
:
mainSeq
})
if
Type
==
AddBlock
{
if
Type
==
types
.
AddBlock
{
storeBatch
.
Set
(
calcHashToMainSequenceKey
(
hash
),
sequenceBytes
)
}
storeBatch
.
Set
(
LastSequence
,
sequenceBytes
)
...
...
@@ -1301,7 +1299,7 @@ func (bs *BlockStore) CreateSequences(batchSize int64) {
// seq->hash
var
blockSequence
types
.
BlockSequence
blockSequence
.
Hash
=
header
.
Hash
blockSequence
.
Type
=
AddBlock
blockSequence
.
Type
=
types
.
AddBlock
BlockSequenceByte
,
err
:=
proto
.
Marshal
(
&
blockSequence
)
if
err
!=
nil
{
storeLog
.
Error
(
"CreateSequences Marshal BlockSequence"
,
"height"
,
i
,
"hash"
,
common
.
ToHex
(
header
.
Hash
),
"error"
,
err
)
...
...
vendor/github.com/33cn/chain33/blockchain/chain_test.go
View file @
aa055772
...
...
@@ -601,7 +601,7 @@ func testGetBlockSequences(t *testing.T, chain *blockchain.BlockChain) {
Sequences
,
err
:=
chain
.
GetBlockSequences
(
&
reqBlock
)
if
err
==
nil
&&
Sequences
!=
nil
{
for
_
,
sequence
:=
range
Sequences
.
Items
{
if
sequence
.
Type
!=
blockchain
.
AddBlock
{
if
sequence
.
Type
!=
types
.
AddBlock
{
t
.
Error
(
"testGetBlockSequences sequence type check error"
)
}
}
...
...
vendor/github.com/33cn/chain33/blockchain/filter_paratx.go
View file @
aa055772
...
...
@@ -23,22 +23,29 @@ func (chain *BlockChain) GetParaTxByTitle(seq *types.ReqParaTxByTitle) (*types.P
return
nil
,
err
}
//获取区块的seq信息
req
:=
&
types
.
ReqBlocks
{
Start
:
seq
.
Start
,
End
:
seq
.
End
,
IsDetail
:
false
,
Pid
:
[]
string
{}}
sequences
,
err
:=
chain
.
GetBlockSequences
(
req
)
if
err
!=
nil
{
filterlog
.
Error
(
"GetParaTxByTitle:GetBlockSequences"
,
"err"
,
err
.
Error
())
return
nil
,
err
//对获取区块的起始和结束值做校验,最多一次取1000个区块,防止取的数据过大导致内存异常
if
seq
.
End
>
seq
.
Start
&&
(
seq
.
End
-
seq
.
Start
>
types
.
MaxBlockCountPerTime
)
{
seq
.
End
=
seq
.
Start
+
types
.
MaxBlockCountPerTime
-
1
}
//通过区块hash获取区块信息
var
reqHashes
types
.
ReqHashes
for
_
,
item
:=
range
sequences
.
Items
{
if
item
!=
nil
{
reqHashes
.
Hashes
=
append
(
reqHashes
.
Hashes
,
item
.
GetHash
())
var
sequences
*
types
.
BlockSequences
if
seq
.
IsSeq
{
req
:=
&
types
.
ReqBlocks
{
Start
:
seq
.
Start
,
End
:
seq
.
End
,
IsDetail
:
false
,
Pid
:
[]
string
{}}
sequences
,
err
=
chain
.
GetBlockSequences
(
req
)
if
err
!=
nil
{
filterlog
.
Error
(
"GetParaTxByTitle:GetBlockSequences"
,
"err"
,
err
.
Error
())
return
nil
,
err
}
for
_
,
item
:=
range
sequences
.
Items
{
if
item
!=
nil
{
reqHashes
.
Hashes
=
append
(
reqHashes
.
Hashes
,
item
.
GetHash
())
}
}
}
else
{
reqHashes
=
chain
.
getBlockHashes
(
seq
.
Start
,
seq
.
End
)
}
//通过区块hash获取区块信息
blocks
,
err
:=
chain
.
GetBlockByHashes
(
reqHashes
.
Hashes
)
if
err
!=
nil
{
filterlog
.
Error
(
"GetParaTxByTitle:GetBlockByHashes"
,
"err"
,
err
)
...
...
@@ -51,7 +58,11 @@ func (chain *BlockChain) GetParaTxByTitle(seq *types.ReqParaTxByTitle) (*types.P
for
i
,
block
:=
range
blocks
.
Items
{
if
block
!=
nil
{
paraTx
=
block
.
FilterParaTxsByTitle
(
seq
.
Title
)
paraTx
.
Type
=
sequences
.
Items
[
i
]
.
GetType
()
if
seq
.
IsSeq
{
paraTx
.
Type
=
sequences
.
Items
[
i
]
.
GetType
()
}
else
{
paraTx
.
Type
=
types
.
AddBlock
}
}
else
{
paraTx
=
nil
}
...
...
@@ -60,13 +71,24 @@ func (chain *BlockChain) GetParaTxByTitle(seq *types.ReqParaTxByTitle) (*types.P
return
&
paraTxs
,
err
}
//checkInputParam 入参检测,主要检测seq的end的值已经title是否合法
func
(
chain
*
BlockChain
)
checkInputParam
(
seq
*
types
.
ReqParaTxByTitle
)
error
{
//checkInputParam 入参检测,主要检测req的end的值以及title是否合法
func
(
chain
*
BlockChain
)
checkInputParam
(
req
*
types
.
ReqParaTxByTitle
)
error
{
var
err
error
var
lastblock
int64
//入参数校验
blockLastSeq
,
err
:=
chain
.
blockStore
.
LoadBlockLastSequence
()
if
err
!=
nil
||
seq
.
End
>
blockLastSeq
||
blockLastSeq
<
0
||
!
strings
.
HasPrefix
(
seq
.
Title
,
types
.
ParaKeyX
)
{
filterlog
.
Error
(
"checkInputParam"
,
"blockLastSeq"
,
blockLastSeq
,
"seq"
,
seq
,
"err"
,
err
)
if
req
.
GetStart
()
>
req
.
GetEnd
()
{
chainlog
.
Error
(
"checkInputParam input must Start <= End:"
,
"Start"
,
req
.
Start
,
"End"
,
req
.
End
)
return
types
.
ErrEndLessThanStartHeight
}
//需要区分是通过seq/height来获取平行链交易
if
req
.
IsSeq
{
lastblock
,
err
=
chain
.
blockStore
.
LoadBlockLastSequence
()
}
else
{
lastblock
=
chain
.
GetBlockHeight
()
}
if
err
!=
nil
||
req
.
End
>
lastblock
||
lastblock
<
0
||
!
strings
.
HasPrefix
(
req
.
Title
,
types
.
ParaKeyX
)
{
filterlog
.
Error
(
"checkInputParam"
,
"lastblock"
,
lastblock
,
"req"
,
req
,
"err"
,
err
)
return
types
.
ErrInvalidParam
}
return
nil
...
...
vendor/github.com/33cn/chain33/blockchain/filter_paratx_test.go
View file @
aa055772
...
...
@@ -142,17 +142,36 @@ func TestGetParaTxByTitle(t *testing.T) {
time
.
Sleep
(
sendTxWait
)
}
var
req
types
.
ReqParaTxByTitle
//通过seq获取para交易
req
.
Start
=
0
req
.
End
=
curheight
req
.
Title
=
"user.p.hyb."
testgetParaTxByTitle
(
t
,
blockchain
,
&
req
,
false
,
false
,
nil
)
req
.
IsSeq
=
true
testgetParaTxByTitle
(
t
,
blockchain
,
&
req
,
0
)
//通过height获取para交易
req
.
IsSeq
=
false
testgetParaTxByTitle
(
t
,
blockchain
,
&
req
,
0
)
//通过height获取para交易
req
.
IsSeq
=
false
req
.
End
=
curheight
+
1
testgetParaTxByTitle
(
t
,
blockchain
,
&
req
,
1
)
chainlog
.
Info
(
"TestGetParaTxByTitle end --------------------"
)
}
func
testgetParaTxByTitle
(
t
*
testing
.
T
,
blockchain
*
blockchain
.
BlockChain
,
req
*
types
.
ReqParaTxByTitle
,
isGroup
bool
,
haveMainTx
bool
,
hashs
[]
string
)
{
func
testgetParaTxByTitle
(
t
*
testing
.
T
,
blockchain
*
blockchain
.
BlockChain
,
req
*
types
.
ReqParaTxByTitle
,
flag
int
)
{
count
:=
req
.
End
-
req
.
Start
+
1
ParaTxDetails
,
err
:=
blockchain
.
GetParaTxByTitle
(
req
)
require
.
NoError
(
t
,
err
)
if
flag
==
0
{
require
.
NoError
(
t
,
err
)
}
if
flag
==
1
{
assert
.
Equal
(
t
,
err
,
types
.
ErrInvalidParam
)
return
}
itemsLen
:=
len
(
ParaTxDetails
.
Items
)
assert
.
Equal
(
t
,
count
,
int64
(
itemsLen
))
for
i
,
txDetail
:=
range
ParaTxDetails
.
Items
{
if
txDetail
!=
nil
{
...
...
vendor/github.com/33cn/chain33/blockchain/proc.go
View file @
aa055772
...
...
@@ -481,6 +481,14 @@ func (chain *BlockChain) delParaChainBlockDetail(msg *queue.Message) {
func
(
chain
*
BlockChain
)
addParaChainBlockDetail
(
msg
*
queue
.
Message
)
{
parablockDetail
:=
msg
.
Data
.
(
*
types
.
ParaChainBlockDetail
)
//根据配置chain.cfgBatchSync和parablockDetail.IsSync
//来决定写数据库时是否需要刷盘,主要是为了同步阶段提高执行区块的效率
if
!
parablockDetail
.
IsSync
&&
!
chain
.
cfgBatchSync
{
atomic
.
CompareAndSwapInt32
(
&
chain
.
isbatchsync
,
1
,
0
)
}
else
{
atomic
.
CompareAndSwapInt32
(
&
chain
.
isbatchsync
,
0
,
1
)
}
chainlog
.
Debug
(
"EventAddParaChainBlockDetail"
,
"height"
,
parablockDetail
.
Blockdetail
.
Block
.
Height
,
"hash"
,
common
.
HashHex
(
parablockDetail
.
Blockdetail
.
Block
.
Hash
()))
// 平行链上P2P模块关闭,不用广播区块
blockDetail
,
err
:=
chain
.
ProcAddParaChainBlockMsg
(
false
,
parablockDetail
,
"self"
)
...
...
vendor/github.com/33cn/chain33/blockchain/query_block.go
View file @
aa055772
...
...
@@ -10,11 +10,18 @@ import (
)
//GetBlockByHashes 通过blockhash 获取对应的block信息
//从数据库获取区块不能太多,防止内存异常。一次最多获取100M区块数据从数据库
func
(
chain
*
BlockChain
)
GetBlockByHashes
(
hashes
[][]
byte
)
(
respblocks
*
types
.
BlockDetails
,
err
error
)
{
var
blocks
types
.
BlockDetails
size
:=
0
for
_
,
hash
:=
range
hashes
{
block
,
err
:=
chain
.
LoadBlockByHash
(
hash
)
if
err
==
nil
&&
block
!=
nil
{
size
+=
block
.
Size
()
if
size
>
types
.
MaxBlockSizePerTime
{
chainlog
.
Error
(
"GetBlockByHashes:overflow"
,
"MaxBlockSizePerTime"
,
types
.
MaxBlockSizePerTime
)
return
&
blocks
,
nil
}
blocks
.
Items
=
append
(
blocks
.
Items
,
block
)
}
else
{
blocks
.
Items
=
append
(
blocks
.
Items
,
nil
)
...
...
@@ -264,3 +271,18 @@ func (chain *BlockChain) ProcAddBlockMsg(broadcast bool, blockdetail *types.Bloc
chainlog
.
Debug
(
"ProcAddBlockMsg result:"
,
"height"
,
blockdetail
.
Block
.
Height
,
"ismain"
,
ismain
,
"isorphan"
,
isorphan
,
"hash"
,
common
.
ToHex
(
blockdetail
.
Block
.
Hash
()),
"err"
,
err
)
return
blockdetail
,
err
}
//getBlockHashes 获取指定height区间对应的blockhashes
func
(
chain
*
BlockChain
)
getBlockHashes
(
startheight
,
endheight
int64
)
types
.
ReqHashes
{
var
reqHashes
types
.
ReqHashes
for
i
:=
startheight
;
i
<=
endheight
;
i
++
{
hash
,
err
:=
chain
.
blockStore
.
GetBlockHashByHeight
(
i
)
if
hash
==
nil
||
err
!=
nil
{
storeLog
.
Error
(
"getBlockHashesByHeight"
,
"height"
,
i
,
"error"
,
err
)
reqHashes
.
Hashes
=
append
(
reqHashes
.
Hashes
,
nil
)
}
else
{
reqHashes
.
Hashes
=
append
(
reqHashes
.
Hashes
,
hash
)
}
}
return
reqHashes
}
vendor/github.com/33cn/chain33/blockchain/rollback.go
View file @
aa055772
...
...
@@ -50,6 +50,16 @@ func (chain *BlockChain) Rollback() {
if
err
!=
nil
{
panic
(
fmt
.
Sprintln
(
"rollback LoadBlockByHeight err :"
,
err
))
}
if
chain
.
cfg
.
RollbackSave
{
//本地保存临时区块
lastHeightSave
:=
false
if
i
==
startHeight
{
lastHeightSave
=
true
}
err
=
chain
.
WriteBlockToDbTemp
(
blockdetail
.
Block
,
lastHeightSave
)
if
err
!=
nil
{
panic
(
fmt
.
Sprintln
(
"rollback WriteBlockToDbTemp fail"
,
"height"
,
blockdetail
.
Block
.
Height
,
"error "
,
err
))
}
}
sequence
:=
int64
(
-
1
)
if
chain
.
isParaChain
{
// 获取平行链的seq
...
...
@@ -64,17 +74,6 @@ func (chain *BlockChain) Rollback() {
}
// 删除storedb中的状态高度
chain
.
sendDelStore
(
blockdetail
.
Block
.
StateHash
,
blockdetail
.
Block
.
Height
)
// 删除之后,本地保存临时区块
if
chain
.
cfg
.
RollbackSave
{
lastHeightSave
:=
false
if
i
==
startHeight
{
lastHeightSave
=
true
}
err
=
chain
.
WriteBlockToDbTemp
(
blockdetail
.
Block
,
lastHeightSave
)
if
err
!=
nil
{
panic
(
fmt
.
Sprintln
(
"rollback WriteBlockToDbTemp fail"
,
"height"
,
blockdetail
.
Block
.
Height
,
"error "
,
err
))
}
}
chainlog
.
Info
(
"chain rollback "
,
"height: "
,
i
,
"blockheight"
,
blockdetail
.
Block
.
Height
,
"hash"
,
common
.
ToHex
(
blockdetail
.
Block
.
Hash
()),
"state hash"
,
common
.
ToHex
(
blockdetail
.
Block
.
StateHash
))
}
}
...
...
vendor/github.com/33cn/chain33/blockchain/task_test.go
View file @
aa055772
...
...
@@ -5,9 +5,12 @@
package
blockchain
import
(
"fmt"
"math/rand"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func
TestTask
(
t
*
testing
.
T
)
{
...
...
@@ -69,27 +72,18 @@ func TestTaskTimeOut(t *testing.T) {
t
.
Log
(
"task not start"
)
return
}
defer
task
.
Cancel
()
timeoutHeight
:=
make
(
chan
int64
,
1
)
timeoutcb
:=
func
(
height
int64
)
{
timeOutProc
(
height
)
fmt
.
Println
(
"timeout:height"
,
height
)
timeoutHeight
<-
height
}
task
.
Start
(
1
,
1
0
,
nil
,
timeoutcb
)
task
.
Start
(
1
,
1
1
,
nil
,
timeoutcb
)
perm
:=
rand
.
Perm
(
10
)
for
i
:=
0
;
i
<
len
(
perm
);
i
++
{
time
.
Sleep
(
time
.
Millisecond
*
10
)
task
.
Done
(
int64
(
perm
[
i
])
+
1
)
if
i
<
len
(
perm
)
-
1
&&
!
task
.
InProgress
()
{
task
.
Cancel
()
t
.
Log
(
"task not done, but InProgress is false"
)
return
}
if
i
==
len
(
perm
)
-
1
&&
task
.
InProgress
()
{
task
.
Cancel
()
t
.
Log
(
"task is done, but InProgress is true"
)
return
}
}
}
func
timeOutProc
(
height
int64
)
{
chainlog
.
Info
(
"timeOutProc"
,
"height"
,
height
)
h
:=
<-
timeoutHeight
assert
.
Equal
(
t
,
h
,
int64
(
11
))
}
vendor/github.com/33cn/chain33/system/dapp/commands/stat.go
View file @
aa055772
...
...
@@ -5,6 +5,7 @@
package
commands
import
(
"bytes"
"encoding/json"
"fmt"
"os"
...
...
@@ -27,15 +28,16 @@ func StatCmd() *cobra.Command {
}
cmd
.
AddCommand
(
GetTotalCoinsCmd
(),
GetExecBalanceCmd
(),
getTotalCoinsCmd
(),
getExecBalanceCmd
(),
totalFeeCmd
(),
)
return
cmd
}
//
G
etTotalCoinsCmd get total coins
func
G
etTotalCoinsCmd
()
*
cobra
.
Command
{
//
g
etTotalCoinsCmd get total coins
func
g
etTotalCoinsCmd
()
*
cobra
.
Command
{
cmd
:=
&
cobra
.
Command
{
Use
:
"total_coins"
,
Short
:
"Get total amount of a token (default: bty of current height)"
,
...
...
@@ -57,43 +59,32 @@ func totalCoins(cmd *cobra.Command, args []string) {
height
,
_
:=
cmd
.
Flags
()
.
GetInt64
(
"height"
)
actual
,
_
:=
cmd
.
Flags
()
.
GetString
(
"actual"
)
if
height
==
-
1
{
rpc
,
err
:=
jsonclient
.
NewJSONClient
(
rpcAddr
)
rpc
,
err
:=
jsonclient
.
NewJSONClient
(
rpcAddr
)
if
err
!=
nil
{
fmt
.
Fprintln
(
os
.
Stderr
,
err
)
return
}
var
stateHashHex
string
if
height
<
0
{
header
,
err
:=
getLastBlock
(
rpc
)
if
err
!=
nil
{
fmt
.
Fprintln
(
os
.
Stderr
,
err
)
return
}
var
res
rpctypes
.
Header
err
=
rpc
.
Call
(
"Chain33.GetLastHeader"
,
nil
,
&
res
)
height
=
header
.
Height
stateHashHex
=
header
.
StateHash
}
else
{
blocks
,
err
:=
getBlocks
(
height
,
height
,
rpc
)
if
err
!=
nil
{
fmt
.
Fprint
ln
(
os
.
Stderr
,
err
)
fmt
.
Fprint
f
(
os
.
Stderr
,
"GetBlocksErr:%s"
,
err
.
Error
()
)
return
}
height
=
res
.
Height
stateHashHex
=
blocks
.
Items
[
0
]
.
Block
.
StateHash
}
// 获取高度statehash
params
:=
rpctypes
.
BlockParam
{
Start
:
height
,
End
:
height
,
//Isdetail: false,
Isdetail
:
true
,
}
rpc
,
err
:=
jsonclient
.
NewJSONClient
(
rpcAddr
)
if
err
!=
nil
{
fmt
.
Fprintln
(
os
.
Stderr
,
err
)
return
}
var
res
rpctypes
.
BlockDetails
err
=
rpc
.
Call
(
"Chain33.GetBlocks"
,
params
,
&
res
)
if
err
!=
nil
{
fmt
.
Fprintln
(
os
.
Stderr
,
err
)
return
}
stateHash
,
err
:=
common
.
FromHex
(
res
.
Items
[
0
]
.
Block
.
StateHash
)
stateHash
,
err
:=
common
.
FromHex
(
stateHashHex
)
if
err
!=
nil
{
fmt
.
Fprintln
(
os
.
Stderr
,
err
)
return
...
...
@@ -104,32 +95,14 @@ func totalCoins(cmd *cobra.Command, args []string) {
resp
:=
commandtypes
.
GetTotalCoinsResult
{}
if
symbol
==
"bty"
{
//查询高度blockhash
params
:=
types
.
ReqInt
{
Height
:
height
}
var
res1
rpctypes
.
ReplyHash
err
=
rpc
.
Call
(
"Chain33.GetBlockHash"
,
params
,
&
res1
)
if
err
!=
nil
{
fmt
.
Fprintln
(
os
.
Stderr
,
err
)
return
}
blockHash
,
err
:=
common
.
FromHex
(
res1
.
Hash
)
if
err
!=
nil
{
fmt
.
Fprintln
(
os
.
Stderr
,
err
)
return
}
//查询手续费
key
:=
append
([]
byte
(
"TotalFeeKey:"
),
blockHash
...
)
params2
:=
types
.
LocalDBGet
{
Keys
:
[][]
byte
{
key
}}
var
res2
types
.
TotalFee
err
=
rpc
.
Call
(
"Chain33.QueryTotalFee"
,
params2
,
&
res2
)
//查询历史总手续费
fee
,
err
:=
queryTotalFeeWithHeight
(
height
,
rpc
)
if
err
!=
nil
{
fmt
.
Fprintln
(
os
.
Stderr
,
err
)
return
}
resp
.
TxCount
=
res2
.
TxCount
resp
.
TxCount
=
fee
.
TxCount
var
issueCoins
int64
//只适用bty主网计算
if
height
<
2270000
{
...
...
@@ -137,7 +110,7 @@ func totalCoins(cmd *cobra.Command, args []string) {
}
else
{
//挖矿产量降低30->8
issueCoins
=
22
*
2269999
+
height
*
8
}
totalAmount
=
(
317430000
+
issueCoins
)
*
types
.
Coin
-
res2
.
Fee
totalAmount
=
(
317430000
+
issueCoins
)
*
types
.
Coin
-
fee
.
Fee
resp
.
TotalAmount
=
strconv
.
FormatFloat
(
float64
(
totalAmount
)
/
float64
(
types
.
Coin
),
'f'
,
4
,
64
)
}
else
{
var
req
types
.
ReqString
...
...
@@ -210,8 +183,8 @@ func totalCoins(cmd *cobra.Command, args []string) {
fmt
.
Println
(
string
(
data
))
}
//
G
etExecBalanceCmd get exec-addr balance of specific addr
func
G
etExecBalanceCmd
()
*
cobra
.
Command
{
//
g
etExecBalanceCmd get exec-addr balance of specific addr
func
g
etExecBalanceCmd
()
*
cobra
.
Command
{
cmd
:=
&
cobra
.
Command
{
Use
:
"exec_balance"
,
Short
:
"Get the exec amount of a token of one address (default: all exec-addr bty of current height of one addr)"
,
...
...
@@ -238,43 +211,30 @@ func execBalance(cmd *cobra.Command, args []string) {
execAddr
,
_
:=
cmd
.
Flags
()
.
GetString
(
"exec_addr"
)
height
,
_
:=
cmd
.
Flags
()
.
GetInt64
(
"height"
)
if
height
==
-
1
{
rpc
,
err
:=
jsonclient
.
NewJSONClient
(
rpcAddr
)
rpc
,
err
:=
jsonclient
.
NewJSONClient
(
rpcAddr
)
if
err
!=
nil
{
fmt
.
Fprintln
(
os
.
Stderr
,
err
)
return
}
var
stateHashHex
string
if
height
<
0
{
header
,
err
:=
getLastBlock
(
rpc
)
if
err
!=
nil
{
fmt
.
Fprintln
(
os
.
Stderr
,
err
)
return
}
var
res
rpctypes
.
Header
err
=
rpc
.
Call
(
"Chain33.GetLastHeader"
,
nil
,
&
res
)
stateHashHex
=
header
.
StateHash
}
else
{
blocks
,
err
:=
getBlocks
(
height
,
height
,
rpc
)
if
err
!=
nil
{
fmt
.
Fprint
ln
(
os
.
Stderr
,
err
)
fmt
.
Fprint
f
(
os
.
Stderr
,
"GetBlocksErr:%s"
,
err
.
Error
()
)
return
}
height
=
res
.
Height
}
// 获取高度statehash
params
:=
rpctypes
.
BlockParam
{
Start
:
height
,
End
:
height
,
//Isdetail: false,
Isdetail
:
true
,
stateHashHex
=
blocks
.
Items
[
0
]
.
Block
.
StateHash
}
rpc
,
err
:=
jsonclient
.
NewJSONClient
(
rpcAddr
)
if
err
!=
nil
{
fmt
.
Fprintln
(
os
.
Stderr
,
err
)
return
}
var
res
rpctypes
.
BlockDetails
err
=
rpc
.
Call
(
"Chain33.GetBlocks"
,
params
,
&
res
)
if
err
!=
nil
{
fmt
.
Fprintln
(
os
.
Stderr
,
err
)
return
}
stateHash
,
err
:=
common
.
FromHex
(
res
.
Items
[
0
]
.
Block
.
StateHash
)
stateHash
,
err
:=
common
.
FromHex
(
stateHashHex
)
if
err
!=
nil
{
fmt
.
Fprintln
(
os
.
Stderr
,
err
)
return
...
...
@@ -293,7 +253,6 @@ func execBalance(cmd *cobra.Command, args []string) {
ExecAddr
:
[]
byte
(
execAddr
),
Execer
:
exec
,
}
reqParam
.
StateHash
=
stateHash
if
len
(
execAddr
)
>
0
{
reqParam
.
Count
=
1
//由于精确匹配一条记录,所以这里设定为1
...
...
@@ -366,3 +325,152 @@ func convertReplyToResult(reply *types.ReplyGetExecBalance, result *commandtypes
result
.
ExecBalances
=
append
(
result
.
ExecBalances
,
item
)
}
}
// totalFeeCmd query total fee command
func
totalFeeCmd
()
*
cobra
.
Command
{
cmd
:=
&
cobra
.
Command
{
Use
:
"total_fee"
,
Short
:
"query total transaction fee, support specific block height interval [start, end]"
,
Run
:
totalFee
,
}
cmd
.
Flags
()
.
Int64P
(
"start_height"
,
"s"
,
0
,
"start block height, default 0"
)
cmd
.
Flags
()
.
Int64P
(
"end_height"
,
"e"
,
-
1
,
"end block height, default current block height"
)
return
cmd
}
func
totalFee
(
cmd
*
cobra
.
Command
,
args
[]
string
)
{
rpcAddr
,
_
:=
cmd
.
Flags
()
.
GetString
(
"rpc_laddr"
)
start
,
_
:=
cmd
.
Flags
()
.
GetInt64
(
"start_height"
)
end
,
_
:=
cmd
.
Flags
()
.
GetInt64
(
"end_height"
)
var
startFeeAmount
,
endFeeAmount
int64
rpc
,
err
:=
jsonclient
.
NewJSONClient
(
rpcAddr
)
if
err
!=
nil
{
fmt
.
Fprintf
(
os
.
Stderr
,
"NewJsonClientErr:%s
\n
"
,
err
.
Error
())
return
}
if
start
<
0
{
start
=
0
}
if
start
>
0
{
totalFee
,
err
:=
queryTotalFeeWithHeight
(
start
-
1
,
rpc
)
if
err
!=
nil
{
fmt
.
Fprintf
(
os
.
Stderr
,
"QueryStartFeeErr:%s
\n
"
,
err
.
Error
())
return
}
startFeeAmount
=
totalFee
.
Fee
}
if
end
<
0
{
//last block fee
currentHeight
,
totalFee
,
err
:=
queryCurrentTotalFee
(
rpc
)
if
err
!=
nil
{
fmt
.
Fprintf
(
os
.
Stderr
,
"QueryCurrentTotalFeeErr:%s
\n
"
,
err
.
Error
())
return
}
endFeeAmount
=
totalFee
.
Fee
end
=
currentHeight
}
else
{
totalFee
,
err
:=
queryTotalFeeWithHeight
(
end
,
rpc
)
if
err
!=
nil
{
fmt
.
Fprintf
(
os
.
Stderr
,
"QueryEndFeeErr:%s
\n
"
,
err
.
Error
())
return
}
endFeeAmount
=
totalFee
.
Fee
}
fee
:=
endFeeAmount
-
startFeeAmount
if
fee
<
0
{
fee
=
0
}
resp
:=
fmt
.
Sprintf
(
`{"startHeight":%d,"endHeight":%d, "totalFee":%s}`
,
start
,
end
,
commandtypes
.
FormatAmountValue2Display
(
fee
))
buf
:=
&
bytes
.
Buffer
{}
err
=
json
.
Indent
(
buf
,
[]
byte
(
resp
),
""
,
" "
)
if
err
!=
nil
{
fmt
.
Fprintf
(
os
.
Stderr
,
"JsonIndentResultErr:%s
\n
"
,
err
.
Error
())
return
}
fmt
.
Println
(
buf
.
String
())
}
//get last block header
func
getLastBlock
(
rpc
*
jsonclient
.
JSONClient
)
(
*
rpctypes
.
Header
,
error
)
{
res
:=
&
rpctypes
.
Header
{}
err
:=
rpc
.
Call
(
"Chain33.GetLastHeader"
,
nil
,
&
res
)
if
err
!=
nil
{
return
nil
,
err
}
return
res
,
nil
}
//get block hash with height
func
getBlockHash
(
height
int64
,
rpc
*
jsonclient
.
JSONClient
)
(
string
,
error
)
{
params
:=
types
.
ReqInt
{
Height
:
height
}
var
res
rpctypes
.
ReplyHash
err
:=
rpc
.
Call
(
"Chain33.GetBlockHash"
,
params
,
&
res
)
if
err
!=
nil
{
return
""
,
err
}
return
res
.
Hash
,
nil
}
func
queryCurrentTotalFee
(
rpc
*
jsonclient
.
JSONClient
)
(
int64
,
*
types
.
TotalFee
,
error
)
{
header
,
err
:=
getLastBlock
(
rpc
)
if
err
!=
nil
{
return
0
,
nil
,
err
}
fee
,
err
:=
queryTotalFeeWithHash
(
header
.
Hash
,
rpc
)
if
err
!=
nil
{
return
0
,
nil
,
err
}
return
header
.
Height
,
fee
,
nil
}
func
queryTotalFeeWithHeight
(
height
int64
,
rpc
*
jsonclient
.
JSONClient
)
(
*
types
.
TotalFee
,
error
)
{
hash
,
err
:=
getBlockHash
(
height
,
rpc
)
if
err
!=
nil
{
return
nil
,
err
}
fee
,
err
:=
queryTotalFeeWithHash
(
hash
,
rpc
)
if
err
!=
nil
{
return
nil
,
err
}
return
fee
,
nil
}
func
queryTotalFeeWithHash
(
blockHash
string
,
rpc
*
jsonclient
.
JSONClient
)
(
*
types
.
TotalFee
,
error
)
{
hash
,
err
:=
common
.
FromHex
(
blockHash
)
if
err
!=
nil
{
return
nil
,
err
}
//查询手续费
params
:=
types
.
LocalDBGet
{
Keys
:
[][]
byte
{
hash
[
:
]}}
res
:=
&
types
.
TotalFee
{}
err
=
rpc
.
Call
(
"Chain33.QueryTotalFee"
,
params
,
&
res
)
if
err
!=
nil
{
return
nil
,
err
}
return
res
,
nil
}
func
getBlocks
(
start
,
end
int64
,
rpc
*
jsonclient
.
JSONClient
)
(
*
rpctypes
.
BlockDetails
,
error
)
{
// 获取blocks
params
:=
rpctypes
.
BlockParam
{
Start
:
start
,
End
:
end
,
//Isdetail: false,
Isdetail
:
true
,
}
res
:=
&
rpctypes
.
BlockDetails
{}
err
:=
rpc
.
Call
(
"Chain33.GetBlocks"
,
params
,
&
res
)
if
err
!=
nil
{
return
nil
,
err
}
return
res
,
nil
}
vendor/github.com/33cn/chain33/system/dapp/commands/wallet.go
View file @
aa055772
...
...
@@ -171,14 +171,14 @@ func addWalletListTxsFlags(cmd *cobra.Command) {
cmd
.
Flags
()
.
Int32P
(
"count"
,
"c"
,
0
,
"number of transactions"
)
cmd
.
MarkFlagRequired
(
"count"
)
cmd
.
Flags
()
.
Int32P
(
"direction"
,
"d"
,
1
,
"query direction (0: pre page, 1: next page)"
)
cmd
.
Flags
()
.
Int32P
(
"direction"
,
"d"
,
0
,
"query direction (0: pre page, 1: next page)"
)
}
func
walletListTxs
(
cmd
*
cobra
.
Command
,
args
[]
string
)
{
rpcLaddr
,
_
:=
cmd
.
Flags
()
.
GetString
(
"rpc_laddr"
)
txHash
,
_
:=
cmd
.
Flags
()
.
GetString
(
"from"
)
count
,
_
:=
cmd
.
Flags
()
.
GetInt32
(
"count"
)
direction
,
_
:=
cmd
.
Flags
()
.
GetInt32
(
"dir"
)
direction
,
_
:=
cmd
.
Flags
()
.
GetInt32
(
"dir
ection
"
)
params
:=
rpctypes
.
ReqWalletTransactionList
{
FromTx
:
txHash
,
Count
:
count
,
...
...
vendor/github.com/33cn/chain33/system/store/mavl/db/tree.go
View file @
aa055772
...
...
@@ -116,6 +116,9 @@ func NewTree(db dbm.DB, sync bool) *Tree {
// 使能情况下非空创建当前整tree的缓存
if
enableMemTree
&&
memTree
==
nil
{
memTree
=
NewTreeMap
(
50
*
10000
)
if
tkCloseCacheLen
==
0
{
tkCloseCacheLen
=
10
*
10000
}
tkCloseCache
=
NewTreeARC
(
int
(
tkCloseCacheLen
))
}
return
&
Tree
{
...
...
vendor/github.com/33cn/chain33/types/block.go
View file @
aa055772
...
...
@@ -244,3 +244,8 @@ func (blockDetail *BlockDetail) filterParaTxGroup(tx *Transaction, index int) ([
}
return
txDetails
,
endIdx
}
// Size 获取blockDetail的Size
func
(
blockDetail
*
BlockDetail
)
Size
()
int
{
return
Size
(
blockDetail
)
}
vendor/github.com/33cn/chain33/types/blockchain.pb.go
View file @
aa055772
This diff is collapsed.
Click to expand it.
vendor/github.com/33cn/chain33/types/const.go
View file @
aa055772
...
...
@@ -61,7 +61,10 @@ const (
Float1E4
float64
=
10000.0
AirDropMinIndex
uint32
=
100000000
//通过钱包的seed生成一个空投地址,最小index索引
AirDropMaxIndex
uint32
=
101000000
//通过钱包的seed生成一个空投地址,最大index索引
MaxBlockCountPerTime
int64
=
1000
//从数据库中一次性获取block的最大数 1000个
MaxBlockSizePerTime
=
100000000
//从数据库中一次性获取block的最大size100M
AddBlock
int64
=
1
DelBlock
int64
=
2
)
//全局账户私钥/公钥
...
...
vendor/github.com/33cn/chain33/types/proto/blockchain.proto
View file @
aa055772
...
...
@@ -203,9 +203,11 @@ message BlockSequences {
//平行链区块详细信息
// blockdetail : 区块详细信息
// sequence :区块序列号
// isSync:写数据库时是否需要刷盘
message
ParaChainBlockDetail
{
BlockDetail
blockdetail
=
1
;
int64
sequence
=
2
;
bool
isSync
=
3
;
}
// 定义para交易结构
...
...
@@ -239,4 +241,5 @@ message ReqParaTxByTitle {
int64
start
=
1
;
int64
end
=
2
;
string
title
=
3
;
bool
isSeq
=
4
;
}
vendor/github.com/33cn/chain33/wallet/common/store.go
View file @
aa055772
...
...
@@ -202,10 +202,16 @@ func (store *Store) GetTxDetailByIter(TxList *types.ReqWalletTransactionList) (*
}
var
txbytes
[][]
byte
//FromTx是空字符串时。默认从最新的交易开始取count个
//FromTx是空字符串时,
//Direction == 0从最新的交易开始倒序取count个
//Direction == 1从最早的交易开始正序取count个
if
len
(
TxList
.
FromTx
)
==
0
{
list
:=
store
.
NewListHelper
()
txbytes
=
list
.
IteratorScanFromLast
(
CalcTxKey
(
""
),
TxList
.
Count
)
if
TxList
.
Direction
==
0
{
txbytes
=
list
.
IteratorScanFromLast
(
CalcTxKey
(
""
),
TxList
.
Count
)
}
else
{
txbytes
=
list
.
IteratorScanFromFirst
(
CalcTxKey
(
""
),
TxList
.
Count
)
}
if
len
(
txbytes
)
==
0
{
storelog
.
Error
(
"GetTxDetailByIter IteratorScanFromLast does not exist tx!"
)
return
nil
,
types
.
ErrTxNotExist
...
...
vendor/github.com/33cn/chain33/wallet/wallet_test.go
View file @
aa055772
...
...
@@ -415,9 +415,11 @@ func testProcImportPrivKey(t *testing.T, wallet *Wallet) {
func
testProcWalletTxList
(
t
*
testing
.
T
,
wallet
*
Wallet
)
{
println
(
"TestProcWalletTxList begin"
)
//倒序获取最新的三笔交易
txList
:=
&
types
.
ReqWalletTransactionList
{
Count
:
3
,
Direction
:
1
,
Direction
:
0
,
FromTx
:
[]
byte
(
""
),
}
msg
:=
wallet
.
client
.
NewMessage
(
"wallet"
,
types
.
EventWalletTransactionList
,
txList
)
...
...
@@ -427,14 +429,26 @@ func testProcWalletTxList(t *testing.T, wallet *Wallet) {
walletTxDetails
:=
resp
.
GetData
()
.
(
*
types
.
WalletTxDetails
)
var
FromTxstr
string
index
:=
make
([]
int64
,
3
)
if
len
(
walletTxDetails
.
TxDetails
)
!=
3
{
t
.
Error
(
"testProcWalletTxList failed"
)
}
println
(
"TestProcWalletTxList dir last-------"
)
for
_
,
walletTxDetail
:=
range
walletTxDetails
.
TxDetails
{
for
i
,
walletTxDetail
:=
range
walletTxDetails
.
TxDetails
{
println
(
"TestProcWalletTxList"
,
"Direction"
,
txList
.
Direction
,
"WalletTxDetail"
,
walletTxDetail
.
String
())
index
[
i
]
=
walletTxDetail
.
GetHeight
()
*
100000
+
walletTxDetail
.
GetIndex
()
FromTxstr
=
fmt
.
Sprintf
(
"%018d"
,
walletTxDetail
.
GetHeight
()
*
100000
+
walletTxDetail
.
GetIndex
())
}
//倒序index值的判断,index[0]>index[1]>index[2]
if
index
[
0
]
<=
index
[
1
]
{
println
(
"TestProcWalletTxList"
,
"index[0]"
,
index
[
0
],
"index[1]"
,
index
[
1
])
t
.
Error
(
"testProcWalletTxList:Reverse check fail!"
)
}
if
index
[
1
]
<=
index
[
2
]
{
println
(
"TestProcWalletTxList"
,
"index[1]"
,
index
[
1
],
"index[2]"
,
index
[
2
])
t
.
Error
(
"testProcWalletTxList:Reverse check fail!"
)
}
txList
.
Direction
=
1
txList
.
Count
=
2
...
...
@@ -466,6 +480,34 @@ func testProcWalletTxList(t *testing.T, wallet *Wallet) {
for
_
,
walletTxDetail
:=
range
walletTxDetails
.
TxDetails
{
println
(
"TestProcWalletTxList"
,
"Direction"
,
txList
.
Direction
,
"WalletTxDetail"
,
walletTxDetail
.
String
())
}
//正序获取最早的三笔交易
txList
=
&
types
.
ReqWalletTransactionList
{
Count
:
3
,
Direction
:
1
,
FromTx
:
[]
byte
(
""
),
}
msg
=
wallet
.
client
.
NewMessage
(
"wallet"
,
types
.
EventWalletTransactionList
,
txList
)
wallet
.
client
.
Send
(
msg
,
true
)
resp
,
err
=
wallet
.
client
.
Wait
(
msg
)
require
.
NoError
(
t
,
err
)
walletTxDetails
=
resp
.
GetData
()
.
(
*
types
.
WalletTxDetails
)
if
len
(
walletTxDetails
.
TxDetails
)
!=
3
{
t
.
Error
(
"testProcWalletTxList failed"
)
}
for
i
,
walletTxDetail
:=
range
walletTxDetails
.
TxDetails
{
index
[
i
]
=
walletTxDetail
.
GetHeight
()
*
100000
+
walletTxDetail
.
GetIndex
()
}
//正序index值的判断,index[0]<index[1]<index[2]
if
index
[
0
]
>=
index
[
1
]
{
println
(
"TestProcWalletTxList"
,
"index[0]"
,
index
[
0
],
"index[1]"
,
index
[
1
])
t
.
Error
(
"testProcWalletTxList:positive check fail!"
)
}
if
index
[
1
]
>=
index
[
2
]
{
println
(
"TestProcWalletTxList"
,
"index[1]"
,
index
[
1
],
"index[2]"
,
index
[
2
])
t
.
Error
(
"testProcWalletTxList:positive check fail!"
)
}
println
(
"TestProcWalletTxList end"
)
println
(
"--------------------------"
)
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment