• R/O
  • HTTP
  • SSH
  • HTTPS

vapor: コミット

Golang implemented sidechain for Bytom


コミットメタ情報

リビジョン7860cae2260bdaf58d3d353ba77fc5d45f87d3b0 (tree)
日時2019-08-21 17:58:48
作者Paladz <yzhu101@uott...>
コミッターGitHub

ログメッセージ

Merge branch 'master' into feature_listchain_api

変更サマリ

差分

--- a/README.md
+++ b/README.md
@@ -107,6 +107,25 @@ To remove a containner:
107107 $ docker rm <containerId>
108108 ```
109109
110+### Reward distribution tool
111+
112+After the supernode and alternative node receive the reward from the node, they will allocate the reward
113+
114+according to the interest rate.
115+
116+The reward calculation rules:
117+
118+ calculate the reward (consensus reward * interest rate * voting weight) according to the weight of votes
119+
120+cast in consensus around, and choose how many rounds of consensus to allocate the reward flexibly.
121+
122+[Tool usage details](./cmd/votereward/README.md)
123+
124+
125+### Merger utxo
126+UTXO has been merged to solve the problem that too much UTXO input causes a failed send transaction to fail.
127+[details](./cmd/utxomerge/README.md)
128+
110129 ## License
111130
112131 [AGPL v3](./LICENSE)
--- a/account/builder.go
+++ b/account/builder.go
@@ -18,7 +18,7 @@ import (
1818
1919 var (
2020 //chainTxUtxoNum maximum utxo quantity in a tx
21- chainTxUtxoNum = 5
21+ chainTxUtxoNum = 20
2222 //chainTxMergeGas chain tx gas
2323 chainTxMergeGas = uint64(10000000)
2424 )
--- a/cmd/consensusreward/main.go
+++ b/cmd/consensusreward/main.go
@@ -15,9 +15,9 @@ import (
1515 const logModule = "consensusereward"
1616
1717 var (
18- rewardStartHeight uint64
19- rewardEndHeight uint64
20- configFile string
18+ startHeight uint64
19+ endHeight uint64
20+ configFile string
2121 )
2222
2323 var RootCmd = &cobra.Command{
@@ -27,8 +27,8 @@ var RootCmd = &cobra.Command{
2727 }
2828
2929 func init() {
30- RootCmd.Flags().Uint64Var(&rewardStartHeight, "reward_start_height", 0, "The starting height of the distributive income reward interval, It is a multiple of the dpos consensus cycle(1200). example: 1200")
31- RootCmd.Flags().Uint64Var(&rewardEndHeight, "reward_end_height", 0, "The end height of the distributive income reward interval, It is a multiple of the dpos consensus cycle(1200). example: 2400")
30+ RootCmd.Flags().Uint64Var(&startHeight, "start_height", 0, "The starting height of the distributive income reward interval, It is a multiple of the dpos consensus cycle(1200). example: 1200")
31+ RootCmd.Flags().Uint64Var(&endHeight, "end_height", 0, "The end height of the distributive income reward interval, It is a multiple of the dpos consensus cycle(1200). example: 2400")
3232 RootCmd.Flags().StringVar(&configFile, "config_file", "reward.json", "config file. default: reward.json")
3333 }
3434
@@ -38,11 +38,11 @@ func runReward(cmd *cobra.Command, args []string) error {
3838 if err := cfg.LoadConfigFile(configFile, config); err != nil {
3939 log.WithFields(log.Fields{"module": logModule, "config": configFile, "error": err}).Fatal("Failded to load config file.")
4040 }
41- if rewardStartHeight >= rewardEndHeight || rewardStartHeight%consensus.ActiveNetParams.RoundVoteBlockNums != 0 || rewardEndHeight%consensus.ActiveNetParams.RoundVoteBlockNums != 0 {
41+ if startHeight >= endHeight || startHeight%consensus.ActiveNetParams.RoundVoteBlockNums != 0 || endHeight%consensus.ActiveNetParams.RoundVoteBlockNums != 0 {
4242 log.Fatal("Please check the height range, which must be multiple of the number of block rounds.")
4343 }
4444
45- s := consensusreward.NewStandbyNodeReward(config, rewardStartHeight, rewardEndHeight)
45+ s := consensusreward.NewStandbyNodeReward(config, startHeight, endHeight)
4646 if err := s.Settlement(); err != nil {
4747 log.WithFields(log.Fields{"module": logModule, "error": err}).Fatal("Standby node rewards failure.")
4848 }
--- /dev/null
+++ b/cmd/utxomerge/README.md
@@ -0,0 +1,24 @@
1+tool use
2+
3+params
4+
5+```shell
6+merge utxo.
7+
8+Usage:
9+ utxomerge [flags]
10+
11+Flags:
12+ --account_id string The accountID of utxo needs to be merged
13+ --address string The received address after merging utxo
14+ --amount uint Total amount of merged utxo
15+ -h, --help help for utxomerge
16+ --host_port string The url for the node. Default:http://127.0.0.1:9889 (default "http://127.0.0.1:9889")
17+ --password string Password of the account
18+```
19+
20+example:
21+
22+```shell
23+./utxomerge --host_port http://127.0.0.1:9889 --account_id 9e54300d-f81d-4c5f-bef3-4e771042d394 --password 123456 --address sp1q8u7xu3e389awrnct0x4flx0h3v7mrfnmpu858p --amount 200000000000
24+```
\ No newline at end of file
--- /dev/null
+++ b/cmd/utxomerge/main.go
@@ -0,0 +1,45 @@
1+package main
2+
3+import (
4+ log "github.com/sirupsen/logrus"
5+ "github.com/spf13/cobra"
6+ "github.com/tendermint/tmlibs/cli"
7+
8+ "github.com/vapor/toolbar/mergeutxo"
9+)
10+
11+var RootCmd = &cobra.Command{
12+ Use: "utxomerge",
13+ Short: "merge utxo.",
14+ RunE: runReward,
15+}
16+
17+var (
18+ hostPort, accountID, password, address string
19+ amount uint64
20+)
21+
22+func init() {
23+ RootCmd.Flags().StringVar(&hostPort, "host_port", "http://127.0.0.1:9889", "The url for the node. Default:http://127.0.0.1:9889")
24+ RootCmd.Flags().StringVar(&accountID, "account_id", "", "The accountID of utxo needs to be merged")
25+ RootCmd.Flags().StringVar(&password, "password", "", "Password of the account")
26+ RootCmd.Flags().StringVar(&address, "address", "", "The received address after merging utxo")
27+ RootCmd.Flags().Uint64Var(&amount, "amount", 0, "Total amount of merged utxo")
28+}
29+
30+func runReward(cmd *cobra.Command, args []string) error {
31+ log.Info("This tool belongs to an open-source project, we can not guarantee this tool is bug-free. Please check the code before using, developers will not be responsible for any asset loss due to bug!")
32+ txIDs, err := mergeutxo.MergeUTXO(hostPort, accountID, password, address, amount)
33+ if err != nil {
34+ log.Fatal(err)
35+ }
36+
37+ log.Info("Merge utxo successfully. txID: ", txIDs)
38+
39+ return nil
40+}
41+
42+func main() {
43+ cmd := cli.PrepareBaseCmd(RootCmd, "merge_utxo", "./")
44+ cmd.Execute()
45+}
--- a/cmd/votereward/README.md
+++ b/cmd/votereward/README.md
@@ -1,10 +1,20 @@
1-A `reward.json` would look like this:
1+## database
2+
3+- Create a MySQL database locally or with server installation
4+- Import table structure to MySQL database, table structure path: vapor/toolbar/vote_reward/database/dump_reward.sql
5+
6+
7+
8+## configuration file
9+
10+- Default file name:reward.json
11+- A `reward.json` would look like this:
212
313 ```json
414 {
5- "node_ip": "http://127.0.0.1:9889",
6- "chain_id": "solonet",
7- "mysql": {
15+ "node_ip": "http://127.0.0.1:9889", // node API address, replace with self node API address
16+ "chain_id": "mainnet", //Node network type
17+ "mysql": { // Mysql connection information
818 "connection": {
919 "host": "192.168.30.186",
1020 "port": 3306,
@@ -12,14 +22,14 @@ A `reward.json` would look like this:
1222 "password": "123456",
1323 "database": "reward"
1424 },
15- "log_mode": false
25+ "log_mode": false // default
1626 },
1727 "reward_config": {
18- "xpub": "9742a39a0bcfb5b7ac8f56f1894fbb694b53ebf58f9a032c36cc22d57a06e49e94ff7199063fb7a78190624fa3530f611404b56fc9af91dcaf4639614512cb64",
19- "account_id": "bd775113-49e0-4678-94bf-2b853f1afe80",
20- "password": "123456",
21- "reward_ratio": 20,
22- "mining_address": "sp1qfpgjve27gx0r9t7vud8vypplkzytgrvqr74rwz"
28+ "xpub": "9742a39a0bcfb5b7ac8f56f1894fbb694b53ebf58f9a032c36cc22d57a06e49e94ff7199063fb7a78190624fa3530f611404b56fc9af91dcaf4639614512cb64", // Node public key (from dashboard Settings), replaced with its own
29+ "account_id": "bd775113-49e0-4678-94bf-2b853f1afe80", // accountID
30+ "password": "123456",// The password corresponding to the account ID
31+ "reward_ratio": 20,// The percentage of a reward given to a voter per block
32+ "mining_address": "sp1qfpgjve27gx0r9t7vud8vypplkzytgrvqr74rwz" // The address that receives the block reward, use the get-mining- address for mining address, for example, curl -x POST http://127.0.0.1:9889/get-mining-address -d '{}'
2333 }
2434 }
2535 ```
@@ -49,3 +59,8 @@ example:
4959 ./votereward reward --reward_start_height 6000 --reward_end_height 7200
5060 ```
5161
62+
63+
64+Note:
65+
66+When an error (Gas credit has been spent) is returned, UTXO needs to be merged.
\ No newline at end of file
--- a/cmd/votereward/main.go
+++ b/cmd/votereward/main.go
@@ -35,6 +35,7 @@ func init() {
3535 }
3636
3737 func runReward(cmd *cobra.Command, args []string) error {
38+ log.Info("This tool belongs to an open-source project, we can not guarantee this tool is bug-free. Please check the code before using, developers will not be responsible for any asset loss due to bug!")
3839 startTime := time.Now()
3940 config := &cfg.Config{}
4041 if err := cfg.LoadConfigFile(configFile, config); err != nil {
--- a/log/log.go
+++ b/log/log.go
@@ -68,15 +68,18 @@ func (hook *BtmHook) ioWrite(entry *logrus.Entry) error {
6868 return err
6969 }
7070
71- _, err = writer.Write(msg)
72- return err
71+ if _, err = writer.Write(msg); err != nil {
72+ return err
73+ }
74+
75+ return writer.Close()
7376 }
7477
7578 func clearLockFiles(logPath string) error {
7679 files, err := ioutil.ReadDir(logPath)
7780 if os.IsNotExist(err) {
7881 return nil
79- }else if err != nil {
82+ } else if err != nil {
8083 return err
8184 }
8285
--- a/netsync/chainmgr/fast_sync.go
+++ b/netsync/chainmgr/fast_sync.go
@@ -20,8 +20,10 @@ var (
2020 fastSyncPivotGap = uint64(64)
2121 minGapStartFastSync = uint64(128)
2222
23- errNoSyncPeer = errors.New("can't find sync peer")
24- errSkeletonSize = errors.New("fast sync skeleton size wrong")
23+ errNoSyncPeer = errors.New("can't find sync peer")
24+ errSkeletonSize = errors.New("fast sync skeleton size wrong")
25+ errNoMainSkeleton = errors.New("No main skeleton found")
26+ errNoSkeletonFound = errors.New("No skeleton found")
2527 )
2628
2729 type fastSync struct {
@@ -83,12 +85,12 @@ func (fs *fastSync) createFetchBlocksTasks(stopBlock *types.Block) ([]*fetchBloc
8385 stopHash := stopBlock.Hash()
8486 skeletonMap := fs.msgFetcher.parallelFetchHeaders(peers, fs.blockLocator(), &stopHash, numOfBlocksSkeletonGap-1)
8587 if len(skeletonMap) == 0 {
86- return nil, errors.New("No skeleton found")
88+ return nil, errNoSkeletonFound
8789 }
8890
8991 mainSkeleton, ok := skeletonMap[fs.mainSyncPeer.ID()]
9092 if !ok {
91- return nil, errors.New("No main skeleton found")
93+ return nil, errNoMainSkeleton
9294 }
9395
9496 if len(mainSkeleton) < minSizeOfSyncSkeleton || len(mainSkeleton) > maxSizeOfSyncSkeleton {
--- a/netsync/chainmgr/fast_sync_test.go
+++ b/netsync/chainmgr/fast_sync_test.go
@@ -3,12 +3,15 @@ package chainmgr
33 import (
44 "io/ioutil"
55 "os"
6+ "reflect"
7+ "sync"
68 "testing"
79 "time"
810
911 "github.com/vapor/consensus"
1012 dbm "github.com/vapor/database/leveldb"
1113 "github.com/vapor/errors"
14+ "github.com/vapor/netsync/peers"
1215 "github.com/vapor/protocol/bc"
1316 "github.com/vapor/protocol/bc/types"
1417 "github.com/vapor/test/mock"
@@ -181,3 +184,175 @@ func TestFastBlockSync(t *testing.T) {
181184 }
182185 }
183186 }
187+
188+type mockFetcher struct {
189+ baseChain []*types.Block
190+ peerStatus map[string][]*types.Block
191+ peers []string
192+ testType int
193+}
194+
195+func (mf *mockFetcher) resetParameter() {
196+ return
197+}
198+
199+func (mf *mockFetcher) addSyncPeer(peerID string) {
200+ return
201+}
202+
203+func (mf *mockFetcher) requireBlock(peerID string, height uint64) (*types.Block, error) {
204+ return nil, nil
205+}
206+
207+func (mf *mockFetcher) parallelFetchBlocks(work []*fetchBlocksWork, downloadNotifyCh chan struct{}, ProcessStopCh chan struct{}, wg *sync.WaitGroup) {
208+ return
209+}
210+
211+func (mf *mockFetcher) parallelFetchHeaders(peers []*peers.Peer, locator []*bc.Hash, stopHash *bc.Hash, skip uint64) map[string][]*types.BlockHeader {
212+ result := make(map[string][]*types.BlockHeader)
213+ switch mf.testType {
214+ case 1:
215+ result["peer1"] = []*types.BlockHeader{&mf.peerStatus["peer1"][1000].BlockHeader, &mf.peerStatus["peer1"][1100].BlockHeader, &mf.peerStatus["peer1"][1200].BlockHeader,
216+ &mf.peerStatus["peer1"][1300].BlockHeader, &mf.peerStatus["peer1"][1400].BlockHeader, &mf.peerStatus["peer1"][1500].BlockHeader,
217+ &mf.peerStatus["peer1"][1600].BlockHeader, &mf.peerStatus["peer1"][1700].BlockHeader, &mf.peerStatus["peer1"][1800].BlockHeader,
218+ }
219+ result["peer2"] = []*types.BlockHeader{&mf.peerStatus["peer2"][1000].BlockHeader, &mf.peerStatus["peer2"][1100].BlockHeader, &mf.peerStatus["peer2"][1200].BlockHeader,
220+ &mf.peerStatus["peer2"][1300].BlockHeader, &mf.peerStatus["peer2"][1400].BlockHeader, &mf.peerStatus["peer2"][1500].BlockHeader,
221+ &mf.peerStatus["peer2"][1600].BlockHeader, &mf.peerStatus["peer2"][1700].BlockHeader, &mf.peerStatus["peer2"][1800].BlockHeader,
222+ }
223+
224+ case 2:
225+ result["peer1"] = []*types.BlockHeader{}
226+ case 3:
227+ case 4:
228+ result["peer2"] = []*types.BlockHeader{&mf.peerStatus["peer2"][1000].BlockHeader, &mf.peerStatus["peer2"][1100].BlockHeader, &mf.peerStatus["peer2"][1200].BlockHeader,
229+ &mf.peerStatus["peer2"][1300].BlockHeader, &mf.peerStatus["peer2"][1400].BlockHeader, &mf.peerStatus["peer2"][1500].BlockHeader,
230+ &mf.peerStatus["peer2"][1600].BlockHeader, &mf.peerStatus["peer2"][1700].BlockHeader, &mf.peerStatus["peer2"][1800].BlockHeader,
231+ }
232+ case 5:
233+ result["peer1"] = []*types.BlockHeader{&mf.peerStatus["peer1"][1000].BlockHeader, &mf.peerStatus["peer1"][1100].BlockHeader, &mf.peerStatus["peer1"][1200].BlockHeader,
234+ &mf.peerStatus["peer1"][1300].BlockHeader, &mf.peerStatus["peer1"][1400].BlockHeader, &mf.peerStatus["peer1"][1500].BlockHeader,
235+ &mf.peerStatus["peer1"][1600].BlockHeader, &mf.peerStatus["peer1"][1700].BlockHeader, &mf.peerStatus["peer1"][1800].BlockHeader,
236+ }
237+ result["peer2"] = []*types.BlockHeader{&mf.peerStatus["peer2"][1000].BlockHeader, &mf.peerStatus["peer2"][1100].BlockHeader, &mf.peerStatus["peer2"][1200].BlockHeader,
238+ &mf.peerStatus["peer2"][1300].BlockHeader, &mf.peerStatus["peer2"][1400].BlockHeader, &mf.peerStatus["peer2"][1500].BlockHeader,
239+ &mf.peerStatus["peer2"][1600].BlockHeader, &mf.peerStatus["peer2"][1700].BlockHeader,
240+ }
241+ }
242+ return result
243+}
244+
245+func TestCreateFetchBlocksTasks(t *testing.T) {
246+ baseChain := mockBlocks(nil, 1000)
247+ chainX := append(baseChain, mockBlocks(baseChain[1000], 2000)...)
248+ chainY := append(baseChain, mockBlocks(baseChain[1000], 1900)...)
249+ peerStatus := make(map[string][]*types.Block)
250+ peerStatus["peer1"] = chainX
251+ peerStatus["peer2"] = chainY
252+ type syncPeer struct {
253+ peer *P2PPeer
254+ bestHeight uint64
255+ irreversibleHeight uint64
256+ }
257+
258+ cases := []struct {
259+ peers []*syncPeer
260+ mainSyncPeer string
261+ testType int
262+ wantTasks []*fetchBlocksWork
263+ wantErr error
264+ }{
265+ // normal test
266+ {
267+ peers: []*syncPeer{
268+ {peer: &P2PPeer{id: "peer1", flag: consensus.SFFullNode | consensus.SFFastSync}, bestHeight: 1000, irreversibleHeight: 1000},
269+ {peer: &P2PPeer{id: "peer2", flag: consensus.SFFullNode | consensus.SFFastSync}, bestHeight: 800, irreversibleHeight: 800},
270+ },
271+ mainSyncPeer: "peer1",
272+ testType: 1,
273+ wantTasks: []*fetchBlocksWork{
274+ {&chainX[1000].BlockHeader, &chainX[1100].BlockHeader}, {&chainX[1100].BlockHeader, &chainX[1200].BlockHeader},
275+ {&chainX[1200].BlockHeader, &chainX[1300].BlockHeader}, {&chainX[1300].BlockHeader, &chainX[1400].BlockHeader},
276+ {&chainX[1400].BlockHeader, &chainX[1500].BlockHeader}, {&chainX[1500].BlockHeader, &chainX[1600].BlockHeader},
277+ {&chainX[1600].BlockHeader, &chainX[1700].BlockHeader}, {&chainX[1700].BlockHeader, &chainX[1800].BlockHeader},
278+ },
279+ wantErr: nil,
280+ },
281+ // test no sync peer
282+ {
283+ peers: []*syncPeer{},
284+ mainSyncPeer: "peer1",
285+ testType: 0,
286+ wantTasks: nil,
287+ wantErr: errNoSyncPeer,
288+ },
289+ // primary sync peer skeleton size error
290+ {
291+ peers: []*syncPeer{
292+ {peer: &P2PPeer{id: "peer1", flag: consensus.SFFullNode | consensus.SFFastSync}, bestHeight: 1000, irreversibleHeight: 1000},
293+ {peer: &P2PPeer{id: "peer2", flag: consensus.SFFullNode | consensus.SFFastSync}, bestHeight: 800, irreversibleHeight: 800},
294+ },
295+ mainSyncPeer: "peer1",
296+ testType: 2,
297+ wantTasks: nil,
298+ wantErr: errSkeletonSize,
299+ },
300+ // no skeleton return
301+ {
302+ peers: []*syncPeer{
303+ {peer: &P2PPeer{id: "peer1", flag: consensus.SFFullNode | consensus.SFFastSync}, bestHeight: 1000, irreversibleHeight: 1000},
304+ {peer: &P2PPeer{id: "peer2", flag: consensus.SFFullNode | consensus.SFFastSync}, bestHeight: 800, irreversibleHeight: 800},
305+ },
306+ mainSyncPeer: "peer1",
307+ testType: 3,
308+ wantTasks: nil,
309+ wantErr: errNoSkeletonFound,
310+ },
311+ // no main skeleton found
312+ {
313+ peers: []*syncPeer{
314+ {peer: &P2PPeer{id: "peer1", flag: consensus.SFFullNode | consensus.SFFastSync}, bestHeight: 1000, irreversibleHeight: 1000},
315+ {peer: &P2PPeer{id: "peer2", flag: consensus.SFFullNode | consensus.SFFastSync}, bestHeight: 800, irreversibleHeight: 800},
316+ },
317+ mainSyncPeer: "peer1",
318+ testType: 4,
319+ wantTasks: nil,
320+ wantErr: errNoMainSkeleton,
321+ },
322+ // skeleton length mismatch
323+ {
324+ peers: []*syncPeer{
325+ {peer: &P2PPeer{id: "peer1", flag: consensus.SFFullNode | consensus.SFFastSync}, bestHeight: 1000, irreversibleHeight: 1000},
326+ {peer: &P2PPeer{id: "peer2", flag: consensus.SFFullNode | consensus.SFFastSync}, bestHeight: 800, irreversibleHeight: 800},
327+ },
328+ mainSyncPeer: "peer1",
329+ testType: 5,
330+ wantTasks: []*fetchBlocksWork{
331+ {&chainX[1000].BlockHeader, &chainX[1100].BlockHeader}, {&chainX[1100].BlockHeader, &chainX[1200].BlockHeader},
332+ {&chainX[1200].BlockHeader, &chainX[1300].BlockHeader}, {&chainX[1300].BlockHeader, &chainX[1400].BlockHeader},
333+ {&chainX[1400].BlockHeader, &chainX[1500].BlockHeader}, {&chainX[1500].BlockHeader, &chainX[1600].BlockHeader},
334+ {&chainX[1600].BlockHeader, &chainX[1700].BlockHeader}, {&chainX[1700].BlockHeader, &chainX[1800].BlockHeader},
335+ },
336+ wantErr: nil,
337+ },
338+ }
339+
340+ for i, c := range cases {
341+ peers := peers.NewPeerSet(NewPeerSet())
342+ for _, syncPeer := range c.peers {
343+ peers.AddPeer(syncPeer.peer)
344+ peers.SetStatus(syncPeer.peer.id, syncPeer.bestHeight, nil)
345+ peers.SetIrreversibleStatus(syncPeer.peer.id, syncPeer.irreversibleHeight, nil)
346+ }
347+ mockChain := mock.NewChain(nil)
348+ fs := newFastSync(mockChain, &mockFetcher{baseChain: baseChain, peerStatus: peerStatus, testType: c.testType}, nil, peers)
349+ fs.mainSyncPeer = fs.peers.GetPeer(c.mainSyncPeer)
350+ tasks, err := fs.createFetchBlocksTasks(baseChain[700])
351+ if err != c.wantErr {
352+ t.Errorf("case %d: got %v want %v", i, err, c.wantErr)
353+ }
354+ if !reflect.DeepEqual(tasks, c.wantTasks) {
355+ t.Errorf("case %d: got %v want %v", i, tasks, c.wantTasks)
356+ }
357+ }
358+}
--- /dev/null
+++ b/netsync/chainmgr/peers_test.go
@@ -0,0 +1,116 @@
1+package chainmgr
2+
3+import (
4+ "testing"
5+)
6+
7+func TestAddDel(t *testing.T) {
8+ syncPeers := newFastSyncPeers()
9+ peers := make(map[string]bool)
10+ peers["Peer1"] = true
11+ peers["Peer2"] = true
12+ for k := range peers {
13+ syncPeers.add(k)
14+ syncPeers.add(k)
15+ }
16+ if syncPeers.size() != len(peers) {
17+ t.Errorf("add peer test err: got %d\nwant %d", syncPeers.size(), len(peers))
18+ }
19+
20+ syncPeers.delete("Peer1")
21+ if syncPeers.size() != 1 {
22+ t.Errorf("add peer test err: got %d\nwant %d", syncPeers.size(), 1)
23+ }
24+
25+ syncPeers.delete("Peer1")
26+ if syncPeers.size() != 1 {
27+ t.Errorf("add peer test err: got %d\nwant %d", syncPeers.size(), 1)
28+ }
29+}
30+
31+func TestIdlePeers(t *testing.T) {
32+ syncPeers := newFastSyncPeers()
33+ peers := make(map[string]bool)
34+ peers["Peer1"] = true
35+ peers["Peer2"] = true
36+ for k := range peers {
37+ syncPeers.add(k)
38+ syncPeers.add(k)
39+ }
40+
41+ idlePeers := syncPeers.selectIdlePeers()
42+ if len(idlePeers) != len(peers) {
43+ t.Errorf("selcet idle peers test err: got %d\nwant %d", len(idlePeers), len(peers))
44+ }
45+
46+ for _, peer := range idlePeers {
47+ if ok := peers[peer]; !ok {
48+ t.Errorf("selcet idle peers test err: want peers %v got %v", peers, idlePeers)
49+ }
50+ }
51+
52+ idlePeers = syncPeers.selectIdlePeers()
53+ if len(idlePeers) != 0 {
54+ t.Errorf("selcet idle peers test err: got %d\nwant %d", len(idlePeers), 0)
55+ }
56+
57+}
58+
59+func TestIdlePeer(t *testing.T) {
60+ syncPeers := newFastSyncPeers()
61+ peers := make(map[string]bool)
62+ peers["Peer1"] = true
63+ peers["Peer2"] = true
64+ for k := range peers {
65+ syncPeers.add(k)
66+ syncPeers.add(k)
67+ }
68+ idlePeer, err := syncPeers.selectIdlePeer()
69+ if err != nil {
70+ t.Errorf("selcet idle peers test err: got %v\nwant %v", err, nil)
71+ }
72+
73+ if ok := peers[idlePeer]; !ok {
74+ t.Error("selcet idle peers test err.")
75+ }
76+ idlePeer, err = syncPeers.selectIdlePeer()
77+ if err != nil {
78+ t.Errorf("selcet idle peers test err: got %v\nwant %v", err, nil)
79+ }
80+
81+ if ok := peers[idlePeer]; !ok {
82+ t.Error("selcet idle peers test err.")
83+ }
84+ idlePeer, err = syncPeers.selectIdlePeer()
85+ if err != errNoValidFastSyncPeer {
86+ t.Errorf("selcet idle peers test err: got %v\nwant %v", err, errNoValidFastSyncPeer)
87+ }
88+}
89+
90+func TestSetIdle(t *testing.T) {
91+ syncPeers := newFastSyncPeers()
92+ peers := make(map[string]bool)
93+ peers["Peer2"] = true
94+ for k := range peers {
95+ syncPeers.add(k)
96+ }
97+ if syncPeers.size() != len(peers) {
98+ t.Errorf("add peer test err: got %d\nwant %d", syncPeers.size(), len(peers))
99+ }
100+ idlePeers := syncPeers.selectIdlePeers()
101+ if len(idlePeers) != len(peers) {
102+ t.Errorf("selcet idle peers test err: got %d\nwant %d", len(idlePeers), len(peers))
103+ }
104+
105+ syncPeers.setIdle("Peer1")
106+ idlePeers = syncPeers.selectIdlePeers()
107+ if len(idlePeers) != 0 {
108+ t.Errorf("selcet idle peers test err: got %d\nwant %d", len(idlePeers), 0)
109+ }
110+
111+ syncPeers.setIdle("Peer2")
112+ idlePeers = syncPeers.selectIdlePeers()
113+ if len(idlePeers) != len(peers) {
114+ t.Errorf("selcet idle peers test err: got %d\nwant %d", len(idlePeers), len(peers))
115+ }
116+}
--- a/netsync/chainmgr/tool_test.go
+++ b/netsync/chainmgr/tool_test.go
@@ -11,6 +11,7 @@ import (
1111 dbm "github.com/vapor/database/leveldb"
1212
1313 "github.com/vapor/consensus"
14+ "github.com/vapor/event"
1415 "github.com/vapor/netsync/peers"
1516 "github.com/vapor/protocol/bc"
1617 "github.com/vapor/protocol/bc/types"
@@ -197,11 +198,12 @@ func mockSync(blocks []*types.Block, mempool *mock.Mempool, fastSyncDB dbm.DB) *
197198 }
198199
199200 return &Manager{
200- chain: chain,
201- blockKeeper: newBlockKeeper(chain, peers, fastSyncDB),
202- peers: peers,
203- mempool: mempool,
204- txSyncCh: make(chan *txSyncMsg),
201+ chain: chain,
202+ blockKeeper: newBlockKeeper(chain, peers, fastSyncDB),
203+ peers: peers,
204+ mempool: mempool,
205+ txSyncCh: make(chan *txSyncMsg),
206+ eventDispatcher: event.NewDispatcher(),
205207 }
206208 }
207209
--- a/netsync/chainmgr/tx_keeper_test.go
+++ b/netsync/chainmgr/tx_keeper_test.go
@@ -12,6 +12,7 @@ import (
1212 "github.com/vapor/consensus"
1313 dbm "github.com/vapor/database/leveldb"
1414 "github.com/vapor/protocol"
15+ core "github.com/vapor/protocol"
1516 "github.com/vapor/protocol/bc"
1617 "github.com/vapor/protocol/bc/types"
1718 "github.com/vapor/test/mock"
@@ -19,8 +20,11 @@ import (
1920
2021 const txsNumber = 2000
2122
22-func getTransactions() []*types.Tx {
23- txs := []*types.Tx{}
23+type mempool struct {
24+}
25+
26+func (m *mempool) GetTransactions() []*core.TxDesc {
27+ txs := []*core.TxDesc{}
2428 for i := 0; i < txsNumber; i++ {
2529 txInput := types.NewSpendInput(nil, bc.NewHash([32]byte{0x01}), *consensus.BTMAssetID, uint64(i), 1, []byte{0x51})
2630 txInput.CommitmentSuffix = []byte{0, 1, 2}
@@ -36,12 +40,13 @@ func getTransactions() []*types.Tx {
3640 Outputs: []*types.TxOutput{
3741 types.NewIntraChainOutput(*consensus.BTMAssetID, uint64(i), []byte{0x6a}),
3842 },
43+ SerializedSize: 1000,
3944 },
4045 Tx: &bc.Tx{
4146 ID: bc.Hash{V0: uint64(i), V1: uint64(i), V2: uint64(i), V3: uint64(i)},
4247 },
4348 }
44- txs = append(txs, tx)
49+ txs = append(txs, &core.TxDesc{Tx: tx})
4550 }
4651 return txs
4752 }
@@ -58,7 +63,7 @@ func TestSyncMempool(t *testing.T) {
5863 blocks := mockBlocks(nil, 5)
5964 a := mockSync(blocks, &mock.Mempool{}, testDBA)
6065 b := mockSync(blocks, &mock.Mempool{}, testDBB)
61-
66+ a.mempool = &mempool{}
6267 netWork := NewNetWork()
6368 netWork.Register(a, "192.168.0.1", "test node A", consensus.SFFullNode)
6469 netWork.Register(b, "192.168.0.2", "test node B", consensus.SFFullNode)
@@ -71,8 +76,7 @@ func TestSyncMempool(t *testing.T) {
7176
7277 go a.syncMempoolLoop()
7378 a.syncMempool("test node B")
74- wantTxs := getTransactions()
75- a.txSyncCh <- &txSyncMsg{"test node B", wantTxs}
79+ wantTxs := a.mempool.GetTransactions()
7680
7781 timeout := time.NewTimer(2 * time.Second)
7882 defer timeout.Stop()
@@ -99,14 +103,82 @@ out:
99103
100104 for i, gotTx := range gotTxs {
101105 index := gotTx.Tx.Inputs[0].Amount()
102- if !reflect.DeepEqual(gotTx.Tx.Inputs[0].Amount(), wantTxs[index].Inputs[0].Amount()) {
103- t.Fatalf("mempool tx err. index:%d\n,gotTx:%s\n,wantTx:%s", i, spew.Sdump(gotTx.Tx.Inputs), spew.Sdump(wantTxs[0].Inputs))
106+ if !reflect.DeepEqual(gotTx.Tx.Inputs[0].Amount(), wantTxs[index].Tx.Inputs[0].Amount()) {
107+ t.Fatalf("mempool tx err. index:%d\n,gotTx:%s\n,wantTx:%s", i, spew.Sdump(gotTx.Tx.Inputs), spew.Sdump(wantTxs[0].Tx.Inputs))
108+ }
109+
110+ if !reflect.DeepEqual(gotTx.Tx.Outputs[0].AssetAmount(), wantTxs[index].Tx.Outputs[0].AssetAmount()) {
111+ t.Fatalf("mempool tx err. index:%d\n,gotTx:%s\n,wantTx:%s", i, spew.Sdump(gotTx.Tx.Outputs), spew.Sdump(wantTxs[0].Tx.Outputs))
112+ }
113+ }
114+}
115+
116+func TestBroadcastTxsLoop(t *testing.T) {
117+ tmpDir, err := ioutil.TempDir(".", "")
118+ if err != nil {
119+ t.Fatalf("failed to create temporary data folder: %v", err)
120+ }
121+ defer os.RemoveAll(tmpDir)
122+ testDBA := dbm.NewDB("testdba", "leveldb", tmpDir)
123+ testDBB := dbm.NewDB("testdbb", "leveldb", tmpDir)
124+
125+ blocks := mockBlocks(nil, 5)
126+ a := mockSync(blocks, &mock.Mempool{}, testDBA)
127+ b := mockSync(blocks, &mock.Mempool{}, testDBB)
128+ a.mempool = &mempool{}
129+ netWork := NewNetWork()
130+ netWork.Register(a, "192.168.0.1", "test node A", consensus.SFFullNode)
131+ netWork.Register(b, "192.168.0.2", "test node B", consensus.SFFullNode)
132+ if B2A, A2B, err := netWork.HandsShake(a, b); err != nil {
133+ t.Errorf("fail on peer hands shake %v", err)
134+ } else {
135+ go B2A.postMan()
136+ go A2B.postMan()
137+ }
138+ a.txMsgSub, err = a.eventDispatcher.Subscribe(core.TxMsgEvent{})
139+ if err != nil {
140+ t.Fatal("txMsgSub subscribe err", err)
141+ }
142+ go a.broadcastTxsLoop()
143+ wantTxs := a.mempool.GetTransactions()
144+ txsNum := 50
145+ for i, txD := range wantTxs {
146+ if i >= txsNum {
147+ break
104148 }
149+ a.eventDispatcher.Post(core.TxMsgEvent{TxMsg: &core.TxPoolMsg{TxDesc: txD, MsgType: core.MsgNewTx}})
150+ }
151+ timeout := time.NewTimer(2 * time.Second)
152+ defer timeout.Stop()
153+ ticker := time.NewTicker(500 * time.Millisecond)
154+ defer ticker.Stop()
105155
106- if !reflect.DeepEqual(gotTx.Tx.Outputs[0].AssetAmount(), wantTxs[index].Outputs[0].AssetAmount()) {
107- t.Fatalf("mempool tx err. index:%d\n,gotTx:%s\n,wantTx:%s", i, spew.Sdump(gotTx.Tx.Outputs), spew.Sdump(wantTxs[0].Outputs))
156+ gotTxs := []*protocol.TxDesc{}
157+ for {
158+ select {
159+ case <-ticker.C:
160+ gotTxs = b.mempool.GetTransactions()
161+ if len(gotTxs) >= txsNum {
162+ goto out
163+ }
164+ case <-timeout.C:
165+ t.Fatalf("mempool sync timeout")
108166 }
167+ }
109168
169+out:
170+ if len(gotTxs) != txsNum {
171+ t.Fatalf("mempool sync txs num err. got:%d want:%d", len(gotTxs), txsNumber)
110172 }
111173
174+ for i, gotTx := range gotTxs {
175+ index := gotTx.Tx.Inputs[0].Amount()
176+ if !reflect.DeepEqual(gotTx.Tx.Inputs[0].Amount(), wantTxs[index].Tx.Inputs[0].Amount()) {
177+ t.Fatalf("mempool tx err. index:%d\n,gotTx:%s\n,wantTx:%s", i, spew.Sdump(gotTx.Tx.Inputs), spew.Sdump(wantTxs[0].Tx.Inputs))
178+ }
179+
180+ if !reflect.DeepEqual(gotTx.Tx.Outputs[0].AssetAmount(), wantTxs[index].Tx.Outputs[0].AssetAmount()) {
181+ t.Fatalf("mempool tx err. index:%d\n,gotTx:%s\n,wantTx:%s", i, spew.Sdump(gotTx.Tx.Outputs), spew.Sdump(wantTxs[0].Tx.Outputs))
182+ }
183+ }
112184 }
--- a/netsync/consensusmgr/block_fetcher.go
+++ b/netsync/consensusmgr/block_fetcher.go
@@ -1,45 +1,44 @@
11 package consensusmgr
22
33 import (
4- "github.com/sirupsen/logrus"
4+ log "github.com/sirupsen/logrus"
55 "gopkg.in/karalabe/cookiejar.v2/collections/prque"
66
7- "github.com/vapor/netsync/peers"
87 "github.com/vapor/p2p/security"
98 "github.com/vapor/protocol/bc"
109 )
1110
1211 const (
1312 maxBlockDistance = 64
14- maxMsgSetSize = 128
1513 newBlockChSize = 64
14+ msgLimit = 128 // peer message number limit
1615 )
1716
1817 // blockFetcher is responsible for accumulating block announcements from various peers
1918 // and scheduling them for retrieval.
2019 type blockFetcher struct {
2120 chain Chain
22- peers *peers.PeerSet
21+ peers Peers
2322
2423 newBlockCh chan *blockMsg
25- queue *prque.Prque
26- msgSet map[bc.Hash]*blockMsg
24+ queue *prque.Prque // block import priority queue
25+ msgSet map[bc.Hash]*blockMsg // already queued blocks
26+ msgCounter map[string]int // per peer msg counter to prevent DOS
2727 }
2828
2929 //NewBlockFetcher creates a block fetcher to retrieve blocks of the new propose.
30-func newBlockFetcher(chain Chain, peers *peers.PeerSet) *blockFetcher {
31- f := &blockFetcher{
30+func newBlockFetcher(chain Chain, peers Peers) *blockFetcher {
31+ return &blockFetcher{
3232 chain: chain,
3333 peers: peers,
3434 newBlockCh: make(chan *blockMsg, newBlockChSize),
3535 queue: prque.New(),
3636 msgSet: make(map[bc.Hash]*blockMsg),
37+ msgCounter: make(map[string]int),
3738 }
38- go f.blockProcessor()
39- return f
4039 }
4140
42-func (f *blockFetcher) blockProcessor() {
41+func (f *blockFetcher) blockProcessorLoop() {
4342 for {
4443 for !f.queue.Empty() {
4544 msg := f.queue.PopItem().(*blockMsg)
@@ -50,14 +49,25 @@ func (f *blockFetcher) blockProcessor() {
5049
5150 f.insert(msg)
5251 delete(f.msgSet, msg.block.Hash())
52+ f.msgCounter[msg.peerID]--
53+ if f.msgCounter[msg.peerID] <= 0 {
54+ delete(f.msgCounter, msg.peerID)
55+ }
5356 }
54- f.add(<-f.newBlockCh)
57+ f.add(<-f.newBlockCh, msgLimit)
5558 }
5659 }
5760
58-func (f *blockFetcher) add(msg *blockMsg) {
61+func (f *blockFetcher) add(msg *blockMsg, limit int) {
62+ // prevent DOS
63+ count := f.msgCounter[msg.peerID] + 1
64+ if count > limit {
65+ log.WithFields(log.Fields{"module": logModule, "peer": msg.peerID, "limit": limit}).Warn("The number of peer messages exceeds the limit")
66+ return
67+ }
68+
5969 bestHeight := f.chain.BestBlockHeight()
60- if len(f.msgSet) > maxMsgSetSize || bestHeight > msg.block.Height || msg.block.Height-bestHeight > maxBlockDistance {
70+ if bestHeight > msg.block.Height || msg.block.Height-bestHeight > maxBlockDistance {
6171 return
6272 }
6373
@@ -65,7 +75,8 @@ func (f *blockFetcher) add(msg *blockMsg) {
6575 if _, ok := f.msgSet[blockHash]; !ok {
6676 f.msgSet[blockHash] = msg
6777 f.queue.Push(msg, -float32(msg.block.Height))
68- logrus.WithFields(logrus.Fields{
78+ f.msgCounter[msg.peerID] = count
79+ log.WithFields(log.Fields{
6980 "module": logModule,
7081 "block height": msg.block.Height,
7182 "block hash": blockHash.String(),
@@ -80,7 +91,6 @@ func (f *blockFetcher) insert(msg *blockMsg) {
8091 if peer == nil {
8192 return
8293 }
83-
8494 f.peers.ProcessIllegal(msg.peerID, security.LevelMsgIllegal, err.Error())
8595 return
8696 }
@@ -91,12 +101,12 @@ func (f *blockFetcher) insert(msg *blockMsg) {
91101
92102 proposeMsg, err := NewBlockProposeMsg(msg.block)
93103 if err != nil {
94- logrus.WithFields(logrus.Fields{"module": logModule, "err": err}).Error("failed on create BlockProposeMsg")
104+ log.WithFields(log.Fields{"module": logModule, "err": err}).Error("failed on create BlockProposeMsg")
95105 return
96106 }
97107
98108 if err := f.peers.BroadcastMsg(NewBroadcastMsg(proposeMsg, consensusChannel)); err != nil {
99- logrus.WithFields(logrus.Fields{"module": logModule, "err": err}).Error("failed on broadcast proposed block")
109+ log.WithFields(log.Fields{"module": logModule, "err": err}).Error("failed on broadcast proposed block")
100110 return
101111 }
102112 }
--- a/netsync/consensusmgr/block_fetcher_test.go
+++ b/netsync/consensusmgr/block_fetcher_test.go
@@ -127,7 +127,7 @@ func TestBlockFetcher(t *testing.T) {
127127 },
128128 }
129129 fetcher := newBlockFetcher(newChain(), peers)
130-
130+ go fetcher.blockProcessorLoop()
131131 for i, c := range testCase {
132132 fetcher.processNewBlock(c.blockMsg)
133133 time.Sleep(10 * time.Millisecond)
@@ -137,3 +137,159 @@ func TestBlockFetcher(t *testing.T) {
137137 }
138138 }
139139 }
140+
141+func TestAddBlockMsg(t *testing.T) {
142+ peers := peers.NewPeerSet(&peerMgr{})
143+ testPeer := "peer1"
144+ testCase := []struct {
145+ blocksMsg []*blockMsg
146+ limit int
147+ queueSize int
148+ msgSetSize int
149+ msgCounter int
150+ }{
151+ //normal test
152+ {
153+ blocksMsg: []*blockMsg{
154+ {
155+ block: &types.Block{
156+ BlockHeader: types.BlockHeader{
157+ Height: 100,
158+ },
159+ },
160+ peerID: testPeer,
161+ },
162+ {
163+ block: &types.Block{
164+ BlockHeader: types.BlockHeader{
165+ Height: 101,
166+ },
167+ },
168+ peerID: testPeer,
169+ },
170+ {
171+ block: &types.Block{
172+ BlockHeader: types.BlockHeader{
173+ Height: 102,
174+ },
175+ },
176+ peerID: testPeer,
177+ },
178+ },
179+ limit: 5,
180+ queueSize: 3,
181+ msgSetSize: 3,
182+ msgCounter: 3,
183+ },
184+ // test DOS
185+ {
186+ blocksMsg: []*blockMsg{
187+ {
188+ block: &types.Block{
189+ BlockHeader: types.BlockHeader{
190+ Version: 1,
191+ Height: 100,
192+ },
193+ },
194+ peerID: testPeer,
195+ },
196+ {
197+ block: &types.Block{
198+ BlockHeader: types.BlockHeader{
199+ Version: 2,
200+ Height: 100,
201+ },
202+ },
203+ peerID: testPeer,
204+ },
205+ {
206+ block: &types.Block{
207+ BlockHeader: types.BlockHeader{
208+ Version: 3,
209+ Height: 100,
210+ },
211+ },
212+ peerID: testPeer,
213+ },
214+ {
215+ block: &types.Block{
216+ BlockHeader: types.BlockHeader{
217+ Version: 4,
218+ Height: 100,
219+ },
220+ },
221+ peerID: testPeer,
222+ },
223+ },
224+ limit: 3,
225+ queueSize: 3,
226+ msgSetSize: 3,
227+ msgCounter: 3,
228+ },
229+
230+ // test msg height does not meet the requirements
231+ {
232+ blocksMsg: []*blockMsg{
233+ {
234+ block: &types.Block{
235+ BlockHeader: types.BlockHeader{
236+ Version: 1,
237+ Height: 98,
238+ },
239+ },
240+ peerID: testPeer,
241+ },
242+ {
243+ block: &types.Block{
244+ BlockHeader: types.BlockHeader{
245+ Version: 2,
246+ Height: 97,
247+ },
248+ },
249+ peerID: testPeer,
250+ },
251+ {
252+ block: &types.Block{
253+ BlockHeader: types.BlockHeader{
254+ Version: 3,
255+ Height: 164,
256+ },
257+ },
258+ peerID: testPeer,
259+ },
260+ {
261+ block: &types.Block{
262+ BlockHeader: types.BlockHeader{
263+ Version: 4,
264+ Height: 165,
265+ },
266+ },
267+ peerID: testPeer,
268+ },
269+ },
270+ limit: 5,
271+ queueSize: 0,
272+ msgSetSize: 0,
273+ msgCounter: 0,
274+ },
275+ }
276+
277+ for i, c := range testCase {
278+ fetcher := newBlockFetcher(newChain(), peers)
279+ for _, msg := range c.blocksMsg {
280+ fetcher.add(msg, c.limit)
281+ }
282+
283+ if fetcher.queue.Size() != c.queueSize {
284+ t.Fatalf("index: %d queue size err got %d: want %d", i, fetcher.queue.Size(), c.queueSize)
285+ }
286+
287+ if len(fetcher.msgSet) != c.msgSetSize {
288+ t.Fatalf("index: %d msg set size err got %d: want %d", i, len(fetcher.msgSet), c.msgSetSize)
289+ }
290+
291+ if fetcher.msgCounter[testPeer] != c.msgCounter {
292+ t.Fatalf("index: %d peer msg counter err got %d: want %d", i, fetcher.msgCounter[testPeer], c.msgCounter)
293+ }
294+ }
295+}
--- a/netsync/consensusmgr/consensus_msg.go
+++ b/netsync/consensusmgr/consensus_msg.go
@@ -69,7 +69,7 @@ func (bs *BlockSignatureMsg) BroadcastMarkSendRecord(ps *peers.PeerSet, peers []
6969
7070 // BroadcastFilterTargetPeers filter target peers to filter the nodes that need to send messages.
7171 func (bs *BlockSignatureMsg) BroadcastFilterTargetPeers(ps *peers.PeerSet) []string {
72- return ps.PeersWithoutSign(bs.Signature)
72+ return ps.PeersWithoutSignature(bs.Signature)
7373 }
7474
7575 // BlockProposeMsg block propose message transferred between nodes.
--- a/netsync/consensusmgr/handle.go
+++ b/netsync/consensusmgr/handle.go
@@ -26,6 +26,17 @@ type Chain interface {
2626 ProcessBlockSignature(signature, pubkey []byte, blockHash *bc.Hash) error
2727 }
2828
29+type Peers interface {
30+ AddPeer(peer peers.BasePeer)
31+ BroadcastMsg(bm peers.BroadcastMsg) error
32+ GetPeer(id string) *peers.Peer
33+ MarkBlock(peerID string, hash *bc.Hash)
34+ MarkBlockSignature(peerID string, signature []byte)
35+ ProcessIllegal(peerID string, level byte, reason string)
36+ RemovePeer(peerID string)
37+ SetStatus(peerID string, height uint64, hash *bc.Hash)
38+}
39+
2940 type blockMsg struct {
3041 block *types.Block
3142 peerID string
@@ -35,7 +46,7 @@ type blockMsg struct {
3546 type Manager struct {
3647 sw Switch
3748 chain Chain
38- peers *peers.PeerSet
49+ peers Peers
3950 blockFetcher *blockFetcher
4051 eventDispatcher *event.Dispatcher
4152
@@ -43,7 +54,7 @@ type Manager struct {
4354 }
4455
4556 // NewManager create new manager.
46-func NewManager(sw Switch, chain Chain, dispatcher *event.Dispatcher, peers *peers.PeerSet) *Manager {
57+func NewManager(sw Switch, chain Chain, peers Peers, dispatcher *event.Dispatcher) *Manager {
4758 manager := &Manager{
4859 sw: sw,
4960 chain: chain,
@@ -180,6 +191,7 @@ func (m *Manager) removePeer(peerID string) {
180191
181192 //Start consensus manager service.
182193 func (m *Manager) Start() error {
194+ go m.blockFetcher.blockProcessorLoop()
183195 go m.blockProposeMsgBroadcastLoop()
184196 go m.blockSignatureMsgBroadcastLoop()
185197 return nil
--- /dev/null
+++ b/netsync/consensusmgr/handle_test.go
@@ -0,0 +1,231 @@
1+package consensusmgr
2+
3+import (
4+ "math/rand"
5+ "net"
6+ "reflect"
7+ "testing"
8+ "time"
9+
10+ "github.com/tendermint/tmlibs/flowrate"
11+
12+ "github.com/vapor/consensus"
13+ "github.com/vapor/event"
14+ "github.com/vapor/netsync/peers"
15+ "github.com/vapor/p2p"
16+ "github.com/vapor/protocol/bc"
17+ "github.com/vapor/protocol/bc/types"
18+)
19+
20+type p2peer struct {
21+}
22+
23+func (p *p2peer) Addr() net.Addr {
24+ return nil
25+}
26+
27+func (p *p2peer) ID() string {
28+ return ""
29+}
30+
31+func (p *p2peer) RemoteAddrHost() string {
32+ return ""
33+}
34+func (p *p2peer) ServiceFlag() consensus.ServiceFlag {
35+ return 0
36+}
37+func (p *p2peer) TrafficStatus() (*flowrate.Status, *flowrate.Status) {
38+ return nil, nil
39+}
40+func (p *p2peer) TrySend(byte, interface{}) bool {
41+ return true
42+}
43+func (p *p2peer) IsLAN() bool {
44+ return false
45+}
46+
47+func mockBlocks(startBlock *types.Block, height uint64) []*types.Block {
48+ blocks := []*types.Block{}
49+ indexBlock := &types.Block{}
50+ if startBlock == nil {
51+ indexBlock = &types.Block{BlockHeader: types.BlockHeader{Version: uint64(rand.Uint32())}}
52+ blocks = append(blocks, indexBlock)
53+ } else {
54+ indexBlock = startBlock
55+ }
56+
57+ for indexBlock.Height < height {
58+ block := &types.Block{
59+ BlockHeader: types.BlockHeader{
60+ Height: indexBlock.Height + 1,
61+ PreviousBlockHash: indexBlock.Hash(),
62+ Version: uint64(rand.Uint32()),
63+ },
64+ }
65+ blocks = append(blocks, block)
66+ indexBlock = block
67+ }
68+ return blocks
69+}
70+
71+type mockSW struct {
72+}
73+
74+func (s *mockSW) AddReactor(name string, reactor p2p.Reactor) p2p.Reactor {
75+ return nil
76+}
77+
78+type mockChain struct {
79+}
80+
81+func (c *mockChain) BestBlockHeight() uint64 {
82+ return 0
83+}
84+
85+func (c *mockChain) GetHeaderByHash(*bc.Hash) (*types.BlockHeader, error) {
86+ return nil, nil
87+}
88+
89+func (c *mockChain) ProcessBlock(*types.Block) (bool, error) {
90+ return false, nil
91+}
92+
93+func (c *mockChain) ProcessBlockSignature(signature, pubkey []byte, blockHash *bc.Hash) error {
94+ return nil
95+}
96+
97+type mockPeers struct {
98+ msgCount *int
99+ knownBlock *bc.Hash
100+ blockHeight *uint64
101+ knownSignature *[]byte
102+}
103+
104+func newMockPeers(msgCount *int, knownBlock *bc.Hash, blockHeight *uint64, signature *[]byte) *mockPeers {
105+ return &mockPeers{
106+ msgCount: msgCount,
107+ knownBlock: knownBlock,
108+ blockHeight: blockHeight,
109+ knownSignature: signature,
110+ }
111+}
112+
113+func (ps *mockPeers) AddPeer(peer peers.BasePeer) {
114+
115+}
116+
117+func (ps *mockPeers) BroadcastMsg(bm peers.BroadcastMsg) error {
118+ *ps.msgCount++
119+ return nil
120+}
121+func (ps *mockPeers) GetPeer(id string) *peers.Peer {
122+ return &peers.Peer{BasePeer: &p2peer{}}
123+}
124+func (ps *mockPeers) MarkBlock(peerID string, hash *bc.Hash) {
125+ *ps.knownBlock = *hash
126+}
127+
128+func (ps *mockPeers) MarkBlockSignature(peerID string, signature []byte) {
129+ *ps.knownSignature = append(*ps.knownSignature, signature...)
130+}
131+
132+func (ps *mockPeers) ProcessIllegal(peerID string, level byte, reason string) {
133+
134+}
135+func (p *mockPeers) RemovePeer(peerID string) {
136+
137+}
138+func (ps *mockPeers) SetStatus(peerID string, height uint64, hash *bc.Hash) {
139+ *ps.blockHeight = height
140+}
141+
142+func TestBlockProposeMsgBroadcastLoop(t *testing.T) {
143+ dispatcher := event.NewDispatcher()
144+ msgCount := 0
145+ blockHeight := 100
146+ mgr := NewManager(&mockSW{}, &mockChain{}, newMockPeers(&msgCount, nil, nil, nil), dispatcher)
147+ blocks := mockBlocks(nil, uint64(blockHeight))
148+
149+ mgr.Start()
150+ defer mgr.Stop()
151+ time.Sleep(10 * time.Millisecond)
152+ for _, block := range blocks {
153+ mgr.eventDispatcher.Post(event.NewProposedBlockEvent{Block: *block})
154+ }
155+ time.Sleep(10 * time.Millisecond)
156+ if msgCount != blockHeight+1 {
157+ t.Fatalf("broad propose block msg err. got:%d\n want:%d", msgCount, blockHeight+1)
158+ }
159+}
160+
161+func TestBlockSignatureMsgBroadcastLoop(t *testing.T) {
162+ dispatcher := event.NewDispatcher()
163+ msgCount := 0
164+ blockHeight := 100
165+ mgr := NewManager(&mockSW{}, &mockChain{}, newMockPeers(&msgCount, nil, nil, nil), dispatcher)
166+ blocks := mockBlocks(nil, uint64(blockHeight))
167+
168+ mgr.Start()
169+ defer mgr.Stop()
170+ time.Sleep(10 * time.Millisecond)
171+ for _, block := range blocks {
172+ mgr.eventDispatcher.Post(event.BlockSignatureEvent{BlockHash: block.Hash(), Signature: []byte{0x1, 0x2}, XPub: []byte{0x011, 0x022}})
173+ }
174+ time.Sleep(10 * time.Millisecond)
175+ if msgCount != blockHeight+1 {
176+ t.Fatalf("broad propose block msg err. got:%d\n want:%d", msgCount, blockHeight+1)
177+ }
178+}
179+
180+func TestProcessBlockProposeMsg(t *testing.T) {
181+ dispatcher := event.NewDispatcher()
182+ msgCount := 0
183+ var knownBlock bc.Hash
184+ blockHeight := uint64(0)
185+ peerID := "Peer1"
186+ mgr := NewManager(&mockSW{}, &mockChain{}, newMockPeers(&msgCount, &knownBlock, &blockHeight, nil), dispatcher)
187+ block := &types.Block{
188+ BlockHeader: types.BlockHeader{
189+ Height: 100,
190+ PreviousBlockHash: bc.NewHash([32]byte{0x1}),
191+ Version: uint64(rand.Uint32()),
192+ },
193+ }
194+ msg, err := NewBlockProposeMsg(block)
195+ if err != nil {
196+ t.Fatal("create new block propose msg err", err)
197+ }
198+
199+ mgr.processMsg(peerID, 0, msg)
200+ if knownBlock != block.Hash() {
201+ t.Fatalf("mark propose block msg err. got:%d\n want:%d", knownBlock, block.Hash())
202+ }
203+
204+ if blockHeight != block.Height {
205+ t.Fatalf("set peer status err. got:%d\n want:%d", blockHeight, block.Height)
206+ }
207+}
208+
209+func TestProcessBlockSignatureMsg(t *testing.T) {
210+ dispatcher := event.NewDispatcher()
211+ msgCount := 0
212+ knownSignature := []byte{}
213+ peerID := "Peer1"
214+ mgr := NewManager(&mockSW{}, &mockChain{}, newMockPeers(&msgCount, nil, nil, &knownSignature), dispatcher)
215+ block := &types.Block{
216+ BlockHeader: types.BlockHeader{
217+ Height: 100,
218+ PreviousBlockHash: bc.NewHash([32]byte{0x1}),
219+ Version: uint64(rand.Uint32()),
220+ },
221+ }
222+
223+ signature := []byte{0x01, 0x02}
224+ msg := NewBlockSignatureMsg(block.Hash(), signature, []byte{0x03, 0x04})
225+
226+ mgr.processMsg(peerID, 0, msg)
227+
228+ if !reflect.DeepEqual(knownSignature, signature) {
229+ t.Fatalf("set peer status err. got:%d\n want:%d", knownSignature, signature)
230+ }
231+}
--- a/netsync/peers/peer.go
+++ b/netsync/peers/peer.go
@@ -252,32 +252,6 @@ func (p *Peer) markTransaction(hash *bc.Hash) {
252252 p.knownTxs.Add(hash.String())
253253 }
254254
255-func (ps *PeerSet) PeersWithoutBlock(hash bc.Hash) []string {
256- ps.mtx.RLock()
257- defer ps.mtx.RUnlock()
258-
259- var peers []string
260- for _, peer := range ps.peers {
261- if !peer.knownBlocks.Has(hash.String()) {
262- peers = append(peers, peer.ID())
263- }
264- }
265- return peers
266-}
267-
268-func (ps *PeerSet) PeersWithoutSign(signature []byte) []string {
269- ps.mtx.RLock()
270- defer ps.mtx.RUnlock()
271-
272- var peers []string
273- for _, peer := range ps.peers {
274- if !peer.knownSignatures.Has(hex.EncodeToString(signature)) {
275- peers = append(peers, peer.ID())
276- }
277- }
278- return peers
279-}
280-
281255 func (p *Peer) SendBlock(block *types.Block) (bool, error) {
282256 msg, err := msgs.NewBlockMessage(block)
283257 if err != nil {
@@ -544,14 +518,6 @@ func (ps *PeerSet) BroadcastTx(tx *types.Tx) error {
544518 return nil
545519 }
546520
547-func (ps *PeerSet) ErrorHandler(peerID string, level byte, err error) {
548- if errors.Root(err) == ErrPeerMisbehave {
549- ps.ProcessIllegal(peerID, level, err.Error())
550- } else {
551- ps.RemovePeer(peerID)
552- }
553-}
554-
555521 // Peer retrieves the registered peer with the given id.
556522 func (ps *PeerSet) GetPeer(id string) *Peer {
557523 ps.mtx.RLock()
@@ -618,14 +584,27 @@ func (ps *PeerSet) MarkTx(peerID string, txHash bc.Hash) {
618584 peer.markTransaction(&txHash)
619585 }
620586
621-func (ps *PeerSet) peersWithoutBlock(hash *bc.Hash) []*Peer {
587+func (ps *PeerSet) PeersWithoutBlock(hash bc.Hash) []string {
622588 ps.mtx.RLock()
623589 defer ps.mtx.RUnlock()
624590
625- peers := []*Peer{}
591+ var peers []string
626592 for _, peer := range ps.peers {
627593 if !peer.knownBlocks.Has(hash.String()) {
628- peers = append(peers, peer)
594+ peers = append(peers, peer.ID())
595+ }
596+ }
597+ return peers
598+}
599+
600+func (ps *PeerSet) PeersWithoutSignature(signature []byte) []string {
601+ ps.mtx.RLock()
602+ defer ps.mtx.RUnlock()
603+
604+ var peers []string
605+ for _, peer := range ps.peers {
606+ if !peer.knownSignatures.Has(hex.EncodeToString(signature)) {
607+ peers = append(peers, peer.ID())
629608 }
630609 }
631610 return peers
@@ -681,10 +660,3 @@ func (ps *PeerSet) SetIrreversibleStatus(peerID string, height uint64, hash *bc.
681660
682661 peer.SetIrreversibleStatus(height, hash)
683662 }
684-
685-func (ps *PeerSet) Size() int {
686- ps.mtx.RLock()
687- defer ps.mtx.RUnlock()
688-
689- return len(ps.peers)
690-}
--- /dev/null
+++ b/netsync/peers/peer_test.go
@@ -0,0 +1,417 @@
1+package peers
2+
3+import (
4+ "net"
5+ "reflect"
6+ "testing"
7+
8+ "github.com/davecgh/go-spew/spew"
9+ "github.com/tendermint/tmlibs/flowrate"
10+ "github.com/vapor/consensus"
11+ "github.com/vapor/p2p/security"
12+ "github.com/vapor/protocol/bc"
13+ "github.com/vapor/protocol/bc/types"
14+)
15+
16+var (
17+ peer1ID = "PEER1"
18+ peer2ID = "PEER2"
19+ peer3ID = "PEER3"
20+ peer4ID = "PEER4"
21+
22+ block1000Hash = bc.NewHash([32]byte{0x01, 0x02})
23+ block2000Hash = bc.NewHash([32]byte{0x02, 0x03})
24+ block3000Hash = bc.NewHash([32]byte{0x03, 0x04})
25+)
26+
27+type basePeer struct {
28+ id string
29+ serviceFlag consensus.ServiceFlag
30+ isLan bool
31+}
32+
33+func (bp *basePeer) Addr() net.Addr {
34+ return nil
35+}
36+
37+func (bp *basePeer) ID() string {
38+ return bp.id
39+}
40+
41+func (bp *basePeer) RemoteAddrHost() string {
42+ switch bp.ID() {
43+ case peer1ID:
44+ return peer1ID
45+ case peer2ID:
46+ return peer2ID
47+ case peer3ID:
48+ return peer3ID
49+ case peer4ID:
50+ return peer4ID
51+ }
52+ return ""
53+}
54+
55+func (bp *basePeer) ServiceFlag() consensus.ServiceFlag {
56+ return bp.serviceFlag
57+}
58+
59+func (bp *basePeer) TrafficStatus() (*flowrate.Status, *flowrate.Status) {
60+ return nil, nil
61+}
62+
63+func (bp *basePeer) TrySend(byte, interface{}) bool {
64+ return true
65+}
66+
67+func (bp *basePeer) IsLAN() bool {
68+ return bp.isLan
69+}
70+
71+func TestSetPeerStatus(t *testing.T) {
72+ peer := newPeer(&basePeer{})
73+ height := uint64(100)
74+ hash := bc.NewHash([32]byte{0x1, 0x2})
75+ peer.SetBestStatus(height, &hash)
76+ if peer.Height() != height {
77+ t.Fatalf("test set best status err. got %d want %d", peer.Height(), height)
78+ }
79+}
80+
81+func TestSetIrreversibleStatus(t *testing.T) {
82+ peer := newPeer(&basePeer{})
83+ height := uint64(100)
84+ hash := bc.NewHash([32]byte{0x1, 0x2})
85+ peer.SetIrreversibleStatus(height, &hash)
86+ if peer.IrreversibleHeight() != height {
87+ t.Fatalf("test set Irreversible status err. got %d want %d", peer.Height(), height)
88+ }
89+}
90+
91+func TestAddFilterAddresses(t *testing.T) {
92+ peer := newPeer(&basePeer{})
93+ tx := types.NewTx(types.TxData{
94+ Inputs: []*types.TxInput{
95+ types.NewSpendInput(nil, bc.Hash{}, bc.NewAssetID([32]byte{1}), 5, 1, []byte("spendProgram")),
96+ },
97+ Outputs: []*types.TxOutput{
98+ types.NewIntraChainOutput(bc.NewAssetID([32]byte{3}), 8, []byte("outProgram")),
99+ },
100+ })
101+
102+ peer.AddFilterAddresses([][]byte{[]byte("spendProgram")})
103+ if !peer.isRelatedTx(tx) {
104+ t.Fatal("test filter addresses error.")
105+ }
106+
107+ peer.AddFilterAddresses([][]byte{[]byte("testProgram")})
108+ if peer.isRelatedTx(tx) {
109+ t.Fatal("test filter addresses error.")
110+ }
111+}
112+
113+func TestFilterClear(t *testing.T) {
114+ peer := newPeer(&basePeer{})
115+ tx := types.NewTx(types.TxData{
116+ Inputs: []*types.TxInput{
117+ types.NewSpendInput(nil, bc.Hash{}, bc.NewAssetID([32]byte{1}), 5, 1, []byte("spendProgram")),
118+ },
119+ Outputs: []*types.TxOutput{
120+ types.NewIntraChainOutput(bc.NewAssetID([32]byte{3}), 8, []byte("outProgram")),
121+ },
122+ })
123+
124+ peer.AddFilterAddresses([][]byte{[]byte("spendProgram")})
125+ if !peer.isRelatedTx(tx) {
126+ t.Fatal("test filter addresses error.")
127+ }
128+
129+ peer.FilterClear()
130+ if peer.isRelatedTx(tx) {
131+ t.Fatal("test filter addresses error.")
132+ }
133+}
134+
135+func TestGetRelatedTxAndStatus(t *testing.T) {
136+ peer := newPeer(&basePeer{})
137+ txs := []*types.Tx{
138+ types.NewTx(types.TxData{
139+ Inputs: []*types.TxInput{
140+ types.NewSpendInput(nil, bc.Hash{}, bc.NewAssetID([32]byte{1}), 5, 1, []byte("spendProgram1")),
141+ },
142+ Outputs: []*types.TxOutput{
143+ types.NewIntraChainOutput(bc.NewAssetID([32]byte{3}), 8, []byte("outProgram1")),
144+ },
145+ }),
146+ types.NewTx(types.TxData{
147+ Inputs: []*types.TxInput{
148+ types.NewSpendInput(nil, bc.Hash{}, bc.NewAssetID([32]byte{1}), 5, 1, []byte("spendProgram2")),
149+ },
150+ Outputs: []*types.TxOutput{
151+ types.NewIntraChainOutput(bc.NewAssetID([32]byte{3}), 8, []byte("outProgram2")),
152+ },
153+ }),
154+ types.NewTx(types.TxData{
155+ Inputs: []*types.TxInput{
156+ types.NewSpendInput(nil, bc.Hash{}, bc.NewAssetID([32]byte{1}), 5, 1, []byte("spendProgram3")),
157+ },
158+ Outputs: []*types.TxOutput{
159+ types.NewIntraChainOutput(bc.NewAssetID([32]byte{3}), 8, []byte("outProgram3")),
160+ },
161+ }),
162+ }
163+ txStatuses := &bc.TransactionStatus{
164+ VerifyStatus: []*bc.TxVerifyResult{{StatusFail: true}, {StatusFail: false}, {StatusFail: false}},
165+ }
166+ peer.AddFilterAddresses([][]byte{[]byte("spendProgram1"), []byte("outProgram3")})
167+ gotTxs, gotStatus := peer.getRelatedTxAndStatus(txs, txStatuses)
168+ if len(gotTxs) != 2 {
169+ t.Error("TestGetRelatedTxAndStatus txs size error")
170+ }
171+
172+ if !reflect.DeepEqual(*gotTxs[0].Tx, *txs[0].Tx) {
173+ t.Errorf("txs msg test err: got %s\nwant %s", spew.Sdump(gotTxs[0].Tx), spew.Sdump(txs[0].Tx))
174+ }
175+
176+ if !reflect.DeepEqual(*gotTxs[1].Tx, *txs[2].Tx) {
177+ t.Errorf("txs msg test err: got %s\nwant %s", spew.Sdump(gotTxs[1].Tx), spew.Sdump(txs[2].Tx))
178+ }
179+
180+ if gotStatus[0].StatusFail != true || gotStatus[1].StatusFail != false {
181+ t.Error("TestGetRelatedTxAndStatus txs status error")
182+ }
183+}
184+
185+type basePeerSet struct {
186+}
187+
188+func (bp *basePeerSet) StopPeerGracefully(string) {
189+
190+}
191+
192+func (bp *basePeerSet) IsBanned(ip string, level byte, reason string) bool {
193+ switch ip {
194+ case peer1ID:
195+ return true
196+ case peer2ID:
197+ return false
198+ case peer3ID:
199+ return true
200+ case peer4ID:
201+ return false
202+ }
203+ return false
204+}
205+
206+func TestMarkBlock(t *testing.T) {
207+ ps := NewPeerSet(&basePeerSet{})
208+ ps.AddPeer(&basePeer{id: peer1ID})
209+ ps.AddPeer(&basePeer{id: peer2ID})
210+ ps.AddPeer(&basePeer{id: peer3ID})
211+
212+ blockHash := bc.NewHash([32]byte{0x01, 0x02})
213+ ps.MarkBlock(peer1ID, &blockHash)
214+ targetPeers := []string{peer2ID, peer3ID}
215+
216+ peers := ps.PeersWithoutBlock(blockHash)
217+ if len(peers) != len(targetPeers) {
218+ t.Fatalf("test mark block err. Number of target peers %d got %d", 1, len(peers))
219+ }
220+
221+ for _, targetPeer := range targetPeers {
222+ flag := false
223+ for _, gotPeer := range peers {
224+ if gotPeer == targetPeer {
225+ flag = true
226+ break
227+ }
228+ }
229+ if !flag {
230+ t.Errorf("test mark block err. can't found target peer %s ", targetPeer)
231+ }
232+ }
233+}
234+
235+func TestMarkStatus(t *testing.T) {
236+ ps := NewPeerSet(&basePeerSet{})
237+ ps.AddPeer(&basePeer{id: peer1ID})
238+ ps.AddPeer(&basePeer{id: peer2ID})
239+ ps.AddPeer(&basePeer{id: peer3ID})
240+
241+ height := uint64(1024)
242+ ps.MarkStatus(peer1ID, height)
243+ targetPeers := []string{peer2ID, peer3ID}
244+
245+ peers := ps.peersWithoutNewStatus(height)
246+ if len(peers) != len(targetPeers) {
247+ t.Fatalf("test mark status err. Number of target peers %d got %d", 1, len(peers))
248+ }
249+
250+ for _, targetPeer := range targetPeers {
251+ flag := false
252+ for _, gotPeer := range peers {
253+ if gotPeer.ID() == targetPeer {
254+ flag = true
255+ break
256+ }
257+ }
258+ if !flag {
259+ t.Errorf("test mark status err. can't found target peer %s ", targetPeer)
260+ }
261+ }
262+}
263+
264+func TestMarkBlockSignature(t *testing.T) {
265+ ps := NewPeerSet(&basePeerSet{})
266+ ps.AddPeer(&basePeer{id: peer1ID})
267+ ps.AddPeer(&basePeer{id: peer2ID})
268+ ps.AddPeer(&basePeer{id: peer3ID})
269+
270+ signature := []byte{0x01, 0x02}
271+ ps.MarkBlockSignature(peer1ID, signature)
272+ targetPeers := []string{peer2ID, peer3ID}
273+
274+ peers := ps.PeersWithoutSignature(signature)
275+ if len(peers) != len(targetPeers) {
276+ t.Fatalf("test mark block signature err. Number of target peers %d got %d", 1, len(peers))
277+ }
278+
279+ for _, targetPeer := range targetPeers {
280+ flag := false
281+ for _, gotPeer := range peers {
282+ if gotPeer == targetPeer {
283+ flag = true
284+ break
285+ }
286+ }
287+ if !flag {
288+ t.Errorf("test mark block signature err. can't found target peer %s ", targetPeer)
289+ }
290+ }
291+}
292+
293+func TestMarkTx(t *testing.T) {
294+ ps := NewPeerSet(&basePeerSet{})
295+ ps.AddPeer(&basePeer{id: peer1ID})
296+ ps.AddPeer(&basePeer{id: peer2ID})
297+ ps.AddPeer(&basePeer{id: peer3ID})
298+
299+ txHash := bc.NewHash([32]byte{0x01, 0x02})
300+ ps.MarkTx(peer1ID, txHash)
301+ peers := ps.peersWithoutTx(&txHash)
302+ targetPeers := []string{peer2ID, peer3ID}
303+ if len(peers) != len(targetPeers) {
304+ t.Fatalf("test mark tx err. Number of target peers %d got %d", 1, len(peers))
305+ }
306+
307+ for _, targetPeer := range targetPeers {
308+ flag := false
309+ for _, gotPeer := range peers {
310+ if gotPeer.ID() == targetPeer {
311+ flag = true
312+ break
313+ }
314+ }
315+ if !flag {
316+ t.Errorf("test mark tx err. can't found target peer %s ", targetPeer)
317+ }
318+ }
319+}
320+
321+func TestSetStatus(t *testing.T) {
322+ ps := NewPeerSet(&basePeerSet{})
323+ ps.AddPeer(&basePeer{id: peer1ID, serviceFlag: consensus.SFFullNode})
324+ ps.AddPeer(&basePeer{id: peer2ID, serviceFlag: consensus.SFFullNode})
325+ ps.AddPeer(&basePeer{id: peer3ID, serviceFlag: consensus.SFFastSync})
326+ ps.AddPeer(&basePeer{id: peer4ID, serviceFlag: consensus.SFFullNode, isLan: true})
327+ ps.SetStatus(peer1ID, 1000, &block1000Hash)
328+ ps.SetStatus(peer2ID, 2000, &block2000Hash)
329+ ps.SetStatus(peer3ID, 3000, &block3000Hash)
330+ ps.SetStatus(peer4ID, 2000, &block2000Hash)
331+ targetPeer := peer4ID
332+
333+ peer := ps.BestPeer(consensus.SFFullNode)
334+
335+ if peer.ID() != targetPeer {
336+ t.Fatalf("test set status err. Name of target peer %s got %s", peer4ID, peer.ID())
337+ }
338+}
339+
340+func TestIrreversibleStatus(t *testing.T) {
341+ ps := NewPeerSet(&basePeerSet{})
342+ ps.AddPeer(&basePeer{id: peer1ID, serviceFlag: consensus.SFFullNode})
343+ ps.AddPeer(&basePeer{id: peer2ID, serviceFlag: consensus.SFFullNode})
344+ ps.AddPeer(&basePeer{id: peer3ID, serviceFlag: consensus.SFFastSync})
345+ ps.AddPeer(&basePeer{id: peer4ID, serviceFlag: consensus.SFFastSync, isLan: true})
346+ ps.SetIrreversibleStatus(peer1ID, 1000, &block1000Hash)
347+ ps.SetIrreversibleStatus(peer2ID, 2000, &block2000Hash)
348+ ps.SetIrreversibleStatus(peer3ID, 3000, &block3000Hash)
349+ ps.SetIrreversibleStatus(peer4ID, 3000, &block3000Hash)
350+ targetPeer := peer4ID
351+ peer := ps.BestIrreversiblePeer(consensus.SFFastSync)
352+
353+ if peer.ID() != targetPeer {
354+ t.Fatalf("test set status err. Name of target peer %s got %s", peer4ID, peer.ID())
355+ }
356+}
357+
358+func TestGetPeersByHeight(t *testing.T) {
359+ ps := NewPeerSet(&basePeerSet{})
360+ ps.AddPeer(&basePeer{id: peer1ID, serviceFlag: consensus.SFFullNode})
361+ ps.AddPeer(&basePeer{id: peer2ID, serviceFlag: consensus.SFFullNode})
362+ ps.AddPeer(&basePeer{id: peer3ID, serviceFlag: consensus.SFFastSync})
363+ ps.AddPeer(&basePeer{id: peer4ID, serviceFlag: consensus.SFFullNode, isLan: true})
364+ ps.SetStatus(peer1ID, 1000, &block1000Hash)
365+ ps.SetStatus(peer2ID, 2000, &block2000Hash)
366+ ps.SetStatus(peer3ID, 3000, &block3000Hash)
367+ ps.SetStatus(peer4ID, 2000, &block2000Hash)
368+ peers := ps.GetPeersByHeight(2000)
369+ targetPeers := []string{peer2ID, peer3ID, peer4ID}
370+ if len(peers) != len(targetPeers) {
371+ t.Fatalf("test get peers by height err. Number of target peers %d got %d", 3, len(peers))
372+ }
373+
374+ for _, targetPeer := range targetPeers {
375+ flag := false
376+ for _, gotPeer := range peers {
377+ if gotPeer.ID() == targetPeer {
378+ flag = true
379+ break
380+ }
381+ }
382+ if !flag {
383+ t.Errorf("test get peers by height err. can't found target peer %s ", targetPeer)
384+ }
385+ }
386+}
387+
388+func TestRemovePeer(t *testing.T) {
389+ ps := NewPeerSet(&basePeerSet{})
390+ ps.AddPeer(&basePeer{id: peer1ID})
391+ ps.AddPeer(&basePeer{id: peer2ID})
392+
393+ ps.RemovePeer(peer1ID)
394+ if peer := ps.GetPeer(peer1ID); peer != nil {
395+ t.Fatalf("remove peer %s err", peer1ID)
396+ }
397+
398+ if peer := ps.GetPeer(peer2ID); peer == nil {
399+ t.Fatalf("Error remove peer %s err", peer2ID)
400+ }
401+}
402+
403+func TestProcessIllegal(t *testing.T) {
404+ ps := NewPeerSet(&basePeerSet{})
405+ ps.AddPeer(&basePeer{id: peer1ID})
406+ ps.AddPeer(&basePeer{id: peer2ID})
407+
408+ ps.ProcessIllegal(peer1ID, security.LevelMsgIllegal, "test")
409+ if peer := ps.GetPeer(peer1ID); peer != nil {
410+ t.Fatalf("remove peer %s err", peer1ID)
411+ }
412+
413+ ps.ProcessIllegal(peer2ID, security.LevelMsgIllegal, "test")
414+ if peer := ps.GetPeer(peer2ID); peer == nil {
415+ t.Fatalf("Error remove peer %s err", peer2ID)
416+ }
417+}
--- a/netsync/sync_manager.go
+++ b/netsync/sync_manager.go
@@ -67,7 +67,7 @@ func NewSyncManager(config *config.Config, chain *protocol.Chain, txPool *protoc
6767 if err != nil {
6868 return nil, err
6969 }
70- consensusMgr := consensusmgr.NewManager(sw, chain, dispatcher, peers)
70+ consensusMgr := consensusmgr.NewManager(sw, chain, peers, dispatcher)
7171 return &SyncManager{
7272 config: config,
7373 sw: sw,
--- a/p2p/security/security.go
+++ b/p2p/security/security.go
@@ -6,7 +6,7 @@ import (
66 cfg "github.com/vapor/config"
77 )
88
9-const logModule = "p2p/security"
9+const logModule = "p2pSecurity"
1010
1111 type Security struct {
1212 filter *PeerFilter
--- a/protocol/state/consensus_result_test.go
+++ b/protocol/state/consensus_result_test.go
@@ -2,9 +2,14 @@ package state
22
33 import (
44 "encoding/hex"
5+ "math"
6+ "reflect"
57 "testing"
68
9+ "github.com/davecgh/go-spew/spew"
10+
711 "github.com/vapor/consensus"
12+ "github.com/vapor/crypto/ed25519/chainkd"
813 "github.com/vapor/errors"
914 "github.com/vapor/math/checked"
1015 "github.com/vapor/protocol/bc"
@@ -12,6 +17,178 @@ import (
1217 "github.com/vapor/testutil"
1318 )
1419
20+func TestApplyTransaction(t *testing.T) {
21+ testXpub, _ := hex.DecodeString("a8018a1ba4d85fc7118bbd065612da78b2c503e61a1a093d9c659567c5d3a591b3752569fbcafa951b2304b8f576f3f220e03b957ca819840e7c29e4b7fb2c4d")
22+
23+ cases := []struct {
24+ desc string
25+ tx *types.Tx
26+ prevConsensusResult *ConsensusResult
27+ postConsensusResult *ConsensusResult
28+ wantErr error
29+ }{
30+ {
31+ desc: "test num Of vote overflow",
32+ tx: &types.Tx{
33+ TxData: types.TxData{
34+ Inputs: []*types.TxInput{types.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), *consensus.BTMAssetID, 600000000, 0, nil)},
35+ Outputs: []*types.TxOutput{types.NewVoteOutput(*consensus.BTMAssetID, math.MaxUint64-1000, []byte{0x51}, testXpub)},
36+ },
37+ },
38+ prevConsensusResult: &ConsensusResult{
39+ NumOfVote: map[string]uint64{
40+ hex.EncodeToString(testXpub): 1000000,
41+ },
42+ },
43+ postConsensusResult: &ConsensusResult{
44+ NumOfVote: map[string]uint64{},
45+ },
46+ wantErr: checked.ErrOverflow,
47+ },
48+ {
49+ desc: "test num Of veto overflow",
50+ tx: &types.Tx{
51+ TxData: types.TxData{
52+ Inputs: []*types.TxInput{types.NewVetoInput(nil, bc.NewHash([32]byte{0xff}), *consensus.BTMAssetID, 100000000, 0, []byte{0x51}, testXpub)},
53+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, 100000000, []byte{0x51})},
54+ },
55+ },
56+ prevConsensusResult: &ConsensusResult{
57+ NumOfVote: map[string]uint64{
58+ hex.EncodeToString(testXpub): 1000000,
59+ },
60+ },
61+ postConsensusResult: &ConsensusResult{
62+ NumOfVote: map[string]uint64{},
63+ },
64+ wantErr: checked.ErrOverflow,
65+ },
66+ {
67+ desc: "test del pubkey from NumOfVote",
68+ tx: &types.Tx{
69+ TxData: types.TxData{
70+ Inputs: []*types.TxInput{types.NewVetoInput(nil, bc.NewHash([32]byte{0xff}), *consensus.BTMAssetID, 1000000, 0, []byte{0x51}, testXpub)},
71+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, 100000000, []byte{0x51})},
72+ },
73+ },
74+ prevConsensusResult: &ConsensusResult{
75+ NumOfVote: map[string]uint64{
76+ hex.EncodeToString(testXpub): 1000000,
77+ },
78+ },
79+ postConsensusResult: &ConsensusResult{
80+ NumOfVote: map[string]uint64{},
81+ },
82+ wantErr: nil,
83+ },
84+ }
85+
86+ for i, c := range cases {
87+ if err := c.prevConsensusResult.ApplyTransaction(c.tx); err != nil {
88+ if err != c.wantErr {
89+ t.Errorf("test case #%d want err = %v, got err = %v", i, c.wantErr, err)
90+ }
91+ continue
92+ }
93+
94+ if !testutil.DeepEqual(c.prevConsensusResult, c.postConsensusResult) {
95+ t.Errorf("test case #%d, want %v, got %v", i, c.postConsensusResult, c.prevConsensusResult)
96+ }
97+ }
98+}
99+
100+func TestAttachCoinbaseReward(t *testing.T) {
101+ cases := []struct {
102+ desc string
103+ block *types.Block
104+ prevConsensusResult *ConsensusResult
105+ postConsensusResult *ConsensusResult
106+ wantErr error
107+ }{
108+ {
109+ desc: "normal test with block contain coinbase tx and other tx",
110+ block: &types.Block{
111+ BlockHeader: types.BlockHeader{
112+ Height: 1,
113+ },
114+ Transactions: []*types.Tx{
115+ {
116+ TxData: types.TxData{
117+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
118+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
119+ },
120+ },
121+ {
122+ TxData: types.TxData{
123+ Inputs: []*types.TxInput{types.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), *consensus.BTMAssetID, 300000000, 0, nil)},
124+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, 250000000, []byte{0x51})},
125+ },
126+ },
127+ },
128+ },
129+ prevConsensusResult: &ConsensusResult{
130+ CoinbaseReward: map[string]uint64{
131+ hex.EncodeToString([]byte{0x51}): 50000000,
132+ hex.EncodeToString([]byte{0x52}): 80000000,
133+ },
134+ },
135+ postConsensusResult: &ConsensusResult{
136+ CoinbaseReward: map[string]uint64{
137+ hex.EncodeToString([]byte{0x51}): consensus.BlockSubsidy(1) + 50000000,
138+ },
139+ },
140+ wantErr: nil,
141+ },
142+ {
143+ desc: "test coinbase reward overflow",
144+ block: &types.Block{
145+ BlockHeader: types.BlockHeader{
146+ Height: 100,
147+ },
148+ Transactions: []*types.Tx{
149+ {
150+ TxData: types.TxData{
151+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
152+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
153+ },
154+ },
155+ {
156+ TxData: types.TxData{
157+ Inputs: []*types.TxInput{types.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), *consensus.BTMAssetID, math.MaxUint64-80000000, 0, nil)},
158+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, 0, []byte{0x52})},
159+ },
160+ },
161+ },
162+ },
163+ prevConsensusResult: &ConsensusResult{
164+ CoinbaseReward: map[string]uint64{
165+ hex.EncodeToString([]byte{0x51}): 80000000,
166+ hex.EncodeToString([]byte{0x52}): 50000000,
167+ },
168+ },
169+ postConsensusResult: &ConsensusResult{
170+ CoinbaseReward: map[string]uint64{
171+ hex.EncodeToString([]byte{0x51}): consensus.BlockSubsidy(1) + 50000000,
172+ },
173+ },
174+ wantErr: checked.ErrOverflow,
175+ },
176+ }
177+
178+ for i, c := range cases {
179+ if err := c.prevConsensusResult.AttachCoinbaseReward(c.block); err != nil {
180+ if err != c.wantErr {
181+ t.Errorf("test case #%d want err = %v, got err = %v", i, c.wantErr, err)
182+ }
183+ continue
184+ }
185+
186+ if !testutil.DeepEqual(c.prevConsensusResult, c.postConsensusResult) {
187+ t.Errorf("test case #%d, want %v, got %v", i, c.postConsensusResult, c.prevConsensusResult)
188+ }
189+ }
190+}
191+
15192 func TestCalCoinbaseReward(t *testing.T) {
16193 cases := []struct {
17194 desc string
@@ -658,8 +835,100 @@ func TestConsensusDetachBlock(t *testing.T) {
658835 },
659836 wantErr: errors.New("not found coinbase receiver"),
660837 },
838+ {
839+ desc: "test number of vote overflow",
840+ block: &types.Block{
841+ BlockHeader: types.BlockHeader{
842+ Height: consensus.MainNetParams.RoundVoteBlockNums - 1,
843+ PreviousBlockHash: bc.Hash{V0: 1},
844+ },
845+ Transactions: []*types.Tx{
846+ &types.Tx{
847+ TxData: types.TxData{
848+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
849+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
850+ },
851+ },
852+ &types.Tx{
853+ TxData: types.TxData{
854+ Inputs: []*types.TxInput{types.NewSpendInput(nil, bc.NewHash([32]byte{0xff}), *consensus.BTMAssetID, 600000000, 0, nil)},
855+ Outputs: []*types.TxOutput{types.NewVoteOutput(*consensus.BTMAssetID, 600000000, []byte{0x51}, testXpub)},
856+ },
857+ },
858+ },
859+ },
860+ consensusResult: &ConsensusResult{
861+ BlockHash: testutil.MustDecodeHash("4ebd9e7c00d3e0370931689c6eb9e2131c6700fe66e6b9718028dd75d7a4e329"),
862+ CoinbaseReward: map[string]uint64{
863+ "51": 100000000,
864+ },
865+ NumOfVote: map[string]uint64{},
866+ },
867+ wantErr: checked.ErrOverflow,
868+ },
869+ {
870+ desc: "test number of veto overflow",
871+ block: &types.Block{
872+ BlockHeader: types.BlockHeader{
873+ Height: consensus.MainNetParams.RoundVoteBlockNums - 1,
874+ PreviousBlockHash: bc.Hash{V0: 1},
875+ },
876+ Transactions: []*types.Tx{
877+ &types.Tx{
878+ TxData: types.TxData{
879+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
880+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
881+ },
882+ },
883+ &types.Tx{
884+ TxData: types.TxData{
885+ Inputs: []*types.TxInput{types.NewVetoInput(nil, bc.NewHash([32]byte{0xff}), *consensus.BTMAssetID, math.MaxUint64, 0, []byte{0x51}, testXpub)},
886+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, math.MaxUint64, []byte{0x51})},
887+ },
888+ },
889+ },
890+ },
891+ consensusResult: &ConsensusResult{
892+ BlockHash: testutil.MustDecodeHash("4ebd9e7c00d3e0370931689c6eb9e2131c6700fe66e6b9718028dd75d7a4e329"),
893+ CoinbaseReward: map[string]uint64{
894+ "51": 100000000,
895+ },
896+ NumOfVote: map[string]uint64{
897+ "a8018a1ba4d85fc7118bbd065612da78b2c503e61a1a093d9c659567c5d3a591b3752569fbcafa951b2304b8f576f3f220e03b957ca819840e7c29e4b7fb2c4d": 100,
898+ },
899+ },
900+ wantErr: checked.ErrOverflow,
901+ },
902+ {
903+ desc: "test detch coinbase overflow",
904+ block: &types.Block{
905+ BlockHeader: types.BlockHeader{
906+ Height: consensus.MainNetParams.RoundVoteBlockNums - 1,
907+ PreviousBlockHash: bc.Hash{V0: 1},
908+ },
909+ Transactions: []*types.Tx{
910+ &types.Tx{
911+ TxData: types.TxData{
912+ Inputs: []*types.TxInput{types.NewCoinbaseInput([]byte{0x01})},
913+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(bc.AssetID{}, 0, []byte{0x51})},
914+ },
915+ },
916+ &types.Tx{
917+ TxData: types.TxData{
918+ Inputs: []*types.TxInput{types.NewVetoInput(nil, bc.NewHash([32]byte{0xff}), *consensus.BTMAssetID, math.MaxUint64, 0, []byte{0x51}, testXpub)},
919+ Outputs: []*types.TxOutput{types.NewIntraChainOutput(*consensus.BTMAssetID, math.MaxUint64, []byte{0x51})},
920+ },
921+ },
922+ },
923+ },
924+ consensusResult: &ConsensusResult{
925+ BlockHash: testutil.MustDecodeHash("4ebd9e7c00d3e0370931689c6eb9e2131c6700fe66e6b9718028dd75d7a4e329"),
926+ CoinbaseReward: map[string]uint64{},
927+ NumOfVote: map[string]uint64{},
928+ },
929+ wantErr: checked.ErrOverflow,
930+ },
661931 }
662-
663932 for i, c := range cases {
664933 if err := c.consensusResult.DetachBlock(c.block); err != nil {
665934 if err.Error() != c.wantErr.Error() {
@@ -772,3 +1041,86 @@ func TestGetCoinbaseRewards(t *testing.T) {
7721041 }
7731042 }
7741043 }
1044+
1045+func TestConsensusNodes(t *testing.T) {
1046+ var xpub1, xpub2, xpub3, xpub4, xpub5, xpub6, xpub7 chainkd.XPub
1047+ strPub1 := "0f8669abbd3cc0a167156188e428f940088d5b2f36bb3449df71d2bdc5e077814ea3f68628eef279ed435f51ee26cff00f8bd28fabfd500bedb2a9e369f5c825"
1048+ strPub2 := "e7f458ee8d2ba19b0fdc7410d1fd57e9c2e1a79377c661d66c55effe49d7ffc920e40510442d4a10b7bea06c09fb0b41f52601135adaaa7136204db36106c093"
1049+ strPub3 := "1bec3a35da038ec7a76c40986e80b5af2dcef60341970e3fc58b4db0797bd4ca9b2cbf3d7ab820832e22a80b5b86ae1427f7f706a7780089958b2862e7bc0842"
1050+ strPub4 := "b7f463446a31b3792cd168d52b7a89b3657bca3e25d6854db1488c389ab6fc8d538155c25c1ee6975cc7def19710908c7d9b7463ca34a22058b456b45e498db9"
1051+ strPub5 := "b928e46bb01e834fdf167185e31b15de7cc257af8bbdf17f9c7fefd5bb97b306d048b6bc0da2097152c1c2ff38333c756a543adbba7030a447dcc776b8ac64ef"
1052+ strPub6 := "36695997983028c279c3360ca345a90e3af1f9e3df2506119fca31cdc844be31630f9a421f4d1658e15d67a15ce29c36332dd45020d2a0147fcce4949ccd9a67"
1053+ strPub7 := "123"
1054+
1055+ xpub1.UnmarshalText([]byte(strPub1))
1056+ xpub2.UnmarshalText([]byte(strPub2))
1057+ xpub3.UnmarshalText([]byte(strPub3))
1058+ xpub4.UnmarshalText([]byte(strPub4))
1059+ xpub5.UnmarshalText([]byte(strPub5))
1060+ xpub6.UnmarshalText([]byte(strPub6))
1061+ xpub7.UnmarshalText([]byte(strPub7))
1062+
1063+ cases := []struct {
1064+ consensusResult *ConsensusResult
1065+ consensusNode map[string]*ConsensusNode
1066+ wantErr error
1067+ }{
1068+ {
1069+ consensusResult: &ConsensusResult{
1070+ NumOfVote: map[string]uint64{
1071+ strPub1: 838063475500000, //1
1072+ strPub2: 474794800000000, //3
1073+ strPub3: 833812985000000, //2
1074+ strPub4: 285918061999999, //4
1075+ strPub5: 1228455289930297, //0
1076+ strPub6: 274387690000000, //5
1077+ strPub7: 1028455289930297,
1078+ },
1079+ },
1080+ consensusNode: map[string]*ConsensusNode{
1081+ strPub1: &ConsensusNode{XPub: xpub1, VoteNum: 838063475500000, Order: 1},
1082+ strPub2: &ConsensusNode{XPub: xpub2, VoteNum: 474794800000000, Order: 3},
1083+ strPub3: &ConsensusNode{XPub: xpub3, VoteNum: 833812985000000, Order: 2},
1084+ strPub4: &ConsensusNode{XPub: xpub4, VoteNum: 285918061999999, Order: 4},
1085+ strPub5: &ConsensusNode{XPub: xpub5, VoteNum: 1228455289930297, Order: 0},
1086+ strPub6: &ConsensusNode{XPub: xpub6, VoteNum: 274387690000000, Order: 5},
1087+ },
1088+ wantErr: chainkd.ErrBadKeyStr,
1089+ },
1090+ }
1091+
1092+ for i, c := range cases {
1093+ consensusNode, err := c.consensusResult.ConsensusNodes()
1094+ if err != nil {
1095+ if err != c.wantErr {
1096+ t.Errorf("test case #%d want err = %v, got err = %v", i, c.wantErr, err)
1097+ }
1098+ continue
1099+ }
1100+
1101+ if !testutil.DeepEqual(consensusNode, c.consensusNode) {
1102+ t.Errorf("test case #%d, want %v, got %v", i, c.consensusNode, consensusNode)
1103+ }
1104+ }
1105+}
1106+
1107+func TestFork(t *testing.T) {
1108+ consensusResult := &ConsensusResult{
1109+ Seq: 100,
1110+ NumOfVote: map[string]uint64{
1111+ "a": 100,
1112+ "b": 200,
1113+ },
1114+ CoinbaseReward: map[string]uint64{
1115+ "c": 300,
1116+ "d": 400,
1117+ },
1118+ BlockHash: bc.NewHash([32]byte{0x1, 0x2}),
1119+ BlockHeight: 1024,
1120+ }
1121+ copy := consensusResult.Fork()
1122+
1123+ if !reflect.DeepEqual(consensusResult, copy) {
1124+ t.Fatalf("failed on test consensusResult got %s want %s", spew.Sdump(copy), spew.Sdump(consensusResult))
1125+ }
1126+}
--- a/protocol/state/utxo_view_test.go
+++ b/protocol/state/utxo_view_test.go
@@ -3,8 +3,11 @@ package state
33 import (
44 "testing"
55
6+ "github.com/davecgh/go-spew/spew"
7+
68 "github.com/vapor/consensus"
79 "github.com/vapor/database/storage"
10+ "github.com/vapor/errors"
811 "github.com/vapor/protocol/bc"
912 "github.com/vapor/testutil"
1013 )
@@ -595,3 +598,832 @@ func TestDetachBlock(t *testing.T) {
595598 }
596599 }
597600 }
601+
602+func TestApplyCrossChainUTXO(t *testing.T) {
603+ cases := []struct {
604+ desc string
605+ block *bc.Block
606+ tx *bc.Tx
607+ prevUTXOView *UtxoViewpoint
608+ postUTXOView *UtxoViewpoint
609+ err error
610+ }{
611+ {
612+ desc: "normal test",
613+ block: &bc.Block{
614+ BlockHeader: &bc.BlockHeader{
615+ Height: 100,
616+ },
617+ },
618+ tx: &bc.Tx{
619+ TxHeader: &bc.TxHeader{
620+ ResultIds: []*bc.Hash{},
621+ },
622+ MainchainOutputIDs: []bc.Hash{
623+ bc.Hash{V0: 0},
624+ },
625+ Entries: voteEntry,
626+ },
627+ prevUTXOView: &UtxoViewpoint{
628+ Entries: map[bc.Hash]*storage.UtxoEntry{
629+ bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.CrosschainUTXOType, 0, false),
630+ },
631+ },
632+ postUTXOView: &UtxoViewpoint{
633+ Entries: map[bc.Hash]*storage.UtxoEntry{
634+ bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.CrosschainUTXOType, 100, true),
635+ },
636+ },
637+ err: nil,
638+ },
639+ {
640+ desc: "test failed to find mainchain output entry",
641+ block: &bc.Block{
642+ BlockHeader: &bc.BlockHeader{},
643+ },
644+ tx: &bc.Tx{
645+ TxHeader: &bc.TxHeader{
646+ ResultIds: []*bc.Hash{},
647+ },
648+ MainchainOutputIDs: []bc.Hash{
649+ bc.Hash{V0: 0},
650+ },
651+ Entries: voteEntry,
652+ },
653+ prevUTXOView: &UtxoViewpoint{
654+ Entries: map[bc.Hash]*storage.UtxoEntry{},
655+ },
656+ postUTXOView: &UtxoViewpoint{
657+ Entries: map[bc.Hash]*storage.UtxoEntry{},
658+ },
659+ err: errors.New("fail to find mainchain output entry"),
660+ },
661+ {
662+ desc: "test mainchain output has been spent",
663+ block: &bc.Block{
664+ BlockHeader: &bc.BlockHeader{},
665+ },
666+ tx: &bc.Tx{
667+ TxHeader: &bc.TxHeader{
668+ ResultIds: []*bc.Hash{},
669+ },
670+ MainchainOutputIDs: []bc.Hash{
671+ bc.Hash{V0: 0},
672+ },
673+ Entries: voteEntry,
674+ },
675+ prevUTXOView: &UtxoViewpoint{
676+ Entries: map[bc.Hash]*storage.UtxoEntry{
677+ bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.CrosschainUTXOType, 0, true),
678+ },
679+ },
680+ postUTXOView: &UtxoViewpoint{
681+ Entries: map[bc.Hash]*storage.UtxoEntry{},
682+ },
683+ err: errors.New("mainchain output has been spent"),
684+ },
685+ }
686+
687+ for i, c := range cases {
688+ if err := c.prevUTXOView.applyCrossChainUtxo(c.block, c.tx); err != nil {
689+ if err.Error() != c.err.Error() {
690+ t.Errorf("test case #%d want err = %v, got err = %v", i, c.err, err)
691+ }
692+ continue
693+ }
694+
695+ if !testutil.DeepEqual(c.prevUTXOView, c.postUTXOView) {
696+ t.Errorf("test case #%d, want %v, got %v", i, c.postUTXOView, c.prevUTXOView)
697+ }
698+ }
699+}
700+
701+func TestApplyOutputUTXO(t *testing.T) {
702+ cases := []struct {
703+ desc string
704+ block *bc.Block
705+ tx *bc.Tx
706+ statusFail bool
707+ prevUTXOView *UtxoViewpoint
708+ postUTXOView *UtxoViewpoint
709+ err error
710+ }{
711+ {
712+ desc: "normal test IntraChainOutput,VoteOutput,Retirement",
713+ block: &bc.Block{
714+ BlockHeader: &bc.BlockHeader{},
715+ },
716+ tx: &bc.Tx{
717+ TxHeader: &bc.TxHeader{
718+ ResultIds: []*bc.Hash{&bc.Hash{V0: 0}, &bc.Hash{V0: 1}, &bc.Hash{V0: 2}},
719+ },
720+ Entries: map[bc.Hash]bc.Entry{
721+ bc.Hash{V0: 0}: &bc.IntraChainOutput{
722+ Source: &bc.ValueSource{
723+ Value: &bc.AssetAmount{
724+ AssetId: &bc.AssetID{V0: 0},
725+ Amount: 100,
726+ },
727+ },
728+ },
729+ bc.Hash{V0: 1}: &bc.VoteOutput{
730+ Source: &bc.ValueSource{
731+ Value: &bc.AssetAmount{
732+ AssetId: &bc.AssetID{V0: 1},
733+ },
734+ },
735+ },
736+ bc.Hash{V0: 2}: &bc.Retirement{
737+ Source: &bc.ValueSource{
738+ Value: &bc.AssetAmount{
739+ AssetId: &bc.AssetID{V0: 1},
740+ },
741+ },
742+ },
743+ },
744+ },
745+ prevUTXOView: &UtxoViewpoint{
746+ Entries: map[bc.Hash]*storage.UtxoEntry{},
747+ },
748+ postUTXOView: &UtxoViewpoint{
749+ Entries: map[bc.Hash]*storage.UtxoEntry{
750+ bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.NormalUTXOType, 0, false),
751+ bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, false),
752+ },
753+ },
754+ err: nil,
755+ },
756+ {
757+ desc: "test statusFail",
758+ block: &bc.Block{
759+ BlockHeader: &bc.BlockHeader{},
760+ },
761+ tx: &bc.Tx{
762+ TxHeader: &bc.TxHeader{
763+ ResultIds: []*bc.Hash{&bc.Hash{V0: 0}, &bc.Hash{V0: 1}, &bc.Hash{V0: 2}},
764+ },
765+ Entries: map[bc.Hash]bc.Entry{
766+ bc.Hash{V0: 0}: &bc.IntraChainOutput{
767+ Source: &bc.ValueSource{
768+ Value: &bc.AssetAmount{
769+ AssetId: &bc.AssetID{V0: 0},
770+ Amount: 100,
771+ },
772+ },
773+ },
774+ bc.Hash{V0: 1}: &bc.VoteOutput{
775+ Source: &bc.ValueSource{
776+ Value: &bc.AssetAmount{
777+ AssetId: consensus.BTMAssetID,
778+ },
779+ },
780+ },
781+ bc.Hash{V0: 2}: &bc.Retirement{
782+ Source: &bc.ValueSource{
783+ Value: &bc.AssetAmount{
784+ AssetId: &bc.AssetID{V0: 1},
785+ },
786+ },
787+ },
788+ },
789+ },
790+ statusFail: true,
791+ prevUTXOView: &UtxoViewpoint{
792+ Entries: map[bc.Hash]*storage.UtxoEntry{},
793+ },
794+ postUTXOView: &UtxoViewpoint{
795+ Entries: map[bc.Hash]*storage.UtxoEntry{
796+ bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, false),
797+ },
798+ },
799+ err: nil,
800+ },
801+ {
802+ desc: "test failed on found id from tx entry",
803+ block: &bc.Block{
804+ BlockHeader: &bc.BlockHeader{},
805+ },
806+ tx: &bc.Tx{
807+ TxHeader: &bc.TxHeader{
808+ ResultIds: []*bc.Hash{&bc.Hash{V0: 0}, &bc.Hash{V0: 1}, &bc.Hash{V0: 2}},
809+ },
810+ Entries: map[bc.Hash]bc.Entry{
811+ bc.Hash{V0: 1}: &bc.VoteOutput{
812+ Source: &bc.ValueSource{
813+ Value: &bc.AssetAmount{
814+ AssetId: consensus.BTMAssetID,
815+ },
816+ },
817+ },
818+ bc.Hash{V0: 2}: &bc.Retirement{
819+ Source: &bc.ValueSource{
820+ Value: &bc.AssetAmount{
821+ AssetId: &bc.AssetID{V0: 1},
822+ },
823+ },
824+ },
825+ },
826+ },
827+ statusFail: false,
828+ prevUTXOView: &UtxoViewpoint{
829+ Entries: map[bc.Hash]*storage.UtxoEntry{},
830+ },
831+ postUTXOView: &UtxoViewpoint{
832+ Entries: map[bc.Hash]*storage.UtxoEntry{},
833+ },
834+ err: bc.ErrMissingEntry,
835+ },
836+ }
837+
838+ for i, c := range cases {
839+ if err := c.prevUTXOView.applyOutputUtxo(c.block, c.tx, c.statusFail); err != nil {
840+ if errors.Root(err) != errors.Root(c.err) {
841+ t.Errorf("test case #%d want err = %v, got err = %v", i, c.err.Error(), err.Error())
842+ }
843+ continue
844+ }
845+
846+ if !testutil.DeepEqual(c.prevUTXOView, c.postUTXOView) {
847+ t.Errorf("test case #%d, want %v, got %v", i, c.postUTXOView, c.prevUTXOView)
848+ }
849+ }
850+}
851+
852+func TestApplySpendUTXO(t *testing.T) {
853+ cases := []struct {
854+ desc string
855+ block *bc.Block
856+ tx *bc.Tx
857+ statusFail bool
858+ prevUTXOView *UtxoViewpoint
859+ postUTXOView *UtxoViewpoint
860+ err error
861+ }{
862+ {
863+ desc: "normal test",
864+ block: &bc.Block{
865+ BlockHeader: &bc.BlockHeader{
866+ Height: consensus.ActiveNetParams.VotePendingBlockNumber,
867+ },
868+ },
869+ tx: &bc.Tx{
870+ TxHeader: &bc.TxHeader{},
871+ SpentOutputIDs: []bc.Hash{{V0: 0}, {V0: 1}, {V0: 2}},
872+ Entries: map[bc.Hash]bc.Entry{
873+ bc.Hash{V0: 0}: &bc.IntraChainOutput{
874+ Source: &bc.ValueSource{
875+ Value: &bc.AssetAmount{
876+ AssetId: &bc.AssetID{V0: 0},
877+ Amount: 100,
878+ },
879+ },
880+ },
881+ bc.Hash{V0: 1}: &bc.VoteOutput{
882+ Source: &bc.ValueSource{
883+ Value: &bc.AssetAmount{
884+ AssetId: &bc.AssetID{V0: 1},
885+ },
886+ },
887+ },
888+ bc.Hash{V0: 2}: &bc.IntraChainOutput{
889+ Source: &bc.ValueSource{
890+ Value: &bc.AssetAmount{
891+ AssetId: consensus.BTMAssetID,
892+ Amount: 100,
893+ },
894+ },
895+ },
896+ },
897+ },
898+ prevUTXOView: &UtxoViewpoint{
899+ Entries: map[bc.Hash]*storage.UtxoEntry{
900+ bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.NormalUTXOType, 0, false),
901+ bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, false),
902+ bc.Hash{V0: 2}: storage.NewUtxoEntry(storage.CoinbaseUTXOType, 0, false),
903+ },
904+ },
905+ postUTXOView: &UtxoViewpoint{
906+ Entries: map[bc.Hash]*storage.UtxoEntry{
907+ bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.NormalUTXOType, 0, true),
908+ bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, true),
909+ bc.Hash{V0: 2}: storage.NewUtxoEntry(storage.CoinbaseUTXOType, 0, true),
910+ },
911+ },
912+ err: nil,
913+ },
914+ {
915+ desc: "test coinbase is not ready for use",
916+ block: &bc.Block{
917+ BlockHeader: &bc.BlockHeader{
918+ Height: consensus.ActiveNetParams.CoinbasePendingBlockNumber - 1,
919+ },
920+ },
921+ tx: &bc.Tx{
922+ TxHeader: &bc.TxHeader{},
923+ SpentOutputIDs: []bc.Hash{{V0: 1}, {V0: 2}},
924+ Entries: map[bc.Hash]bc.Entry{
925+ bc.Hash{V0: 1}: &bc.VoteOutput{
926+ Source: &bc.ValueSource{
927+ Value: &bc.AssetAmount{
928+ AssetId: &bc.AssetID{V0: 1},
929+ },
930+ },
931+ },
932+ bc.Hash{V0: 2}: &bc.IntraChainOutput{
933+ Source: &bc.ValueSource{
934+ Value: &bc.AssetAmount{
935+ AssetId: consensus.BTMAssetID,
936+ Amount: 100,
937+ },
938+ },
939+ },
940+ },
941+ },
942+ prevUTXOView: &UtxoViewpoint{
943+ Entries: map[bc.Hash]*storage.UtxoEntry{
944+ bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.NormalUTXOType, 0, false),
945+ bc.Hash{V0: 2}: storage.NewUtxoEntry(storage.CoinbaseUTXOType, 0, false),
946+ },
947+ },
948+ postUTXOView: &UtxoViewpoint{
949+ Entries: map[bc.Hash]*storage.UtxoEntry{},
950+ },
951+ err: errors.New("coinbase utxo is not ready for use"),
952+ },
953+ {
954+ desc: "test Coin is within the voting lock time",
955+ block: &bc.Block{
956+ BlockHeader: &bc.BlockHeader{
957+ Height: consensus.ActiveNetParams.VotePendingBlockNumber - 1,
958+ },
959+ },
960+ tx: &bc.Tx{
961+ TxHeader: &bc.TxHeader{},
962+ SpentOutputIDs: []bc.Hash{{V0: 1}, {V0: 2}},
963+ Entries: map[bc.Hash]bc.Entry{
964+ bc.Hash{V0: 1}: &bc.VoteOutput{
965+ Source: &bc.ValueSource{
966+ Value: &bc.AssetAmount{
967+ AssetId: &bc.AssetID{V0: 1},
968+ },
969+ },
970+ },
971+ bc.Hash{V0: 2}: &bc.IntraChainOutput{
972+ Source: &bc.ValueSource{
973+ Value: &bc.AssetAmount{
974+ AssetId: consensus.BTMAssetID,
975+ Amount: 100,
976+ },
977+ },
978+ },
979+ },
980+ },
981+ prevUTXOView: &UtxoViewpoint{
982+ Entries: map[bc.Hash]*storage.UtxoEntry{
983+ bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.NormalUTXOType, 0, false),
984+ bc.Hash{V0: 2}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, false),
985+ },
986+ },
987+ postUTXOView: &UtxoViewpoint{
988+ Entries: map[bc.Hash]*storage.UtxoEntry{},
989+ },
990+ err: errors.New("Coin is within the voting lock time"),
991+ },
992+ {
993+ desc: "test utxo has been spent",
994+ block: &bc.Block{
995+ BlockHeader: &bc.BlockHeader{
996+ Height: 0,
997+ },
998+ },
999+ tx: &bc.Tx{
1000+ TxHeader: &bc.TxHeader{},
1001+ SpentOutputIDs: []bc.Hash{{V0: 0}, {V0: 1}, {V0: 2}},
1002+ Entries: map[bc.Hash]bc.Entry{
1003+ bc.Hash{V0: 0}: &bc.IntraChainOutput{
1004+ Source: &bc.ValueSource{
1005+ Value: &bc.AssetAmount{
1006+ AssetId: &bc.AssetID{V0: 0},
1007+ Amount: 100,
1008+ },
1009+ },
1010+ },
1011+ bc.Hash{V0: 1}: &bc.VoteOutput{
1012+ Source: &bc.ValueSource{
1013+ Value: &bc.AssetAmount{
1014+ AssetId: &bc.AssetID{V0: 1},
1015+ },
1016+ },
1017+ },
1018+ bc.Hash{V0: 2}: &bc.IntraChainOutput{
1019+ Source: &bc.ValueSource{
1020+ Value: &bc.AssetAmount{
1021+ AssetId: consensus.BTMAssetID,
1022+ Amount: 100,
1023+ },
1024+ },
1025+ },
1026+ },
1027+ },
1028+ prevUTXOView: &UtxoViewpoint{
1029+ Entries: map[bc.Hash]*storage.UtxoEntry{
1030+ bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.NormalUTXOType, 0, true),
1031+ bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, false),
1032+ bc.Hash{V0: 2}: storage.NewUtxoEntry(storage.CoinbaseUTXOType, 0, false),
1033+ },
1034+ },
1035+ postUTXOView: &UtxoViewpoint{
1036+ Entries: map[bc.Hash]*storage.UtxoEntry{},
1037+ },
1038+ err: errors.New("utxo has been spent"),
1039+ },
1040+ {
1041+ desc: "test faild to find utxo entry",
1042+ block: &bc.Block{
1043+ BlockHeader: &bc.BlockHeader{
1044+ Height: 0,
1045+ },
1046+ },
1047+ tx: &bc.Tx{
1048+ TxHeader: &bc.TxHeader{},
1049+ SpentOutputIDs: []bc.Hash{{V0: 0}, {V0: 1}, {V0: 2}},
1050+ Entries: map[bc.Hash]bc.Entry{
1051+ bc.Hash{V0: 0}: &bc.IntraChainOutput{
1052+ Source: &bc.ValueSource{
1053+ Value: &bc.AssetAmount{
1054+ AssetId: &bc.AssetID{V0: 0},
1055+ Amount: 100,
1056+ },
1057+ },
1058+ },
1059+ bc.Hash{V0: 1}: &bc.VoteOutput{
1060+ Source: &bc.ValueSource{
1061+ Value: &bc.AssetAmount{
1062+ AssetId: &bc.AssetID{V0: 1},
1063+ },
1064+ },
1065+ },
1066+ bc.Hash{V0: 2}: &bc.IntraChainOutput{
1067+ Source: &bc.ValueSource{
1068+ Value: &bc.AssetAmount{
1069+ AssetId: consensus.BTMAssetID,
1070+ Amount: 100,
1071+ },
1072+ },
1073+ },
1074+ },
1075+ },
1076+ prevUTXOView: &UtxoViewpoint{
1077+ Entries: map[bc.Hash]*storage.UtxoEntry{
1078+ bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, false),
1079+ bc.Hash{V0: 2}: storage.NewUtxoEntry(storage.CoinbaseUTXOType, 0, false),
1080+ },
1081+ },
1082+ postUTXOView: &UtxoViewpoint{
1083+ Entries: map[bc.Hash]*storage.UtxoEntry{},
1084+ },
1085+ err: errors.New("fail to find utxo entry"),
1086+ },
1087+ }
1088+
1089+ for i, c := range cases {
1090+ if err := c.prevUTXOView.applySpendUtxo(c.block, c.tx, c.statusFail); err != nil {
1091+ if err.Error() != c.err.Error() {
1092+ t.Errorf("test case #%d want err = %v, got err = %v", i, err.Error(), c.err.Error())
1093+ }
1094+ continue
1095+ }
1096+
1097+ if !testutil.DeepEqual(c.prevUTXOView, c.postUTXOView) {
1098+ t.Errorf("test case #%d, want %v, got %v", i, spew.Sdump(c.postUTXOView), spew.Sdump(c.prevUTXOView))
1099+ }
1100+ }
1101+}
1102+
1103+func TestDetachCrossChainUTXO(t *testing.T) {
1104+ cases := []struct {
1105+ desc string
1106+ tx *bc.Tx
1107+ prevUTXOView *UtxoViewpoint
1108+ postUTXOView *UtxoViewpoint
1109+ err error
1110+ }{
1111+ {
1112+ desc: "normal test",
1113+ tx: &bc.Tx{
1114+ TxHeader: &bc.TxHeader{
1115+ ResultIds: []*bc.Hash{},
1116+ },
1117+ MainchainOutputIDs: []bc.Hash{
1118+ bc.Hash{V0: 0},
1119+ },
1120+ Entries: voteEntry,
1121+ },
1122+ prevUTXOView: &UtxoViewpoint{
1123+ Entries: map[bc.Hash]*storage.UtxoEntry{
1124+ bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.CrosschainUTXOType, 0, true),
1125+ },
1126+ },
1127+ postUTXOView: &UtxoViewpoint{
1128+ Entries: map[bc.Hash]*storage.UtxoEntry{
1129+ bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.CrosschainUTXOType, 0, false),
1130+ },
1131+ },
1132+ err: nil,
1133+ },
1134+ {
1135+ desc: "test failed to find mainchain output entry",
1136+ tx: &bc.Tx{
1137+ TxHeader: &bc.TxHeader{
1138+ ResultIds: []*bc.Hash{},
1139+ },
1140+ MainchainOutputIDs: []bc.Hash{
1141+ bc.Hash{V0: 0},
1142+ },
1143+ Entries: voteEntry,
1144+ },
1145+ prevUTXOView: &UtxoViewpoint{
1146+ Entries: map[bc.Hash]*storage.UtxoEntry{},
1147+ },
1148+ postUTXOView: &UtxoViewpoint{
1149+ Entries: map[bc.Hash]*storage.UtxoEntry{},
1150+ },
1151+ err: errors.New("fail to find mainchain output entry"),
1152+ },
1153+ {
1154+ desc: "test revert output is unspent",
1155+ tx: &bc.Tx{
1156+ TxHeader: &bc.TxHeader{
1157+ ResultIds: []*bc.Hash{},
1158+ },
1159+ MainchainOutputIDs: []bc.Hash{
1160+ bc.Hash{V0: 0},
1161+ },
1162+ Entries: voteEntry,
1163+ },
1164+ prevUTXOView: &UtxoViewpoint{
1165+ Entries: map[bc.Hash]*storage.UtxoEntry{
1166+ bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.CrosschainUTXOType, 0, false),
1167+ },
1168+ },
1169+ postUTXOView: &UtxoViewpoint{
1170+ Entries: map[bc.Hash]*storage.UtxoEntry{},
1171+ },
1172+ err: errors.New("mainchain output is unspent"),
1173+ },
1174+ }
1175+
1176+ for i, c := range cases {
1177+ if err := c.prevUTXOView.detachCrossChainUtxo(c.tx); err != nil {
1178+ if err.Error() != c.err.Error() {
1179+ t.Errorf("test case #%d want err = %v, got err = %v", i, c.err, err)
1180+ }
1181+ continue
1182+ }
1183+
1184+ if !testutil.DeepEqual(c.prevUTXOView, c.postUTXOView) {
1185+ t.Errorf("test case #%d, want %v, got %v", i, c.postUTXOView, c.prevUTXOView)
1186+ }
1187+ }
1188+}
1189+
1190+func TestDetachOutputUTXO(t *testing.T) {
1191+ cases := []struct {
1192+ desc string
1193+ tx *bc.Tx
1194+ statusFail bool
1195+ prevUTXOView *UtxoViewpoint
1196+ postUTXOView *UtxoViewpoint
1197+ err error
1198+ }{
1199+ {
1200+ desc: "normal test IntraChainOutput,VoteOutput",
1201+ tx: &bc.Tx{
1202+ TxHeader: &bc.TxHeader{
1203+ ResultIds: []*bc.Hash{&bc.Hash{V0: 0}, &bc.Hash{V0: 1}, &bc.Hash{V0: 2}},
1204+ },
1205+ Entries: map[bc.Hash]bc.Entry{
1206+ bc.Hash{V0: 0}: &bc.IntraChainOutput{
1207+ Source: &bc.ValueSource{
1208+ Value: &bc.AssetAmount{
1209+ AssetId: &bc.AssetID{V0: 0},
1210+ Amount: 100,
1211+ },
1212+ },
1213+ },
1214+ bc.Hash{V0: 1}: &bc.VoteOutput{
1215+ Source: &bc.ValueSource{
1216+ Value: &bc.AssetAmount{
1217+ AssetId: &bc.AssetID{V0: 1},
1218+ },
1219+ },
1220+ },
1221+ bc.Hash{V0: 2}: &bc.Retirement{
1222+ Source: &bc.ValueSource{
1223+ Value: &bc.AssetAmount{
1224+ AssetId: &bc.AssetID{V0: 1},
1225+ },
1226+ },
1227+ },
1228+ },
1229+ },
1230+ prevUTXOView: &UtxoViewpoint{
1231+ Entries: map[bc.Hash]*storage.UtxoEntry{
1232+ bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.NormalUTXOType, 0, true),
1233+ bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, true),
1234+ },
1235+ },
1236+ postUTXOView: &UtxoViewpoint{
1237+ Entries: map[bc.Hash]*storage.UtxoEntry{
1238+ bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.NormalUTXOType, 0, true),
1239+ bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, true),
1240+ },
1241+ },
1242+ err: nil,
1243+ },
1244+ {
1245+ desc: "test statusFail",
1246+ tx: &bc.Tx{
1247+ TxHeader: &bc.TxHeader{
1248+ ResultIds: []*bc.Hash{&bc.Hash{V0: 0}, &bc.Hash{V0: 1}},
1249+ },
1250+ Entries: map[bc.Hash]bc.Entry{
1251+ bc.Hash{V0: 0}: &bc.IntraChainOutput{
1252+ Source: &bc.ValueSource{
1253+ Value: &bc.AssetAmount{
1254+ AssetId: &bc.AssetID{V0: 0},
1255+ Amount: 100,
1256+ },
1257+ },
1258+ },
1259+ bc.Hash{V0: 1}: &bc.VoteOutput{
1260+ Source: &bc.ValueSource{
1261+ Value: &bc.AssetAmount{
1262+ AssetId: consensus.BTMAssetID,
1263+ },
1264+ },
1265+ },
1266+ },
1267+ },
1268+ statusFail: true,
1269+ prevUTXOView: &UtxoViewpoint{
1270+ Entries: map[bc.Hash]*storage.UtxoEntry{},
1271+ },
1272+ postUTXOView: &UtxoViewpoint{
1273+ Entries: map[bc.Hash]*storage.UtxoEntry{
1274+ bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, true),
1275+ },
1276+ },
1277+ err: nil,
1278+ },
1279+ {
1280+ desc: "test failed on found id from tx entry",
1281+ tx: &bc.Tx{
1282+ TxHeader: &bc.TxHeader{
1283+ ResultIds: []*bc.Hash{&bc.Hash{V0: 0}, &bc.Hash{V0: 1}, &bc.Hash{V0: 2}},
1284+ },
1285+ Entries: map[bc.Hash]bc.Entry{
1286+ bc.Hash{V0: 1}: &bc.VoteOutput{
1287+ Source: &bc.ValueSource{
1288+ Value: &bc.AssetAmount{
1289+ AssetId: consensus.BTMAssetID,
1290+ },
1291+ },
1292+ },
1293+ bc.Hash{V0: 2}: &bc.Retirement{
1294+ Source: &bc.ValueSource{
1295+ Value: &bc.AssetAmount{
1296+ AssetId: &bc.AssetID{V0: 1},
1297+ },
1298+ },
1299+ },
1300+ },
1301+ },
1302+ statusFail: false,
1303+ prevUTXOView: &UtxoViewpoint{
1304+ Entries: map[bc.Hash]*storage.UtxoEntry{},
1305+ },
1306+ postUTXOView: &UtxoViewpoint{
1307+ Entries: map[bc.Hash]*storage.UtxoEntry{},
1308+ },
1309+ err: bc.ErrMissingEntry,
1310+ },
1311+ }
1312+
1313+ for i, c := range cases {
1314+ if err := c.prevUTXOView.detachOutputUtxo(c.tx, c.statusFail); err != nil {
1315+ if errors.Root(err) != errors.Root(c.err) {
1316+ t.Errorf("test case #%d want err = %v, got err = %v", i, c.err.Error(), err.Error())
1317+ }
1318+ continue
1319+ }
1320+
1321+ if !testutil.DeepEqual(c.prevUTXOView, c.postUTXOView) {
1322+ t.Errorf("test case #%d, want %v, got %v", i, c.postUTXOView, c.prevUTXOView)
1323+ }
1324+ }
1325+}
1326+
1327+func TestDetachSpendUTXO(t *testing.T) {
1328+ cases := []struct {
1329+ desc string
1330+ tx *bc.Tx
1331+ statusFail bool
1332+ prevUTXOView *UtxoViewpoint
1333+ postUTXOView *UtxoViewpoint
1334+ err error
1335+ }{
1336+ {
1337+ desc: "normal test",
1338+ tx: &bc.Tx{
1339+ TxHeader: &bc.TxHeader{},
1340+ SpentOutputIDs: []bc.Hash{{V0: 0}, {V0: 1}},
1341+ Entries: map[bc.Hash]bc.Entry{
1342+ bc.Hash{V0: 0}: &bc.IntraChainOutput{
1343+ Source: &bc.ValueSource{
1344+ Value: &bc.AssetAmount{
1345+ AssetId: &bc.AssetID{V0: 0},
1346+ Amount: 100,
1347+ },
1348+ },
1349+ },
1350+ bc.Hash{V0: 1}: &bc.VoteOutput{
1351+ Source: &bc.ValueSource{
1352+ Value: &bc.AssetAmount{
1353+ AssetId: &bc.AssetID{V0: 1},
1354+ },
1355+ },
1356+ },
1357+ },
1358+ },
1359+ prevUTXOView: &UtxoViewpoint{
1360+ Entries: map[bc.Hash]*storage.UtxoEntry{
1361+ bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.NormalUTXOType, 0, true),
1362+ bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, true),
1363+ },
1364+ },
1365+ postUTXOView: &UtxoViewpoint{
1366+ Entries: map[bc.Hash]*storage.UtxoEntry{
1367+ bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.NormalUTXOType, 0, false),
1368+ bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, false),
1369+ },
1370+ },
1371+ err: nil,
1372+ },
1373+ {
1374+ desc: "test utxo has been spent",
1375+ tx: &bc.Tx{
1376+ TxHeader: &bc.TxHeader{},
1377+ SpentOutputIDs: []bc.Hash{{V0: 0}, {V0: 1}, {V0: 2}},
1378+ Entries: map[bc.Hash]bc.Entry{
1379+ bc.Hash{V0: 0}: &bc.IntraChainOutput{
1380+ Source: &bc.ValueSource{
1381+ Value: &bc.AssetAmount{
1382+ AssetId: consensus.BTMAssetID,
1383+ Amount: 100,
1384+ },
1385+ },
1386+ },
1387+ bc.Hash{V0: 1}: &bc.VoteOutput{
1388+ Source: &bc.ValueSource{
1389+ Value: &bc.AssetAmount{
1390+ AssetId: &bc.AssetID{V0: 1},
1391+ },
1392+ },
1393+ },
1394+ bc.Hash{V0: 2}: &bc.IntraChainOutput{
1395+ Source: &bc.ValueSource{
1396+ Value: &bc.AssetAmount{
1397+ AssetId: consensus.BTMAssetID,
1398+ Amount: 100,
1399+ },
1400+ },
1401+ },
1402+ },
1403+ },
1404+ prevUTXOView: &UtxoViewpoint{
1405+ Entries: map[bc.Hash]*storage.UtxoEntry{
1406+ bc.Hash{V0: 0}: storage.NewUtxoEntry(storage.NormalUTXOType, 0, false),
1407+ bc.Hash{V0: 1}: storage.NewUtxoEntry(storage.VoteUTXOType, 0, true),
1408+ },
1409+ },
1410+ postUTXOView: &UtxoViewpoint{
1411+ Entries: map[bc.Hash]*storage.UtxoEntry{},
1412+ },
1413+ err: errors.New("try to revert an unspent utxo"),
1414+ },
1415+ }
1416+
1417+ for i, c := range cases {
1418+ if err := c.prevUTXOView.detachSpendUtxo(c.tx, c.statusFail); err != nil {
1419+ if err.Error() != c.err.Error() {
1420+ t.Errorf("test case #%d want err = %v, got err = %v", i, err.Error(), c.err.Error())
1421+ }
1422+ continue
1423+ }
1424+
1425+ if !testutil.DeepEqual(c.prevUTXOView, c.postUTXOView) {
1426+ t.Errorf("test case #%d, want %v, got %v", i, spew.Sdump(c.postUTXOView), spew.Sdump(c.prevUTXOView))
1427+ }
1428+ }
1429+}
--- a/test/builder_test.go
+++ b/test/builder_test.go
@@ -55,26 +55,16 @@ func TestBuildBtmTxChain(t *testing.T) {
5555 wantUtxo: 10 * chainTxMergeGas,
5656 },
5757 {
58- inputUtxo: []uint64{22, 123, 53, 234, 23, 4, 2423, 24, 23, 43, 34, 234, 234, 24},
58+ inputUtxo: []uint64{22, 123, 53, 234, 23, 4, 2423, 24, 23, 43, 34, 234, 234, 24, 11, 16, 33, 59, 73, 89, 66},
5959 wantInput: [][]uint64{
60- []uint64{22, 123, 53, 234, 23},
61- []uint64{4, 2423, 24, 23, 43},
62- []uint64{34, 234, 234, 24, 454},
63- []uint64{2516, 979},
64- []uint64{234, 24, 197},
65- []uint64{260, 2469, 310},
66- []uint64{454, 3038},
60+ []uint64{22, 123, 53, 234, 23, 4, 2423, 24, 23, 43, 34, 234, 234, 24, 11, 16, 33, 59, 73, 89},
61+ []uint64{66, 3778},
6762 },
6863 wantOutput: [][]uint64{
69- []uint64{454},
70- []uint64{2516},
71- []uint64{979},
72- []uint64{3494},
73- []uint64{454},
74- []uint64{3038},
75- []uint64{3491},
64+ []uint64{3778},
65+ []uint64{3843},
7666 },
77- wantUtxo: 3494 * chainTxMergeGas,
67+ wantUtxo: 3843 * chainTxMergeGas,
7868 },
7969 }
8070
--- a/toolbar/apinode/transaction.go
+++ b/toolbar/apinode/transaction.go
@@ -1,6 +1,7 @@
11 package apinode
22
33 import (
4+ "encoding/hex"
45 "encoding/json"
56
67 "github.com/vapor/blockchain/txbuilder"
@@ -44,9 +45,33 @@ func (c *ControlAddressAction) MarshalJSON() ([]byte, error) {
4445 })
4546 }
4647
47-func (n *Node) BatchSendBTM(accountID, password string, outputs map[string]uint64) error {
48- totalBTM := uint64(10000000)
48+type RetireAction struct {
49+ *bc.AssetAmount
50+ Arbitrary []byte
51+}
52+
53+func (r *RetireAction) MarshalJSON() ([]byte, error) {
54+ return json.Marshal(&struct {
55+ Type string `json:"type"`
56+ Arbitrary string `json:"arbitrary"`
57+ *bc.AssetAmount
58+ }{
59+ Type: "retire",
60+ Arbitrary: hex.EncodeToString(r.Arbitrary),
61+ AssetAmount: r.AssetAmount,
62+ })
63+}
64+
65+func (n *Node) BatchSendBTM(accountID, password string, outputs map[string]uint64, memo []byte) (string, error) {
66+ totalBTM := uint64(1000000)
4967 actions := []interface{}{}
68+ if len(memo) > 0 {
69+ actions = append(actions, &RetireAction{
70+ Arbitrary: memo,
71+ AssetAmount: &bc.AssetAmount{AssetId: consensus.BTMAssetID, Amount: 1},
72+ })
73+ }
74+
5075 for address, amount := range outputs {
5176 actions = append(actions, &ControlAddressAction{
5277 Address: address,
@@ -62,16 +87,15 @@ func (n *Node) BatchSendBTM(accountID, password string, outputs map[string]uint6
6287
6388 tpl, err := n.buildTx(actions)
6489 if err != nil {
65- return err
90+ return "", err
6691 }
6792
6893 tpl, err = n.signTx(tpl, password)
6994 if err != nil {
70- return err
95+ return "", err
7196 }
7297
73- _, err = n.SubmitTx(tpl.Transaction)
74- return err
98+ return n.SubmitTx(tpl.Transaction)
7599 }
76100
77101 type buildTxReq struct {
@@ -89,6 +113,18 @@ func (n *Node) buildTx(actions []interface{}) (*txbuilder.Template, error) {
89113 return result, n.request(url, payload, result)
90114 }
91115
116+func (n *Node) BuildChainTxs(actions []interface{}) ([]*txbuilder.Template, error) {
117+ url := "/build-chain-transactions"
118+
119+ payload, err := json.Marshal(&buildTxReq{Actions: actions})
120+ if err != nil {
121+ return nil, errors.Wrap(err, "Marshal spend request")
122+ }
123+
124+ result := []*txbuilder.Template{}
125+ return result, n.request(url, payload, &result)
126+}
127+
92128 type signTxReq struct {
93129 Tx *txbuilder.Template `json:"transaction"`
94130 Password string `json:"password"`
@@ -118,6 +154,35 @@ func (n *Node) signTx(tpl *txbuilder.Template, password string) (*txbuilder.Temp
118154 return resp.Tx, nil
119155 }
120156
157+type signTxsReq struct {
158+ Txs []*txbuilder.Template `json:"transactions"`
159+ Password string `json:"password"`
160+}
161+
162+type signTxsResp struct {
163+ Txs []*txbuilder.Template `json:"transaction"`
164+ SignComplete bool `json:"sign_complete"`
165+}
166+
167+func (n *Node) SignTxs(tpls []*txbuilder.Template, password string) ([]*txbuilder.Template, error) {
168+ url := "/sign-transactions"
169+ payload, err := json.Marshal(&signTxsReq{Txs: tpls, Password: password})
170+ if err != nil {
171+ return nil, errors.Wrap(err, "json marshal")
172+ }
173+
174+ resp := &signTxsResp{}
175+ if err := n.request(url, payload, resp); err != nil {
176+ return nil, err
177+ }
178+
179+ if !resp.SignComplete {
180+ return nil, errors.New("sign fail")
181+ }
182+
183+ return resp.Txs, nil
184+}
185+
121186 type submitTxReq struct {
122187 Tx *types.Tx `json:"raw_transaction"`
123188 }
@@ -136,3 +201,22 @@ func (n *Node) SubmitTx(tx *types.Tx) (string, error) {
136201 res := &submitTxResp{}
137202 return res.TxID, n.request(url, payload, res)
138203 }
204+
205+type submitTxsReq struct {
206+ Txs []*types.Tx `json:"raw_transactions"`
207+}
208+
209+type submitTxsResp struct {
210+ TxsID []string `json:"tx_id"`
211+}
212+
213+func (n *Node) SubmitTxs(txs []*types.Tx) ([]string, error) {
214+ url := "/submit-transactions"
215+ payload, err := json.Marshal(submitTxsReq{Txs: txs})
216+ if err != nil {
217+ return []string{}, errors.Wrap(err, "json marshal")
218+ }
219+
220+ res := &submitTxsResp{}
221+ return res.TxsID, n.request(url, payload, res)
222+}
--- a/toolbar/consensusreward/consensusreward.go
+++ b/toolbar/consensusreward/consensus_reward.go
@@ -3,6 +3,8 @@ package consensusreward
33 import (
44 "math/big"
55
6+ log "github.com/sirupsen/logrus"
7+
68 "github.com/vapor/consensus"
79 "github.com/vapor/errors"
810 "github.com/vapor/toolbar/apinode"
@@ -57,6 +59,7 @@ func (s *StandbyNodeReward) Settlement() error {
5759 return err
5860 }
5961 }
62+
6063 rewards := map[string]uint64{}
6164 for _, item := range s.cfg.RewardConf.Node {
6265 if reward, ok := s.xpubRewards[item.XPub]; ok {
@@ -67,5 +70,14 @@ func (s *StandbyNodeReward) Settlement() error {
6770 if len(rewards) == 0 {
6871 return nil
6972 }
70- return s.node.BatchSendBTM(s.cfg.RewardConf.AccountID, s.cfg.RewardConf.Password, rewards)
73+
74+ txID, err := s.node.BatchSendBTM(s.cfg.RewardConf.AccountID, s.cfg.RewardConf.Password, rewards, []byte{})
75+ if err == nil {
76+ log.WithFields(log.Fields{
77+ "tx_hash": txID,
78+ "start_height": s.startHeight,
79+ "end_height": s.endHeight,
80+ }).Info("success on submit consensus reward transaction")
81+ }
82+ return err
7183 }
--- a/toolbar/federation/synchron/mainchain_keeper.go
+++ b/toolbar/federation/synchron/mainchain_keeper.go
@@ -141,6 +141,10 @@ func locateSideChainTx(output *types.TxOutput) string {
141141 return ""
142142 }
143143
144+ if insts[0].Op != vm.OP_FAIL {
145+ return ""
146+ }
147+
144148 return string(insts[1].Data)
145149 }
146150
--- /dev/null
+++ b/toolbar/mergeutxo/merger_utxo.go
@@ -0,0 +1,41 @@
1+package mergeutxo
2+
3+import (
4+ "github.com/vapor/consensus"
5+ "github.com/vapor/protocol/bc"
6+ "github.com/vapor/protocol/bc/types"
7+ "github.com/vapor/toolbar/apinode"
8+)
9+
10+func MergeUTXO(hostPort, accountID, password, address string, amount uint64) ([]string, error) {
11+ actions := []interface{}{}
12+
13+ actions = append(actions, &apinode.ControlAddressAction{
14+ Address: address,
15+ AssetAmount: &bc.AssetAmount{AssetId: consensus.BTMAssetID, Amount: amount},
16+ })
17+
18+ actions = append(actions, &apinode.SpendAccountAction{
19+ AccountID: accountID,
20+ AssetAmount: &bc.AssetAmount{AssetId: consensus.BTMAssetID, Amount: amount},
21+ })
22+
23+ node := apinode.NewNode(hostPort)
24+
25+ tpls, err := node.BuildChainTxs(actions)
26+ if err != nil {
27+ return []string{}, err
28+ }
29+
30+ tpls, err = node.SignTxs(tpls, password)
31+ if err != nil {
32+ return []string{}, err
33+ }
34+
35+ txs := []*types.Tx{}
36+ for _, tpl := range tpls {
37+ txs = append(txs, tpl.Transaction)
38+ }
39+
40+ return node.SubmitTxs(txs)
41+}
--- a/toolbar/vote_reward/settlementvotereward/settlementreward.go
+++ b/toolbar/vote_reward/settlementvotereward/settlementreward.go
@@ -2,6 +2,7 @@ package settlementvotereward
22
33 import (
44 "bytes"
5+ "encoding/json"
56 "math/big"
67
78 "github.com/jinzhu/gorm"
@@ -36,6 +37,13 @@ type SettlementReward struct {
3637 endHeight uint64
3738 }
3839
40+type memo struct {
41+ StartHeight uint64 `json:"start_height"`
42+ EndHeight uint64 `json:"end_height"`
43+ NodePubkey string `json:"node_pubkey"`
44+ RewardRatio uint64 `json:"reward_ratio"`
45+}
46+
3947 func NewSettlementReward(db *gorm.DB, cfg *config.Config, startHeight, endHeight uint64) *SettlementReward {
4048 return &SettlementReward{
4149 db: db,
@@ -85,8 +93,19 @@ func (s *SettlementReward) Settlement() error {
8593 return errNotRewardTx
8694 }
8795
96+ data, err := json.Marshal(&memo{
97+ StartHeight: s.startHeight,
98+ EndHeight: s.endHeight,
99+ NodePubkey: s.rewardCfg.XPub,
100+ RewardRatio: s.rewardCfg.RewardRatio,
101+ })
102+ if err != nil {
103+ return err
104+ }
105+
88106 // send transactions
89- return s.node.BatchSendBTM(s.rewardCfg.AccountID, s.rewardCfg.Password, s.rewards)
107+ _, err = s.node.BatchSendBTM(s.rewardCfg.AccountID, s.rewardCfg.Password, s.rewards, data)
108+ return err
90109 }
91110
92111 func (s *SettlementReward) getStandbyNodeReward(height uint64) (uint64, error) {
旧リポジトリブラウザで表示