├── .travis.yml ├── banner.png ├── process.png ├── architecture.png ├── scripts ├── client-cert.sh └── server-cert.sh ├── trans ├── contracts │ ├── erc20.sol │ ├── wallet.sol │ ├── bank.sol │ ├── oracle.sol │ └── sink.sol ├── withdraw_handler.go ├── disallow_handler.go ├── deposit_handler.go ├── allow_handler.go ├── erc20_handler.go ├── account_rep.go ├── account.go └── watcher.go ├── version.go ├── pb ├── protocol.proto └── protocol.pb.go ├── common ├── backoff.go ├── certutil.go ├── hexutil_test.go ├── hexutil.go └── configutil.go ├── Makefile ├── errors └── errors.go ├── util ├── aes.go ├── encode.go └── util.go ├── config.toml.example ├── log.xml.example ├── localdb ├── rundb.go └── database.go ├── README_CN.md ├── config ├── config.go └── types.go ├── main.go ├── token └── token.go ├── LICENSE ├── README.md ├── node └── node.go └── operate └── operate.go /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | sudo: false 3 | go: 4 | - "1.10.x" -------------------------------------------------------------------------------- /banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boxproject/voucher/HEAD/banner.png -------------------------------------------------------------------------------- /process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boxproject/voucher/HEAD/process.png -------------------------------------------------------------------------------- /architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/boxproject/voucher/HEAD/architecture.png -------------------------------------------------------------------------------- /scripts/client-cert.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # call this script with an email address (valid or not). 3 | # must install openssl before run this script. 4 | # like: 5 | # ./makecert.sh demo@random.com 6 | 7 | FULLPATH=$1 8 | 9 | rm -f ${FULLPATH}/client.pem ${FULLPATH}/client.key 10 | 11 | SUBJECT="/C=CN/ST=Shanghai/L=Earth/O=BOX/OU=DC/CN=box.la/emailAddress" 12 | 13 | EMAIL=${2:-develop@2se.com} 14 | DAYS=${3:-3650} 15 | 16 | openssl req -new -nodes -x509 -out ${FULLPATH}/client.pem -keyout ${FULLPATH}/client.key -days ${DAYS} -subj "${SUBJECT}=${EMAIL}" -------------------------------------------------------------------------------- /scripts/server-cert.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # call this script with an email address (valid or not). 3 | # must install openssl before run this script. 4 | # like: 5 | # ./makecert.sh demo@random.com 6 | 7 | set -e 8 | FULLPATH=$1 9 | 10 | rm -f ${FULLPATH}/server.pem ${FULLPATH}/server.key 11 | SUBJECT="/C=CN/ST=Shanghai/L=Earth/O=BOX/OU=DC/CN=box.la/emailAddress" 12 | 13 | EMAIL=${2:-develop@2se.com} 14 | DAYS=${3:-3650} 15 | 16 | openssl req -new -nodes -x509 -out ${FULLPATH}/server.pem -keyout ${FULLPATH}/server.key -days ${DAYS} -subj "${SUBJECT}=${EMAIL}" -------------------------------------------------------------------------------- /trans/contracts/erc20.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.10; 2 | 3 | contract ERC20 { 4 | function totalSupply() constant public returns (uint); 5 | function balanceOf(address _owner) constant public returns (uint balance); 6 | function transfer(address _to, uint _value) public returns (bool success); 7 | function transferFrom(address _from, address _to, uint _value) public returns (bool success); 8 | function approve(address _spender, uint _value) public returns (bool success); 9 | function allowance(address _owner, address _spender) constant public returns (uint remaining); 10 | event Transfer(address indexed _from, address indexed _to, uint _value); 11 | event Approval(address indexed _owner, address indexed _spender, uint _value); 12 | } -------------------------------------------------------------------------------- /version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package main 15 | 16 | // go build -ldflags "-X main.version=`date -u +.%Y%m%d.%H%M%S`" main.go 17 | var ( 18 | stage = "dev" 19 | version string 20 | gitCommit string 21 | ) -------------------------------------------------------------------------------- /pb/protocol.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package pb; 4 | 5 | service Synchronizer { 6 | 7 | rpc router (RouterRequest) returns (RouterResponse) {} 8 | 9 | rpc heart (HeartRequest) returns (HeartResponse) {} 10 | 11 | rpc listen (stream ListenReq) returns (stream StreamRsp) {} 12 | } 13 | 14 | message RouterRequest { 15 | string routerType = 1; 16 | string routerName = 2; 17 | bytes msg = 3; 18 | } 19 | 20 | message RouterResponse { 21 | string code = 1; 22 | } 23 | 24 | message HeartRequest { 25 | string routerType = 1; 26 | string serverName = 2; 27 | string name = 3; 28 | string ip = 4; 29 | bytes msg = 5; 30 | } 31 | 32 | message HeartResponse { 33 | string code = 1; 34 | } 35 | 36 | // The request listen 37 | message ListenReq { 38 | string serverName = 1; 39 | string name = 2; 40 | string ip = 3; 41 | } 42 | 43 | // The stream rsp 44 | message StreamRsp { 45 | bytes msg = 1; 46 | } -------------------------------------------------------------------------------- /trans/contracts/wallet.sol: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | pragma solidity ^0.4.10; 16 | 17 | import "./bank.sol"; 18 | import "./erc20.sol"; 19 | 20 | contract Wallet { 21 | 22 | event WalletS(address bank); 23 | 24 | Bank bank; 25 | address owner; 26 | 27 | function Wallet(Bank _bank) public { 28 | bank = _bank; 29 | owner = msg.sender; 30 | WalletS(bank); 31 | } 32 | 33 | function() payable { 34 | if (msg.value > 0) { 35 | bank.transfer(msg.value); 36 | } 37 | } 38 | 39 | function transferERC20(ERC20 token, address _to, uint _amount) { 40 | // 只有收款账号才能操作转账 41 | // Only wallet owner can transfer 42 | require(msg.sender == owner); 43 | if (!token.transfer(_to, _amount)) { 44 | revert(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /common/backoff.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | import ( 18 | "math/rand" 19 | "time" 20 | ) 21 | 22 | var DefaultBackoff = Backoff{ 23 | MaxDelay: 120 * time.Second, 24 | baseDelay: 1.0 * time.Second, 25 | factor: 1.6, 26 | jitter: 0.2, 27 | } 28 | 29 | //重连次数记录 30 | var RetryCount = 0 31 | 32 | type Backoff struct { 33 | MaxDelay time.Duration 34 | baseDelay time.Duration 35 | factor float64 36 | jitter float64 37 | } 38 | 39 | func (bc Backoff) Duration(retries int) time.Duration { 40 | if retries == 0 { 41 | return bc.baseDelay 42 | } 43 | 44 | backoff, max := float64(bc.baseDelay), float64(bc.MaxDelay) 45 | for backoff < max && retries > 0 { 46 | backoff *= bc.factor 47 | retries-- 48 | } 49 | 50 | if backoff > max { 51 | backoff = max 52 | } 53 | 54 | backoff *= 1 + bc.jitter*(rand.Float64()*2-1) 55 | if backoff < 0 { 56 | return 0 57 | } 58 | 59 | return time.Duration(backoff) 60 | } 61 | -------------------------------------------------------------------------------- /common/certutil.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package common 15 | 16 | import ( 17 | "crypto/rand" 18 | "io" 19 | "os/exec" 20 | "path" 21 | 22 | "github.com/boxproject/voucher/config" 23 | ) 24 | 25 | func GenCert(cfg *config.Config, certScriptFile string) error { 26 | certPath := path.Join(cfg.Basedir, config.DEFAULTCERTSDIRNAME) 27 | script := path.Join(cfg.Basedir, config.DEFAULTSCRIPTDIRNAME, certScriptFile) 28 | cmd := exec.Command(script, certPath) 29 | if err := cmd.Run(); err != nil { 30 | return err 31 | } 32 | 33 | return nil 34 | } 35 | 36 | func GenSecret(len int) ([]byte, error) { 37 | b := make([]byte, len) 38 | _, err := io.ReadFull(rand.Reader, b) 39 | if err != nil { 40 | return nil, err 41 | } 42 | 43 | return b, nil 44 | } 45 | 46 | func GetServerCert(cfg *config.Config) string { 47 | return path.Join(cfg.Basedir, config.DEFAULTCERTSDIRNAME, "server.pem") 48 | } 49 | 50 | func GetServerKey(cfg *config.Config) string { 51 | return path.Join(cfg.Basedir, config.DEFAULTCERTSDIRNAME, "server.key") 52 | } 53 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Copyright 2018 The box.la Authors All rights reserved. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | APPNAME = "voucher" 16 | DATEVERSION=$(shell date -u +%Y%m%d) 17 | COMMIT_SHA=$(shell git rev-parse --short HEAD) 18 | PWD := $(shell pwd) 19 | CERTSPATH := $(PWD)/build/certs 20 | 21 | .PHONY:all 22 | all:build 23 | 24 | .PHONY:build 25 | build:generate 26 | -rm -rf build 27 | -mkdir -p build/{bin,certs,db,scripts,log} 28 | go generate . 29 | go build -o build/bin/${APPNAME} -ldflags "-X main.version=${DATEVERSION} -X main.gitCommit=${COMMIT}" 30 | cp ./config.toml build/bin/config.toml 31 | cp ./log.xml build/bin/log.xml 32 | cp ./scripts/*.sh build/scripts/ 33 | 34 | .PHONY:generate 35 | generate: 36 | go generate . 37 | 38 | .PHONY:rebuild 39 | rebuild: 40 | -rm -rf build/bin 41 | -mkdir -p build/bin 42 | go build -o build/bin/${APPNAME} -ldflags "-X main.version=${DATEVERSION} -X main.gitCommit=${COMMIT}" 43 | cp ./config.toml build/bin/config.toml 44 | cp ./log.xml build/bin/log.xml 45 | 46 | .PHONY:install 47 | install:build 48 | mv ${APPNAME} ${GOPATH}/bin/ 49 | 50 | .PHONY:clean 51 | clean: 52 | -rm -rf build -------------------------------------------------------------------------------- /errors/errors.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package errors 15 | 16 | import ( 17 | "fmt" 18 | ) 19 | 20 | var ( 21 | NoConfigErr = &VoucherERR{Message: "no specify config"} 22 | NoBasedirErr = &VoucherERR{Message: "the configuration file does not specify the basedir path."} 23 | NoPrivateNodeErr = &VoucherERR{Message: "no private node"} 24 | NoDataErr = &VoucherERR{Message: "no data"} 25 | GenServerCertErr = &VoucherERR{Message: "generate server cert failed."} 26 | GenClientCertErr = &VoucherERR{Message: "generate client cert failed."} 27 | GenServerSecretErr = &VoucherERR{Message: "generate server secret failed."} 28 | GenRandomPassErr = &VoucherERR{Message: "generate random password failed."} 29 | SaveSecretErr = &VoucherERR{Message: "save the secret to db failed."} 30 | SavePassErr = &VoucherERR{Message: "save the password to db failed."} 31 | ) 32 | 33 | type VoucherERR struct { 34 | Message string 35 | Err error 36 | } 37 | 38 | func (e *VoucherERR) Error() string { 39 | if e.Err != nil { 40 | return fmt.Sprintf("%s -> %v", e.Message, e.Err) 41 | } else { 42 | return fmt.Sprintf("%s", e.Message) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /util/aes.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "crypto/aes" 5 | "crypto/cipher" 6 | //"encoding/base64" 7 | "encoding/hex" 8 | log "github.com/alecthomas/log4go" 9 | ) 10 | 11 | //Decrypter 12 | func CBCDecrypter(data, cbcKey []byte) []byte { 13 | if len(data) < aes.BlockSize { 14 | log.Error("data is too short.") 15 | return []byte{} 16 | } 17 | 18 | deDataWithIv, err := hex.DecodeString(string(data)) 19 | if err != nil { 20 | log.Error("hex Decode error:", err) 21 | } 22 | 23 | iv := make([]byte, aes.BlockSize) 24 | copy(iv, deDataWithIv[:aes.BlockSize]) 25 | 26 | c, err := aes.NewCipher(cbcKey) 27 | if err != nil { 28 | log.Error("NewCipher[%s] err: %s", cbcKey, err) 29 | } 30 | decrypter := cipher.NewCBCDecrypter(c, iv) 31 | 32 | deData := deDataWithIv[aes.BlockSize:] 33 | 34 | cbcData := make([]byte, len(deData)) 35 | decrypter.CryptBlocks(cbcData, deData) 36 | 37 | length := len(deData) 38 | 39 | pubkey := cbcData[:length-int(cbcData[length-1])] 40 | return pubkey 41 | 42 | } 43 | 44 | //Encrypter 45 | //func CBCEncrypter(data []byte) []byte { 46 | // key := make([]byte, len([]byte(cbcKey))) 47 | // copy(key, []byte(cbcKey)) 48 | // c, err := aes.NewCipher(key) 49 | // if err != nil { 50 | // fmt.Println("%s: NewCipher(%d bytes) = %s", err) 51 | // } 52 | // encrypter := cipher.NewCBCEncrypter(c, iv) 53 | // 54 | // //规整数据长度 55 | // length := 0 56 | // if len(data)%encrypter.BlockSize() != 0 { 57 | // length = (len(data)/encrypter.BlockSize() + 1) * encrypter.BlockSize() 58 | // } 59 | // src := make([]byte, length) 60 | // copy(src, data) 61 | // 62 | // deData := make([]byte, length) 63 | // encrypter.CryptBlocks(deData, src) 64 | // plaintext := base64.StdEncoding.EncodeToString(deData) 65 | // fmt.Printf("CBCEncrypter-------------------%s\n", plaintext) 66 | // return []byte(plaintext) 67 | //} 68 | -------------------------------------------------------------------------------- /trans/withdraw_handler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package trans 16 | 17 | import ( 18 | "github.com/boxproject/voucher/common" 19 | "github.com/boxproject/voucher/config" 20 | logger "github.com/alecthomas/log4go" 21 | ) 22 | 23 | func newWithdrawHandler(context *HandlerContext) EventHandler { 24 | return &withdrawHandler{context} 25 | } 26 | 27 | type withdrawHandler struct { 28 | context *HandlerContext 29 | } 30 | 31 | func (h *withdrawHandler) Name() common.Hash { 32 | return withdrawEvent 33 | } 34 | 35 | func (h *withdrawHandler) Scan(log *common.EtherLog) error { 36 | logger.Debug("withdrawHandler scan...") 37 | if !common.AddressEquals(log.Address, h.context.ContractAddress) { 38 | return nil 39 | } 40 | 41 | if dataBytes := log.Data; len(dataBytes) > 0 { 42 | address := common.BytesToAddress(dataBytes[:32]).Hex() 43 | amount := common.BytesToHash(dataBytes[32:64]).Big() 44 | if wdHash, err := h.context.Db.Get([]byte(config.WITHDRAW_TX_PRIFIX + log.TxHash.Hex())); err != nil { 45 | logger.Error("get db err: %s", err) 46 | } else { 47 | logger.Debug("withdrawHandler txid: %s, amount: %d", log.TxHash.Hex(), amount) 48 | config.ReportedChan <- &config.GrpcStream{Type: config.GRPC_WITHDRAW_WEB, To: address, Amount: amount, WdHash: common.HexToHash(string(wdHash)),TxHash: log.TxHash.Hex()} 49 | } 50 | } 51 | return nil 52 | } 53 | -------------------------------------------------------------------------------- /config.toml.example: -------------------------------------------------------------------------------- 1 | # 服务基础路径,存放本地数据库以及配置文件等 2 | # 目录结构: 3 | # root/ 4 | # |- bin 5 | # \- voucher 6 | # \- config.toml 7 | # |- certs 8 | # \- server.pem 9 | # \- server.key 10 | # \- client.pem 11 | # \- client.key 12 | # \- dev01.pem 13 | # \- dev01.key 14 | # \- ... 15 | # |- db 16 | # |- scripts 17 | # \- client-cert.sh 18 | # \- server-cert.sh 19 | basedir = "/opt/box/voucher" 20 | 21 | [agentservice] 22 | # 服务名称 23 | name = "voucher" 24 | # 服务别名 25 | alias = "server1" 26 | companionname = "companion" 27 | #http 28 | ipandport = "127.0.0.1:19092" 29 | pattern = "/addkey" 30 | # rpc 31 | rpcapi = "127.0.0.1:50502" 32 | # 如果不填该参数,默认查找 ${basedir}/certs/${name}.pem 33 | clientcert = "/opt/box/voucher/ssl/client.pem" 34 | # 如果不填该参数,默认查找 ${basedir}/certs/${name}.key 35 | clientkey = "/opt/box/voucher/ssl/client.key" 36 | 37 | [secret] 38 | # 服务器端密钥字节数 39 | secretLength = 40 40 | # 初始化时下载客户端证书一次性密码字节数 41 | passLength = 8 42 | # 授权APP的数量 43 | appNum = 2 44 | 45 | [database] 46 | # 如果不提供该参数,默认为 ${basedir}/db 47 | # filePath = "" 48 | # 如果不提供该参数,默认是16 49 | cache = 128 50 | # 如果不提供该参数,默认是16 51 | openFiles = 16 52 | # 如果不提供该参数,默认是 BOX 53 | # prefix = "BOX" 54 | 55 | [log] 56 | # DEBUG/INFO/ERROR/FATAL/WARN 57 | level = "DEBUG" 58 | # json/console 59 | encoding = "console" 60 | # 日志存储位置. 如果不提供,默认为 ${basedir}/log 61 | # logPath = "" 62 | 63 | # 提供手机APP客户端证书下载 64 | [service] 65 | port = 8966 66 | ip = "0.0.0.0" 67 | 68 | # 提供手机端API服务 69 | [api] 70 | port = 9867 71 | ip = "0.0.0.0" 72 | 73 | # 以太坊公链节点 74 | [ethereum] 75 | scheme = "ws://localhost:8546" 76 | delayedBlocks = 0 77 | cursorBlocks = 15900 78 | retries = 10 79 | accountPoolSize = 2 80 | blockNoFilePath = "/opt/box/voucher/blockno.txt" 81 | nonceFilePath = "/opt/box/voucher/nonce.txt" 82 | 83 | # bitcoin node 84 | [bitcoin] 85 | type = "main" 86 | host = "localhost:8332" 87 | rpcuser="user" 88 | rpcpass="pass" 89 | blockNoFilePath = "/opt/box/voucher/btcBlockno.txt" 90 | confirmations=6 91 | -------------------------------------------------------------------------------- /common/hexutil_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | import ( 18 | "github.com/ethereum/go-ethereum/common" 19 | "strings" 20 | "testing" 21 | ) 22 | 23 | type testCase struct { 24 | input string 25 | expect interface{} 26 | result bool 27 | } 28 | 29 | var sTestCases = []testCase{ 30 | { 31 | input: "AllowFlow(bytes32)", 32 | expect: "0x19d544d2bbf9f77ed0ab86140926d49d992616ab5fc07309b84eb6f1d3576d7e", 33 | result: true, 34 | }, 35 | } 36 | 37 | func TestSignEvent(t *testing.T) { 38 | for _, tCase := range sTestCases { 39 | hash := SignEvent(tCase.input) 40 | assertEquals(t, strings.ToLower(hash.String()), tCase.expect.(string), tCase.result) 41 | } 42 | } 43 | 44 | func assertEquals(t *testing.T, a string, b string, result bool) { 45 | if (a == b) != result { 46 | t.Errorf("The result not equals to be expected value.\na: %s\nb: %s", a, b) 47 | t.FailNow() 48 | } 49 | } 50 | 51 | var addrTestCases = []testCase{ 52 | { 53 | input: "0x599d7abdb0a289f85aaca706b55d1b96cc07f349", 54 | expect: []byte{89, 157, 122, 189, 176, 162, 137, 248, 90, 172, 167, 6, 181, 93, 27, 150, 204, 7, 243, 73}, 55 | result: true, 56 | }, 57 | 58 | { 59 | input: "0x599d7abdb0a289f85aaca706b55d1b96cc07f349", 60 | expect: []byte{0}, 61 | result: false, 62 | }, 63 | } 64 | 65 | func TestAddressEquals(t *testing.T) { 66 | for _, tCase := range addrTestCases { 67 | a := common.HexToAddress(tCase.input) 68 | b := common.BytesToAddress(tCase.expect.([]byte)) 69 | //t.Logf("address: %v", b.Bytes()) 70 | result := AddressEquals(a, b) 71 | if result != tCase.result { 72 | t.Errorf("unexpected result. address: %s", tCase.input) 73 | t.FailNow() 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /log.xml.example: -------------------------------------------------------------------------------- 1 | 2 | 3 | stdout 4 | console 5 | 6 | DEBUG 7 | 8 | 9 | file 10 | file 11 | /opt/box/voucher/log/voucher.log 12 | DEBUG 13 | 24 | [%D %T] [%L] (%S) %M 25 | true 26 | 100M 27 | 0K 28 | true 29 | 30 | 31 | xmllog 32 | xml 33 | TRACE 34 | trace.xml 35 | true 36 | 100M 37 | 6K 38 | false 39 | 40 | 41 | donotopen 42 | socket 43 | FINEST 44 | 127.0.0.1:12124 45 | udp 46 | 47 | -------------------------------------------------------------------------------- /trans/contracts/bank.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.10; 2 | 3 | import "./erc20.sol"; 4 | 5 | contract Bank { 6 | // Deposit eth充值事件; eth transfer event 7 | // account 充值的人; account transfer from 8 | // amount 充值数量; amount to transfer 9 | event Deposit(address account, uint256 amount); 10 | event Withdraw(address to, uint256 amount); 11 | event AllowFlow(bytes32 hash); 12 | event DisallowFlow(bytes32 hash); 13 | 14 | address operator; 15 | mapping (bytes32 => bool) signFlow; 16 | 17 | modifier onlyOperator { 18 | require(operator == msg.sender); 19 | _; 20 | } 21 | 22 | modifier onlySignflow(bytes32 sf) { 23 | require(signFlow[sf]); 24 | _; 25 | } 26 | 27 | function Bank() public { 28 | // 该帐号拥有超级超级权限; people who created the contract own it. 29 | operator = msg.sender; 30 | } 31 | 32 | // 任何人都可以向这个合约充值 33 | // 记录日志 谁充值的,充到哪个钱包,充了多少钱 34 | // Anyone can transfer fund to bank 35 | function() payable public { 36 | if (msg.value > 0) { 37 | Deposit(msg.sender, msg.value); 38 | } 39 | } 40 | 41 | // eth提现 42 | // eth withdrawl, only owner with valid signature can withdraw 43 | function withdraw(address to, uint amount, bytes32 sf) public onlyOperator onlySignflow(sf) { 44 | require(to != 0 && amount > 0 && this.balance >= amount); 45 | to.transfer(amount); 46 | Withdraw(to, amount); 47 | } 48 | 49 | function transferERC20(ERC20 token, address _to, uint _amount, bytes32 sf) public onlyOperator onlySignflow(sf) { 50 | require(_to != 0 && _amount > 0); 51 | // 正确性交由erc20合约来决定 52 | if (!token.transfer(_to, _amount)) { 53 | revert(); 54 | } 55 | } 56 | 57 | function allow(bytes32 hash) public onlyOperator returns(bool) { 58 | if (signFlow[hash]) { 59 | return false; 60 | } 61 | 62 | signFlow[hash] = true; 63 | AllowFlow(hash); 64 | return true; 65 | } 66 | 67 | function disallow(bytes32 hash) public onlyOperator returns(bool) { 68 | if (signFlow[hash]) { 69 | delete signFlow[hash]; 70 | return true; 71 | } 72 | 73 | DisallowFlow(hash); 74 | return false; 75 | } 76 | 77 | function available(bytes32 sf) constant public returns(bool) { 78 | return signFlow[sf]; 79 | } 80 | } -------------------------------------------------------------------------------- /common/hexutil.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | import ( 18 | "bytes" 19 | "encoding/hex" 20 | 21 | "github.com/ethereum/go-ethereum/common" 22 | "github.com/ethereum/go-ethereum/core/types" 23 | "github.com/ethereum/go-ethereum/crypto" 24 | "github.com/ethereum/go-ethereum/crypto/sha3" 25 | ) 26 | 27 | type ( 28 | Hash = common.Hash 29 | Address = common.Address 30 | EtherLog = types.Log 31 | ) 32 | const AddressLength = common.AddressLength 33 | 34 | 35 | func BytesToHex(data []byte, with0x bool) string { 36 | unchecksummed := hex.EncodeToString(data) 37 | result := []byte(unchecksummed) 38 | hash := crypto.Keccak256(result) 39 | 40 | for i := 0; i < len(result); i++ { 41 | hashByte := hash[i/2] 42 | if i%2 == 0 { 43 | hashByte = hashByte >> 4 44 | } else { 45 | hashByte &= 0xf 46 | } 47 | if result[i] > '9' && hashByte > 7 { 48 | result[i] -= 32 49 | } 50 | } 51 | 52 | if with0x { 53 | return "0x" + string(result) 54 | } 55 | 56 | return string(result) 57 | } 58 | 59 | func BytesToAddress(data []byte) common.Address { 60 | return common.BytesToAddress(data) 61 | } 62 | 63 | func HexToAddress(data string) common.Address { 64 | return common.HexToAddress(data) 65 | } 66 | 67 | func BytesToHash(data []byte) common.Hash { 68 | return common.BytesToHash(data) 69 | } 70 | 71 | func HexToBytes(hexstr string) ([]byte, error) { 72 | return hex.DecodeString(hexstr) 73 | } 74 | 75 | func HexToHash(hexstr string) common.Hash { 76 | return common.HexToHash(hexstr) 77 | } 78 | 79 | func SignEvent(f string) Hash { 80 | data := []byte(f) 81 | d := sha3.NewKeccak256() 82 | d.Write(data) 83 | return common.BytesToHash(d.Sum(nil)) 84 | } 85 | 86 | func AddressEquals(a, b Address) bool { 87 | return bytes.Equal(a.Bytes(), b.Bytes()) 88 | } 89 | 90 | func Byte2Byte32(src []byte) [32]byte { 91 | var obj [32]byte 92 | copy(obj[:], src[:32]) 93 | return obj 94 | } 95 | -------------------------------------------------------------------------------- /trans/disallow_handler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package trans 16 | 17 | import ( 18 | "github.com/boxproject/voucher/common" 19 | "github.com/boxproject/voucher/config" 20 | logger "github.com/alecthomas/log4go" 21 | ) 22 | 23 | //func newDisallowHandler(context *HandlerContext) EventHandler { 24 | // return &disallowHandler{context} 25 | //} 26 | 27 | type disallowHandler struct { 28 | context *HandlerContext 29 | flag bool 30 | name common.Hash 31 | } 32 | 33 | func (handler *disallowHandler) Name() common.Hash { 34 | return disallowEvent 35 | } 36 | 37 | func newDisallowHandler(cfg *HandlerContext) EventHandler { 38 | return &disallowHandler{cfg, false, disallowEvent} 39 | } 40 | 41 | func (h *disallowHandler) Scan(log *common.EtherLog) error { 42 | logger.Debug("disallowHandler.......") 43 | if !common.AddressEquals(log.Address, h.context.ContractAddress) { 44 | return nil 45 | } 46 | 47 | if dataBytes := log.Data; len(dataBytes) > 0 { 48 | hash := common.BytesToHash(dataBytes[:32]) 49 | logger.Debug("disallowHandler, hash: %s", hash) 50 | if _, err := h.context.Db.Get([]byte(config.HASH_LIST_PRIFIX + hash.Hex())); err != nil { 51 | logger.Error("disallowHandler load content err:%v", err) 52 | } else { 53 | //供查询使用 54 | //hashModel := &config.GrpcStream{} 55 | //if err := json.Unmarshal(hashByte, hashModel); err != nil { 56 | // logger.Error("disallowHandler json marshal error:%v", err) 57 | // return err 58 | //} 59 | config.ReportedChan <- &config.GrpcStream{Type: config.GRPC_HASH_ENABLE_WEB, Hash: hash} 60 | //hashModel.Status = config.HASH_STATUS_DISABLE 61 | //if newHashByte, err := json.Marshal(hashModel); err != nil { 62 | // logger.Error("disallowHandler json marshal error:%v", err) 63 | //} else { 64 | // h.context.State.Save([]byte(config.HASH_LIST_PRIFIX+hash), newHashByte) 65 | // hashHandler.HashList(h.context.Db) 66 | //} 67 | 68 | } 69 | } 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /localdb/rundb.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package localdb 15 | 16 | import ( 17 | "github.com/boxproject/voucher/common" 18 | "github.com/boxproject/voucher/config" 19 | "github.com/boxproject/voucher/errors" 20 | ) 21 | 22 | func RunDatabase(cfg *config.Config) (Database, error) { 23 | db, err := NewDatabase(&cfg.DB) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | _, err = db.Get(config.INIT.Bytes()) 29 | if err != nil { 30 | if errors.NoDataErr != err { 31 | return nil, err 32 | } 33 | 34 | if err = prepare(db, cfg); err != nil { 35 | return nil, err 36 | } 37 | } 38 | 39 | return db, nil 40 | } 41 | 42 | func prepare(db Database, cfg *config.Config) (err error) { 43 | // 如果没有初始化过,则需要产生证书 44 | if err = common.GenCert(cfg, config.SVRCERTSCRIPT); err != nil { 45 | errors.GenServerCertErr.Err = err 46 | return errors.GenServerCertErr 47 | } 48 | 49 | if err = common.GenCert(cfg, config.CLTCERTSCRIPT); err != nil { 50 | errors.GenClientCertErr.Err = err 51 | return errors.GenClientCertErr 52 | } 53 | 54 | var ( 55 | secret []byte 56 | pass []byte 57 | ) 58 | 59 | // 产生服务器端密钥 60 | // Generate server side private key 61 | if secret, err = common.GenSecret(cfg.Secret.SecretLength); err != nil { 62 | errors.GenServerSecretErr.Err = err 63 | return errors.GenServerSecretErr.Err 64 | } 65 | 66 | // 产生随机密钥 67 | // Generate random private key 68 | if pass, err = common.GenSecret(cfg.Secret.PassLength); err != nil { 69 | errors.GenRandomPassErr.Err = err 70 | return errors.GenRandomPassErr 71 | } 72 | 73 | if err = db.Put(config.SECRET.Bytes(), secret); err != nil { 74 | errors.SaveSecretErr.Err = err 75 | return errors.SaveSecretErr 76 | } 77 | 78 | if err = db.Put(config.PASS.Bytes(), pass); err != nil { 79 | errors.SavePassErr.Err = err 80 | return errors.SavePassErr 81 | } 82 | 83 | if err = db.Put(config.INIT.Bytes(), config.Inited.Bytes()); err != nil { 84 | return err 85 | } 86 | 87 | return nil 88 | } 89 | -------------------------------------------------------------------------------- /trans/deposit_handler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package trans 16 | 17 | import ( 18 | "context" 19 | logger "github.com/alecthomas/log4go" 20 | "github.com/boxproject/voucher/common" 21 | "github.com/boxproject/voucher/config" 22 | "github.com/ethereum/go-ethereum/core/types" 23 | "github.com/ethereum/go-ethereum/ethclient" 24 | "math/big" 25 | ) 26 | 27 | func newDepositHandler(ctx *HandlerContext, cli *ethclient.Client) EventHandler { 28 | return &depositHandler{ctx, cli} 29 | } 30 | 31 | type depositHandler struct { 32 | context *HandlerContext 33 | client *ethclient.Client 34 | } 35 | 36 | func (h *depositHandler) Name() common.Hash { 37 | return depositEvent 38 | } 39 | 40 | func (h *depositHandler) Scan(log *common.EtherLog) error { 41 | logger.Debug("depositHandler scan...") 42 | if !common.AddressEquals(log.Address, h.context.ContractAddress) { 43 | return nil 44 | } 45 | 46 | var from common.Address 47 | var toTx string 48 | 49 | block, err := h.client.BlockByNumber(context.Background(), big.NewInt(int64(log.BlockNumber))) 50 | if err != nil { 51 | logger.Error("blockByNumber error: %s", err) 52 | } 53 | 54 | for _, tx := range block.Transactions() { 55 | if tx.Hash().Hex() == log.TxHash.Hex() { 56 | signer := types.NewEIP155Signer(tx.ChainId()) 57 | from, err = signer.Sender(tx) 58 | if err != nil { 59 | logger.Error("blockByNumber getFrom err: %s", err) 60 | } 61 | toTx = tx.To().Hex() 62 | } 63 | } 64 | 65 | if dataBytes := log.Data; len(dataBytes) > 0 { 66 | to := common.BytesToAddress(dataBytes[:32]).Hex() 67 | if from.Hex() == to { //直充 68 | to = toTx 69 | } 70 | amount := common.BytesToHash(dataBytes[32:64]).Big() 71 | logger.Debug("depositHandler from: %s, to: %s, amount: %d", from.Hex(), to, amount) 72 | config.ReportedChan <- &config.GrpcStream{Type: config.GRPC_DEPOSIT_WEB, From: from.Hex(), To: to, Amount: amount, TxHash: log.TxHash.Hex(), Category: big.NewInt(config.CATEGORY_ETH)} 73 | } 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /trans/allow_handler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package trans 16 | 17 | import ( 18 | logger "github.com/alecthomas/log4go" 19 | "github.com/boxproject/voucher/common" 20 | "github.com/boxproject/voucher/config" 21 | ) 22 | 23 | func newAllowHandler(ctx *HandlerContext) EventHandler { 24 | return &allowHandler{ctx, true, allowEvent} 25 | } 26 | 27 | type allowHandler struct { 28 | context *HandlerContext 29 | flag bool 30 | name common.Hash 31 | } 32 | 33 | func (h *allowHandler) Name() common.Hash { 34 | return h.name 35 | } 36 | 37 | func (h *allowHandler) Scan(log *common.EtherLog) error { 38 | logger.Debug("allowHandler.......") 39 | if !common.AddressEquals(log.Address, h.context.ContractAddress) { 40 | return nil 41 | } 42 | 43 | // TODO 直接拿到data数据就是公链成功的签名流模版哈希 44 | //log.Data 45 | //log.BlockNumber 46 | //log.BlockHash 47 | //log.WDHash 48 | //_, err := h.context.State.Load(nil) 49 | //if err != nil { 50 | // if err == errors.NoDataErr { 51 | // logger.Warnf("the hash: %s not in local database.", common.BytesToHex(log.Data, true)) 52 | // return nil 53 | // } 54 | // 55 | // return err 56 | //} 57 | 58 | if dataBytes := log.Data; len(dataBytes) > 0 { 59 | hash := common.BytesToHash(dataBytes[:32]) 60 | logger.Debug("allowHandler hash: %s", hash.Hex()) 61 | if _, err := h.context.Db.Get([]byte(config.HASH_LIST_PRIFIX + hash.Hex())); err != nil { 62 | logger.Error("allowHandler load content err:%v", err) 63 | } else { 64 | //供查询使用 65 | //hashModel := &config.GrpcStream{} 66 | //if err := json.Unmarshal(hashByte, hashModel); err != nil { 67 | // logger.Error("allowHandler json marshal error:%v", err) 68 | // return err 69 | //} 70 | //hashModel.Status = config.HASH_STATUS_ENABLE 71 | config.ReportedChan <- &config.GrpcStream{Type: config.GRPC_HASH_ENABLE_WEB, Hash: hash} 72 | //if newHashByte, err := json.Marshal(hashModel); err != nil { 73 | // logger.Error("allowHandler json marshal error:%v", err) 74 | //} else { 75 | // //h.context.State.Save([]byte(config.HASH_LIST_PRIFIX+hash.Hex()), newHashByte) 76 | // hashHandler.HashList() 77 | //} 78 | } 79 | } 80 | return nil 81 | } 82 | -------------------------------------------------------------------------------- /common/configutil.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package common 15 | 16 | import ( 17 | "github.com/boxproject/voucher/config" 18 | "github.com/boxproject/voucher/errors" 19 | "github.com/BurntSushi/toml" 20 | "os" 21 | "os/exec" 22 | "path" 23 | "path/filepath" 24 | "strings" 25 | ) 26 | 27 | func LoadConfig(filePath string) (*config.Config, error) { 28 | var err error 29 | if filePath == "" { 30 | if filePath, err = tryFindConfig(); err != nil { 31 | return nil, err 32 | } 33 | } 34 | 35 | var c config.Config 36 | if _, err := toml.DecodeFile(filePath, &c); err != nil { 37 | return nil, err 38 | } 39 | 40 | if c.Basedir == "" { 41 | return nil, errors.NoBasedirErr 42 | } 43 | 44 | if c.EthereumConfig.GasLimit > 0 {//修改默认gaslimit 45 | config.DefPubEthGasLimit = c.EthereumConfig.GasLimit 46 | } 47 | 48 | return setDefault(&c), nil 49 | } 50 | 51 | func setDefault(cfg *config.Config) *config.Config { 52 | if cfg.Secret.SecretLength <= 0 { 53 | cfg.Secret.SecretLength = config.DEFAULTSECRETLEN 54 | } 55 | 56 | if cfg.Secret.PassLength <= 0 { 57 | cfg.Secret.PassLength = config.DEFAULTPASSLENT 58 | } 59 | 60 | if cfg.Secret.AppNum <= 0 { 61 | cfg.Secret.AppNum = config.DEFAULTAPPNUM 62 | } 63 | 64 | if cfg.DB.Filepath == "" { 65 | cfg.DB.Filepath = path.Join(cfg.Basedir, config.DEFAULTDBDIRNAME) 66 | } 67 | 68 | if cfg.DB.Prefix == "" { 69 | cfg.DB.Prefix = config.DEFAULTKEYPREFIX 70 | } 71 | 72 | if cfg.LogConfig.LogPath == "" { 73 | cfg.LogConfig.LogPath = path.Join(cfg.Basedir, config.DEFAULTLOGDIRNAME) 74 | } 75 | 76 | cfg.LogConfig.Encoding = strings.ToLower(cfg.LogConfig.Encoding) 77 | 78 | return cfg 79 | } 80 | 81 | func tryFindConfig() (string, error) { 82 | self, err := exec.LookPath(os.Args[0]) 83 | if err != nil { 84 | return "", err 85 | } 86 | 87 | p, err := filepath.Abs(self) 88 | if err != nil { 89 | return "", err 90 | } 91 | 92 | filePath := path.Join(path.Dir(p), config.DEFAULTCONFIGFILENAME) 93 | 94 | _, err = os.Stat(filePath) 95 | if err == nil { 96 | return filePath, nil 97 | } 98 | 99 | if os.IsNotExist(err) { 100 | return "", errors.NoConfigErr 101 | } 102 | 103 | return "", err 104 | } 105 | -------------------------------------------------------------------------------- /trans/erc20_handler.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package trans 16 | 17 | import ( 18 | log "github.com/alecthomas/log4go" 19 | "github.com/boxproject/voucher/common" 20 | "github.com/boxproject/voucher/config" 21 | "github.com/boxproject/voucher/token" 22 | "github.com/ethereum/go-ethereum/ethclient" 23 | "math/big" 24 | "strings" 25 | ) 26 | 27 | func newERC20Handler(cfg *HandlerContext, cli *ethclient.Client) EventHandler { 28 | return &erc20Handler{cfg, cli} 29 | } 30 | 31 | type erc20Handler struct { 32 | context *HandlerContext 33 | client *ethclient.Client 34 | } 35 | 36 | func (e *erc20Handler) Name() common.Hash { 37 | return erc20Event 38 | } 39 | 40 | func (e *erc20Handler) Scan(eLog *common.EtherLog) error { 41 | length := len(eLog.Topics) 42 | //log.Debug("~~~~~ ERC20 event ~~~~~ topic length: %d", length) 43 | if length == 3 { 44 | 45 | // May be ERC20 46 | erc20Addr := eLog.Address.Hex() 47 | // 检测erc20addr 是否是合法的需要监控的地址 48 | isErc20, category := scanAddress(erc20Addr) 49 | if !isErc20 { 50 | return nil 51 | } 52 | // erc20 存在并被监管 53 | 54 | // 检查是否充值成功 55 | // to 是否是监控的交易所的合约地址 56 | // 如果是,则需要关联to 对应的 uuid 57 | // 并登记一笔充值成功的记录 58 | from := common.BytesToAddress(eLog.Topics[1].Bytes()[:32]).Hex() 59 | to := common.BytesToAddress(eLog.Topics[2].Bytes()[:32]).Hex() 60 | 61 | // amount 62 | amount := common.BytesToHash(eLog.Data[:32]).Big() 63 | 64 | log.Info("erc20Handler category: %d, from: %s, to: %s, amount: %d", category, from, to, amount) 65 | 66 | //if wallet, err := NewWallet(common.HexToAddress(to), e.client); err != nil { 67 | // log.Error("wallet error: %s", err) 68 | //} else { 69 | // opts := NewKeyedTransactor() 70 | // wallet.TransferERC20(opts, eLog.Address, e.context.ContractAddress, amount) 71 | //} 72 | 73 | contractAddr := config.RealTimeStatus.ContractAddress 74 | 75 | if from == contractAddr { //提现 76 | if wdHash, err := e.context.Db.Get([]byte(config.WITHDRAW_TX_PRIFIX + eLog.TxHash.Hex())); err != nil { 77 | log.Error("get db err: %s", err) 78 | } else { 79 | config.ReportedChan <- &config.GrpcStream{Type: config.GRPC_WITHDRAW_WEB, From: from, To: to, Amount: amount, TxHash: eLog.TxHash.Hex(), WdHash: common.HexToHash(string(wdHash)), Category: big.NewInt(category)} 80 | } 81 | } else if to == contractAddr { //充值 82 | config.ReportedChan <- &config.GrpcStream{Type: config.GRPC_DEPOSIT_WEB, From: from, To: to, Amount: amount, TxHash: eLog.TxHash.Hex(), Category: big.NewInt(category)} 83 | } 84 | } 85 | return nil 86 | } 87 | 88 | func scanAddress(addr string) (bool, int64) { 89 | if ethToken := token.GetTokenByAddr(strings.ToLower(addr)); ethToken != nil { 90 | return true, ethToken.Category 91 | } 92 | return false, 0 93 | } 94 | -------------------------------------------------------------------------------- /trans/contracts/oracle.sol: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | pragma solidity ^0.4.10; 15 | 16 | contract Oracle { 17 | struct Node { 18 | // 节点签名人 19 | // node signer 20 | address signer; 21 | // 是否被启用,可被停止 22 | // Node enabled or not 23 | bool enabled; 24 | } 25 | 26 | // 这个帐号初始时可以在一个节点上生成,生成后要在别的节点管理时,需要把该帐号挪过去。 27 | // 需要开发管理节点的接口来控制本合约 28 | // This account can be created on one node, if we want to manage it on other nodes, we have to move it to that node. 29 | // Need api to control this contract 30 | address public boss; 31 | 32 | mapping (address => uint) nodeId; 33 | 34 | Node[] nodes; 35 | 36 | modifier onlyBoss { 37 | require(boss == msg.sender); 38 | _; 39 | } 40 | 41 | function Oracle() public { 42 | boss = msg.sender; 43 | } 44 | 45 | // 添加节点系统授权人,这个角色负责打包数据 46 | // Add signer, to sign the data 47 | function addSigner(address signer) onlyBoss public { 48 | // init the array if empty 49 | if (nodes.length == 0) { 50 | nodes.length = 1; 51 | nodeId[0] = 0; 52 | nodes[0] = Node({signer:0, enabled: false}); 53 | } 54 | 55 | uint id = nodeId[signer]; 56 | if (id == 0) { 57 | id = nodes.length; 58 | nodeId[signer] = nodes.length; 59 | nodes.length++; 60 | } 61 | 62 | Node storage node = nodes[id]; 63 | node.signer = signer; 64 | node.enabled = true; 65 | nodeId[signer] = id; 66 | } 67 | 68 | function disableSigner(address signer) onlyBoss public returns(bool) { 69 | uint id = nodeId[signer]; 70 | if (id != 0) { 71 | nodes[id].enabled = false; 72 | return true; 73 | } 74 | 75 | return false; 76 | } 77 | 78 | function count() constant public returns(uint) { 79 | return nodes.length; 80 | } 81 | 82 | function indexOf(uint idx) constant public returns(address, bool) { 83 | address addr = nodes[idx].signer; 84 | bool enabled = nodes[idx].enabled; 85 | return (addr, enabled); 86 | } 87 | 88 | function totalEnabledNodes() constant public returns (uint) { 89 | uint len = nodes.length; 90 | uint i; 91 | for (i = 1; i < nodes.length; i++) { 92 | if (!nodes[i].enabled) { 93 | len -= 1; 94 | } 95 | } 96 | return len - 1; 97 | } 98 | 99 | function isSigner(address signer) constant public returns(bool) { 100 | return (nodeId[signer] != 0 && nodes[nodeId[signer]].enabled); 101 | } 102 | } -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | # BOX产品介绍 2 | **机构掀起数字资产投资狂潮**。 全球 VC 数字资产投资规模由2012年2百万美元飙升至目前34亿美元,期间增幅达1672.5倍。其中,VC数字资产投资累计融资规模由2012年3起激增至2018年182起。在资产数字化的大浪潮下,预计未来将有更多的机构参与进来。 3 | 4 | **机构数字资产管理高度依赖个人钱包,引发诸多问题**。 目前企业数字资产管理还依赖于Imtoken等个人钱包,该类钱包目前连私钥的安全都无法完全保证,更别提做到有效防范内鬼。合规方面,机构使用个人钱包管理数字货币无法实现规范化的财务管理流程,导致账目混乱。此外,在缺乏指导与监督的情况下,数字货币打错地址,资金无法追回的意外时有发生。 5 | 6 | **出于数字货币安全层面的考虑,机构数字货币管理逐渐向冷钱包转移**。由于冷钱包的拖网特性,其安全等级要高于热钱包。为了确保冷钱包的绝对安全,通常情况下都要将冷钱包保存于银行保险库 ,这为机构的高频率打款转账造成了极大的不便。此外,冷钱包的操作方法对非专业人士而言较为复杂,致使冷钱包使用效率低下。 7 | 8 | **钱包嵌入多重签名技术实现私钥多人共管,但便捷性等方面仍存缺陷**。 钱包嵌套多重签名技术一定程度实现了机构多人共管私钥的目的。但多重签名目前依旧存在如下问题:首先,主链需要支持多重签名技术,主链钱包也需支持多重签名,然而现阶段许多主链并不支持多重签名技术;在机构普遍持有多种数字货币的情况下,多重签名给机构数字资产的管理造成一定阻碍。此外,不同主链导入多重签名技术也不同,造成了数字资产管理的极大的不便携性。更为致命的是,有些主链的多重签名会有漏洞,比如以太坊的PARITY事件。综合看,目前市面亟需一款专门针对企业级的数字资产管理的综合解决方案,为企业数字资产管理保驾护航。 9 | 10 | **BOX的横空出世,为企业数字资产安全的管理提供了一套综合解决方案**。 BOX(企业通证保险箱)是一套基于区块链技术的数字资产安全解决方案。BOX以区块链、密码学、通信安全等领域的公理性技术为依托,实现技术间的无缝衔接,从根本上解决了私钥被盗取和指令被篡改的行业痛疾。整套BOX系统的安全高效主要通过私钥、审批流、以及通信安全来实现。 11 | 12 | **一把私钥管理几乎所有数字货币,动态口令方式实现多人共管,一键启停确保私钥安全**。 BOX系统实现了一把私钥管理所有数字货币的目标,理论上所有支持ECDSA的椭圆形曲线算法的公链均可以用一把私钥来管理,在这一点上,BOX系统较多重签名在便携性方面有了巨大的提升。同时,BOX采用多人多口令方式,在签名机内通过算法将口令动态生成私钥,再用私钥去生成公钥。拥有最高权限的合伙人均只掌握动态口令的一部分,实现了私钥多人共管的目的。在存储方面,我们将私钥放在了签名机内存里,不做任何持久化存储,内存里的私钥极难被捕捉。我们又将私钥内存位两端锁住,防止旁道攻击。一旦发生断电或启动等情况,BOX签名机的守护进程将自动清内存关机,这样私钥就消失了。因此即使在签名机裸奔的情况下,从BOX系统得到私钥的机会也几乎为零。拥有最高权限的合伙人可以通过依次输入口令的方式即刻再次恢复原有私钥。此外,为了防止某个合伙人出现意外无法履行职责,我们推荐在设置口令时,通过打印口令备份封存到银行保险箱,并且将其中一份备份交给三方保存。与冷钱包不同的是,平时不需要去动这个备份,仅当合伙人发生意外时才会经由董事会投票决定是否启用口令备份。 13 | 14 | ![整体架构图](https://github.com/boxproject/voucher/blob/master/architecture.png) 15 | 16 | ![审批流程示意](https://github.com/boxproject/voucher/blob/master/process.png) 17 | 18 | **自定义审批流模版,利用区块链不可篡改的特性存入私链中。统一对公账户,高效管理数字资产**。 审批流的模版由企业自主定义,内容主要包括审批层级、发起(审批)人、每一层级最少通过人数、员工的公钥(地址)。将自定义好的模版以及模版的哈希值保存在私链上,以确保审批流的不可篡改性,再由私钥APP确认其有效性。当员工发起审批流时,首先匹对员工私钥以及私钥所对应的地址,确保无误后,通过私链上的伴生程序,将审批流程与保存在私链上的审批流模版一一匹对,若完全符合审批流模版,再通过代理(私钥app接口)流向签名机,在调动签名机里的私钥前,与保存在公链上的审批流模版哈希值进行二次匹配(目前公链二次匹配只支持以太坊),确保无误后,将调动签名机里的私钥进行打款转账。除此之外,BOX为每一个企业提供了统一的对公账户,这样能把企业的资产放在一个账户下进行有效的管理,所有的数字资产的交易都将通过该账户进行交易,防止各种公账私账不分的情况的发生。多数字资产集中管理和数字资产交易的明细这两点,主要体现了BOX系统能够实现多种数字资产的统一管理,并为这些数字资产的交易提供明细记录,这样可以帮助企业进行的账务记录,并为审计提供依据,企业的管理者也可以通过这些记录非常清楚的了解企业资产情况,并进行相应的分析。这些都对于数字资产的有效管理非常重要。 19 | 20 | 硬件方面,部署一套BOX系统需要至少 3(2n+1)台云服务器,每一个云服务器作为一个节点,构建一条私链。一台苹果 MACBOOK 作为签名机,采用苹果电脑作为签名机是考虑到苹果系统较Windows更为安全。同时需要若干台iphone,装载私钥APP & 员工APP。 21 | 22 | **一站式综合解决方案,BOX系统将为投资机构、交易所等数字资产安全保驾护航**。 目前BOX最适合的企业是区块链投资机构、有区块链审计风控需求的企业、交易平台等。区块链投资机构平时会频繁转账、收款,用冷钱包不方便,个人钱包又不适用于多位合伙人企业进行清晰的资产划分,BOX则是多人共管一把私钥,可以很好的解决这一难题,操作起来也比冷钱包便利很多。总体来看,BOX系统通过一系列的优化流程,大幅提高可用性及便捷性,同时达到与冷钱包相当的安全程度。 23 | 24 | BOX代码已上传至全球最大的技术开源社区GitHub,待与诸君共同构建更健康、安全的行业大环境。 任何个体、企业均可无偿使用、部署该系统。为激励 BOX 0.1.0 版本的首批贡献者,BOX团队启动了“BOX超级合伙人“计划,截至目前已有30余家机构与BOX基金会签署了合作意向书。未来BOX团队将着重社群建设以及系统的可扩展性,与众多机构共同构建更健康、安全的行业大环境。BOX在github上的开源代码有多个仓库,提供了整套可部署的解决方案。包括 agent - 私钥APP管理服务端,box-Authorizer - 私钥APP客户端,boxguard - 签名机守护程序,voucher - 接入层,companion - 私链端伴生程序,box-Staff-Manager - 员工APP客户端, box-appServer - 员工APP服务器端。 25 | 26 | # 核心功能 27 | 本程序主要功能点有五个: 28 | 29 | 与代理服务通信 30 | 实时上报签名机状态信息 31 | 接受私钥app的请求,完成离线签名,发布智能合约等 32 | 只接受经过私链确认的审批流以及转账,并进行RSA验签,才认可本次提交的数据正确性。 33 | 监控以太坊公链event log,确认审批流操作,以及充值提现记录等,并将结果通知给代理 34 | ## 使用步骤: 35 | 36 | 初始化。首次使用make build命令 37 | ```sh 38 | make build 39 | cp config.toml.example config.toml 40 | cp log.xml.example log.xml 41 | ``` 42 | 再次make rebuild命令,切记,build命令将清除所有数据; 43 | ```sh 44 | make rebuild 45 | ``` 46 | 将连接代理的地址、端口以及ssl公钥以及证书写入到config.json配置文件中对应的参数中 47 | 将eth公链、比特币公链分别写入到config.json配置文件中对应的参数中 48 | 启动本程序 49 | ## 命令行用法: 50 | 51 | ```bash 52 | ➜ ./voucher 53 | ``` 54 | 55 | ## 功能说明: 56 | 57 | * 故障恢复 58 | 59 | * 根据配置设定出块数据确认,以防止因分叉导致确认数目出错。 60 | 61 | * 审批流创建及确认监控 62 | 63 | * 充值、转账审批监控 64 | -------------------------------------------------------------------------------- /util/encode.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | //"crypto" 5 | "crypto/rsa" 6 | "crypto/sha256" 7 | "github.com/boxproject/voucher/config" 8 | "github.com/boxproject/voucher/localdb" 9 | //"encoding/base64" 10 | "crypto" 11 | "crypto/x509" 12 | "encoding/base64" 13 | log "github.com/alecthomas/log4go" 14 | ) 15 | 16 | //var decrypted string 17 | //var privateKey, publicKey []byte 18 | 19 | //func initi() { 20 | // var err error 21 | // flag.StringVar(&decrypted, "d", "", "加密过的数据") 22 | // flag.Parse() 23 | // publicKey, err = ioutil.ReadFile("public.pem") 24 | // if err != nil { 25 | // os.Exit(-1) 26 | // } 27 | // privateKey, err = ioutil.ReadFile("private.pem") 28 | // if err != nil { 29 | // os.Exit(-1) 30 | // } 31 | //} 32 | 33 | //func main() { 34 | // var data []byte 35 | // var err error 36 | // data, err = RsaEncrypt([]byte("fyxichen")) 37 | // if err != nil { 38 | // panic(err) 39 | // } 40 | // fmt.Println("RsaEncrypt...", data) 41 | // origData, err := RsaDecrypt(data) 42 | // if err != nil { 43 | // panic(err) 44 | // } 45 | // fmt.Println(string(origData)) 46 | //} 47 | 48 | //func getPublicKey(db localdb.Database, appId string) { 49 | // if keyBytes, err := db.Get([]byte(config.APP_KEY_PRIFIX + appId)); err != nil { 50 | // publicKey = keyBytes 51 | // } 52 | //} 53 | 54 | //// 加密 55 | //func RsaEncrypt(origData []byte) ([]byte, error) { 56 | // block, _ := pem.Decode(publicKey) 57 | // if block == nil { 58 | // return nil, errors.New("public key error") 59 | // } 60 | // pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) 61 | // if err != nil { 62 | // return nil, err 63 | // } 64 | // pub := pubInterface.(*rsa.PublicKey) 65 | // return rsa.EncryptPKCS1v15(rand.Reader, pub, origData) 66 | //} 67 | 68 | // 解密 69 | //func RsaDecrypt(ciphertext []byte) ([]byte, error) { 70 | // block, _ := pem.Decode(privateKey) 71 | // if block == nil { 72 | // return nil, errors.New("private key error!") 73 | // } 74 | // priv, err := x509.ParsePKCS1PrivateKey(block.Bytes) 75 | // if err != nil { 76 | // return nil, err 77 | // } 78 | // return rsa.DecryptPKCS1v15(rand.Reader, priv, ciphertext) 79 | //} 80 | 81 | //公钥验证 82 | func RsaSignVer(data []byte, signature []byte, db localdb.Database, appId string) error { 83 | if publicKey, err := db.Get([]byte(config.APP_KEY_PRIFIX + appId)); err != nil { 84 | return err 85 | } else { 86 | deSignData, err := base64.StdEncoding.DecodeString(string(signature)) 87 | if err != nil { 88 | log.Error("base64 Decode error:", err) 89 | } 90 | hashed := sha256.Sum256(data) 91 | keybase64, err := base64.RawStdEncoding.DecodeString(string(publicKey)) 92 | if err != nil { 93 | log.Error("base64 Decode error:", err) 94 | } 95 | pub, err := x509.ParsePKCS1PublicKey(keybase64) 96 | if err != nil { 97 | log.Error("ParsePKCS1PublicKey err: %s", err) 98 | return err 99 | } 100 | 101 | //block, _ := pem.Decode(publicKey) 102 | //if block == nil { 103 | // return errors.New("public key error") 104 | //} 105 | //// 解析公钥 106 | //pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes) 107 | //if err != nil { 108 | // log.Error("解析公钥") 109 | // return err 110 | //} 111 | //// 类型断言 112 | //pub := pubInterface.(*rsa.PublicKey) 113 | // 验证签名 114 | return rsa.VerifyPKCS1v15(pub, crypto.SHA256, hashed[:], deSignData) 115 | } 116 | } 117 | 118 | //公钥验证with公钥 119 | func RsaSignVerWithKey(data []byte, signature []byte, publicKey []byte) error { 120 | log.Debug("publicKey.....", string(publicKey)) 121 | bSignData, err := base64.StdEncoding.DecodeString(string(signature)) 122 | if err != nil { 123 | log.Error("base64 Decode error:", err) 124 | return err 125 | } 126 | hashed := sha256.Sum256(data) 127 | bKey, err := base64.RawStdEncoding.DecodeString(string(publicKey)) 128 | if err != nil { 129 | log.Error("base64 Decode error:", err) 130 | } 131 | //log.Debug("base64 key...", string(bKey)) 132 | pub, err := x509.ParsePKCS1PublicKey(bKey) 133 | if err != nil { 134 | log.Error("ParsePKCS1PublicKey err: %s", err) 135 | return err 136 | } 137 | return rsa.VerifyPKCS1v15(pub, crypto.SHA256, hashed[:], bSignData) 138 | } 139 | -------------------------------------------------------------------------------- /localdb/database.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package localdb 15 | 16 | import ( 17 | "github.com/boxproject/voucher/config" 18 | verrors "github.com/boxproject/voucher/errors" 19 | "github.com/syndtr/goleveldb/leveldb" 20 | "github.com/syndtr/goleveldb/leveldb/errors" 21 | "github.com/syndtr/goleveldb/leveldb/filter" 22 | "github.com/syndtr/goleveldb/leveldb/opt" 23 | "github.com/syndtr/goleveldb/leveldb/util" 24 | logger "github.com/alecthomas/log4go" 25 | ) 26 | 27 | type Database interface { 28 | Put(key []byte, value []byte) error 29 | Get(key []byte) ([]byte, error) 30 | Has(key []byte) (bool, error) 31 | Delete(key []byte) error 32 | GetPrifix(keyPrefix []byte) (map[string]string, error) 33 | Close() error 34 | } 35 | 36 | func NewLVDBDatabase(filePath string, cache int, openFiles int) (*leveldb.DB, error) { 37 | if cache < 16 { 38 | cache = 16 39 | } 40 | if openFiles < 16 { 41 | openFiles = 16 42 | } 43 | 44 | db, err := leveldb.OpenFile(filePath, &opt.Options{ 45 | OpenFilesCacheCapacity: openFiles, 46 | BlockCacheCapacity: cache / 2 * opt.MiB, 47 | WriteBuffer: cache / 4 * opt.MiB, 48 | Filter: filter.NewBloomFilter(10), 49 | }) 50 | 51 | if _, corrupted := err.(*errors.ErrCorrupted); corrupted { 52 | db, err = leveldb.RecoverFile(filePath, nil) 53 | } 54 | 55 | if err != nil { 56 | return nil, err 57 | } 58 | 59 | return db, nil 60 | } 61 | 62 | func NewDatabase(cfg *config.DatabaseConfig) (Database, error) { 63 | db, err := NewLVDBDatabase(cfg.Filepath, cfg.Cache, cfg.Openfiles) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | return &table{ 69 | db: db, 70 | prefix: cfg.Prefix, 71 | }, nil 72 | } 73 | 74 | type table struct { 75 | db *leveldb.DB 76 | prefix string 77 | } 78 | 79 | func (dt *table) Put(key []byte, value []byte) error { 80 | return dt.db.Put(append([]byte(dt.prefix), key...), value, nil) 81 | } 82 | 83 | func (dt *table) Has(key []byte) (ret bool, err error) { 84 | ret, err = dt.db.Has(append([]byte(dt.prefix), key...), nil) 85 | if err != nil { 86 | if err == leveldb.ErrNotFound { 87 | return ret, verrors.NoDataErr 88 | } 89 | return 90 | } 91 | 92 | return ret, nil 93 | } 94 | 95 | func (dt *table) Get(key []byte) ([]byte, error) { 96 | data, err := dt.db.Get(append([]byte(dt.prefix), key...), nil) 97 | if err != nil { 98 | if err == leveldb.ErrNotFound { 99 | return nil, verrors.NoDataErr 100 | } 101 | return nil, err 102 | } 103 | return data, nil 104 | } 105 | 106 | //查询前缀 TODO iter 使用字节组赋值有问题 107 | func (dt *table) GetPrifix(keyPrefix []byte) (map[string]string, error) { 108 | var resMap map[string]string = make(map[string]string) 109 | iter := dt.db.NewIterator(util.BytesPrefix(append([]byte(dt.prefix), keyPrefix...)), nil) 110 | if iter.Error() == leveldb.ErrNotFound { 111 | return nil, verrors.NoDataErr 112 | } 113 | if iter.Error() != nil { 114 | logger.Error("get prifix error") 115 | return nil, iter.Error() 116 | } 117 | for iter.Next() { 118 | resMap[string(iter.Key())] = string(iter.Value()) 119 | } 120 | 121 | iter.Release() 122 | 123 | return resMap, nil 124 | } 125 | 126 | func (dt *table) Delete(key []byte) error { 127 | err := dt.db.Delete(append([]byte(dt.prefix), key...), nil) 128 | if err != nil { 129 | if err == leveldb.ErrNotFound { 130 | return verrors.NoDataErr 131 | } 132 | 133 | return err 134 | } 135 | 136 | return nil 137 | } 138 | 139 | func (dt *table) Close() error { 140 | return dt.db.Close() 141 | } 142 | -------------------------------------------------------------------------------- /trans/account_rep.go: -------------------------------------------------------------------------------- 1 | package trans 2 | 3 | import ( 4 | "github.com/boxproject/voucher/config" 5 | "github.com/boxproject/voucher/localdb" 6 | log "github.com/alecthomas/log4go" 7 | "github.com/boxproject/voucher/util" 8 | "github.com/ethereum/go-ethereum/common" 9 | "math/big" 10 | "sync" 11 | ) 12 | 13 | type AccountHandler struct { 14 | db localdb.Database 15 | quitChannel chan int 16 | ethHandler *EthHandler 17 | accountMu sync.Mutex 18 | accountPoolSize int 19 | //blockNoFilePath string 20 | nonceFilePath string 21 | } 22 | 23 | //初始化 24 | //initialization 25 | func NewAccountHandler(db localdb.Database, handler *EthHandler, poolSize int, path string) *AccountHandler { 26 | return &AccountHandler{db: db, ethHandler: handler, accountPoolSize: poolSize, nonceFilePath: path, quitChannel: make(chan int, 1)} 27 | } 28 | 29 | func (a *AccountHandler) use(address common.Address) { 30 | //db 31 | defer a.accountMu.Unlock() 32 | a.accountMu.Lock() //操作map 加锁 33 | if config.AccountUsedMap[address.Hex()] != "" { 34 | log.Error("account: %s already used!", address.Hex()) 35 | return 36 | } 37 | a.db.Delete([]byte(config.ACCOUNT_NO_USED + address.Hex())) 38 | a.db.Put([]byte(config.ACCOUNT_USED+address.Hex()), []byte(address.Hex())) 39 | config.AccountUsedMap[address.Hex()] = address.Hex() //map中记录使用地址 40 | if addr, err := a.generateAccount(); err != nil { 41 | log.Error("generateAccount err:", err) 42 | } else { 43 | log.Debug("use add addr :", addr) 44 | //config.ReportedChan <- &config.RepM{RepType: config.REP_ACCOUNT_ADD, Account: addr.Hex(), Category: config.CATEGORY_ETH} 45 | } 46 | } 47 | 48 | //生成合约账户 49 | //Create account 50 | func (a *AccountHandler) generateAccount() (addr common.Address, err error) { 51 | log.Debug("generateAccount.......") 52 | 53 | nonce, err := util.ReadNumberFromFile(a.nonceFilePath) //block cfg 54 | if err != nil { 55 | log.Error("read block info err :%s", err) 56 | return 57 | } 58 | 59 | log.Debug("current nonce :%d", nonce.Int64()) 60 | if addr, err = a.ethHandler.DeployWallet(nonce); err != nil { 61 | log.Error("deploy err:", err) 62 | } else { 63 | nonce = nonce.Add(nonce, big.NewInt(config.NONCE_PLUS)) 64 | util.WriteNumberToFile(a.nonceFilePath, nonce) 65 | a.db.Put([]byte(config.ACCOUNT_NO_USED+config.ACCOUNT_TYPE_ETH+addr.Hex()), []byte(addr.Hex())) 66 | } 67 | return 68 | } 69 | 70 | //生成指定数量的account 71 | //Generate specified amount of accounts 72 | func (a *AccountHandler) generateAccounts(count int) (addrs []common.Address, err error) { 73 | var i int 74 | for i = 0; i < count; i++ { 75 | var addr common.Address 76 | if addr, err = a.generateAccount(); err != nil { 77 | break 78 | } else { 79 | addrs = append(addrs, addr) 80 | } 81 | } 82 | return 83 | } 84 | 85 | //启动 读取目前池中多少可用账户 生成指定数量账户 86 | //Startup Load current number of available accounts, generate specified number of accounts 87 | func (a *AccountHandler) Start() { 88 | log.Debug("AccountHandler start.......") 89 | 90 | a.accountMu.Lock() //操作map 加锁 91 | accountUsedDbMap, err := a.db.GetPrifix([]byte(config.ACCOUNT_USED)) 92 | if err != nil { 93 | log.Error("db error:%v", err) 94 | } 95 | 96 | for _, v := range accountUsedDbMap { 97 | account := string(v) 98 | config.AccountUsedMap[account] = account 99 | } 100 | 101 | log.Debug("accountUsedDbMap...", accountUsedDbMap) 102 | 103 | accountNoUsedDbMap, err := a.db.GetPrifix([]byte(config.ACCOUNT_NO_USED)) 104 | noUsedLen := len(accountNoUsedDbMap) 105 | log.Debug("accountNoUsedDbMap...", accountNoUsedDbMap) 106 | log.Debug("noUsedLen...", noUsedLen) 107 | if noUsedLen < a.accountPoolSize { 108 | if addrs, err := a.generateAccounts(a.accountPoolSize - noUsedLen); err != nil { 109 | log.Error("generateAccount err:", err) 110 | } else { 111 | log.Debug("addrs.....", addrs) 112 | } 113 | } 114 | 115 | a.accountMu.Unlock() 116 | 117 | //chan 118 | loop := true 119 | for loop { 120 | select { 121 | case <-a.quitChannel: 122 | log.Info("PriEthHandler::SendMessage thread exitCh!") 123 | loop = false 124 | case account, ok := <-config.AccountUsedChan: 125 | if ok { 126 | a.use(account) 127 | } else { 128 | log.Error("read from channel failed") 129 | } 130 | } 131 | } 132 | } 133 | 134 | //启动 读取目前池中多少可用账户 生成指定数量账户 135 | //Startup Load current number of available accounts, generate specified number of accounts 136 | func (a *AccountHandler) Stop() { 137 | a.quitChannel <- 0 138 | } 139 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package config 15 | 16 | import ( 17 | "fmt" 18 | ) 19 | 20 | const ( 21 | SVRCERTSCRIPT = "server-cert.sh" 22 | CLTCERTSCRIPT = "client-cert.sh" 23 | 24 | DEFAULTSECRETLEN = 40 25 | DEFAULTPASSLENT = 8 26 | DEFAULTAPPNUM = 3 27 | DEFAULTKEYPREFIX = "BOX" 28 | DEFAULTDBDIRNAME = "db" 29 | DEFAULTCERTSDIRNAME = "certs" 30 | DEFAULTSCRIPTDIRNAME = "scripts" 31 | DEFAULTLOGDIRNAME = "log/voucher.log" 32 | DEFAULTCONFIGFILENAME = "config.toml" 33 | ) 34 | 35 | type Config struct { 36 | Basedir string `toml:"basedir"` 37 | AgentSerCfg AgentServiceConfig `toml:"agentservice"` 38 | Secret SecretConfig `toml:"secret"` 39 | DB DatabaseConfig `toml:"database"` 40 | LogConfig LogConfig `toml:"log"` 41 | CertHTTPConfig CertHTTPConfig `toml:"service"` 42 | APIConfig APIConfig `toml:"api"` 43 | EthereumConfig EthereumConfig `toml:"ethereum"` 44 | BitcoinConfig BitcoinConfig `toml:"bitcoin"` 45 | } 46 | 47 | func (c *Config) String() string { 48 | return fmt.Sprintf("[Base DIR] %s\n[Database]\n%s\n\n[Nodes]\n%s\n", c.Basedir, c.DB) 49 | } 50 | 51 | type AgentServiceConfig struct { 52 | Name string `toml:"name"` 53 | Alias string `toml:"alias"` 54 | CompanionName string `toml:"companionname"` 55 | IpAndPort string `toml:"ipandport"` 56 | Pattern string `toml:"pattern"` 57 | RPCAddr string `toml:"rpcapi"` 58 | ClientCertPath string `toml:"clientcert"` 59 | ClientKeyPath string `toml:"clientkey"` 60 | } 61 | 62 | type DatabaseConfig struct { 63 | Filepath string `toml:"filePath"` 64 | Cache int `toml:"cache"` 65 | Openfiles int `toml:"openFiles"` 66 | Prefix string `toml:"prefix"` 67 | } 68 | 69 | func (d DatabaseConfig) String() string { 70 | return fmt.Sprintf("[File Path]: %s, [Cache] %d, [Openfiles] %d", d.Filepath, d.Cache, d.Openfiles) 71 | } 72 | 73 | type SecretConfig struct { 74 | SecretLength int `toml:"secretLength"` 75 | PassLength int `toml:"passLength"` 76 | AppNum int `toml:"appNum"` 77 | } 78 | 79 | // NodeConfig - 私链连接配置 80 | type NodeConfig struct { 81 | Name string `toml:"name"` 82 | RPCAddr string `toml:"rpcapi"` 83 | ClientCertPath string `toml:"clientcert"` 84 | ClientKeyPath string `toml:"clientkey"` 85 | } 86 | 87 | func (n NodeConfig) String() string { 88 | return fmt.Sprintf("[%s]: RPC API: %s\nClient cert: %s\nClient Key: %s\n", n.Name, n.RPCAddr, n.ClientCertPath, n.ClientKeyPath) 89 | } 90 | 91 | type LogConfig struct { 92 | Level string `toml:"level"` 93 | LogPath string `toml:"logPath"` 94 | // json/console 95 | Encoding string `toml:"encoding"` 96 | } 97 | 98 | type CertHTTPConfig struct { 99 | APIConfig 100 | } 101 | 102 | type APIConfig struct { 103 | IP string `toml:"ip"` 104 | Port int `toml:"port"` 105 | } 106 | 107 | type EthereumConfig struct { 108 | Scheme string `toml:"scheme"` 109 | DelayedBlocks int64 `toml:"delayedBlocks"` 110 | CursorBlocks int64 `toml:"cursorBlocks"` 111 | Retries int `toml:retries` 112 | GasLimit int `toml:gasLimit` 113 | AccountPoolSize int `toml:accountPoolSize` 114 | BlockNoFilePath string `toml:blockNoFilePath` 115 | NonceFilePath string `toml:nonceFilePath` 116 | CursorFilePath string `toml:cursorFilePath` 117 | } 118 | 119 | type BitcoinConfig struct { 120 | Type string `toml:"type"` 121 | Host string `toml:"host"` 122 | Rpcuser string `toml:"rpcuser"` 123 | Rpcpass string `toml:"rpcpass"` 124 | Clientcert string `toml:"clientcert"` 125 | Clientkey string `toml:"clientkey"` 126 | Confirmations int64 `toml:"confirmations"` 127 | Initusernum int `toml:initusernum` 128 | Initheight int64 `toml:initheight` 129 | BlockNoFilePath string `toml:blockNoFilePath` 130 | } 131 | -------------------------------------------------------------------------------- /util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | //"bytes" 5 | log "github.com/alecthomas/log4go" 6 | "io/ioutil" 7 | //"math/big" 8 | "bytes" 9 | "crypto/aes" 10 | //"crypto/rand" 11 | "github.com/boxproject/voucher/config" 12 | "github.com/boxproject/voucher/localdb" 13 | "math/big" 14 | "math/rand" 15 | "net" 16 | "os" 17 | "os/user" 18 | "path" 19 | "path/filepath" 20 | "runtime" 21 | "strconv" 22 | "sync" 23 | "time" 24 | "errors" 25 | ) 26 | 27 | var ( 28 | rootPath string 29 | filePath string 30 | //aesRandomStr string 31 | ) 32 | 33 | func homeDir() string { 34 | if home := os.Getenv("HOME"); home != "" { 35 | return home 36 | } 37 | 38 | if usr, err := user.Current(); err == nil { 39 | return usr.HomeDir 40 | } 41 | 42 | return "" 43 | } 44 | 45 | func GetFilePath() string { 46 | return filePath 47 | } 48 | 49 | func DefaultConfigDir() string { 50 | home := homeDir() 51 | if home != "" { 52 | if runtime.GOOS == "darwin" { 53 | return filepath.Join(home, ".bcmonitor") 54 | } else if runtime.GOOS == "windows" { 55 | return filepath.Join(home, "AppData", "Roaming", "bcmonitor") 56 | } else { 57 | return filepath.Join(home, ".bcmonitor") 58 | } 59 | } 60 | 61 | return "" 62 | } 63 | 64 | // configPath 不为空时,不检查fileName 65 | func GetConfigFilePath(configPath, defaultFileName string) string { 66 | for i := 0; i < 3; i++ { 67 | if configPath != "" { 68 | if _, err := os.Stat(configPath); !os.IsNotExist(err) { 69 | break 70 | } 71 | } 72 | 73 | if i == 0 { 74 | configPath = path.Join(GetFilePath(), defaultFileName) 75 | } else if i == 1 { 76 | configPath = path.Join(DefaultConfigDir(), defaultFileName) 77 | } 78 | } 79 | 80 | return configPath 81 | } 82 | 83 | //file 84 | var NoRWMutex sync.RWMutex 85 | 86 | func WriteNumberToFile(filePath string, blkNumber *big.Int) error { 87 | return ioutil.WriteFile(filePath, []byte(blkNumber.String()), 0755) 88 | } 89 | 90 | func ReadNumberFromFile(filePath string) (*big.Int, error) { 91 | if _, err := os.Stat(filePath); os.IsNotExist(err) { 92 | log.Debug("file not found, %v", err) 93 | return big.NewInt(0), err 94 | } 95 | 96 | data, err := ioutil.ReadFile(filePath) 97 | if err != nil { 98 | return nil, err 99 | } 100 | 101 | data = bytes.TrimSpace(data) 102 | delta, isok := big.NewInt(0).SetString(string(data), 10) 103 | if isok == false { 104 | return big.NewInt(0), errors.New("data is nil") 105 | } 106 | return delta, nil 107 | } 108 | 109 | //func ReadBlockInfoFromFile(filePath string) (*config.BlockInfoCfg, error) { 110 | // noRWMutex.Lock() 111 | // defer noRWMutex.Unlock() 112 | // blkInfoCfg := &config.BlockInfoCfg{big.NewInt(0),big.NewInt(0),big.NewInt(0)} 113 | // if _, err := os.Stat(filePath); os.IsNotExist(err) { 114 | // log.Debug("file not found, %v", err) 115 | // return blkInfoCfg, nil 116 | // } 117 | // data, err := ioutil.ReadFile(filePath) 118 | // if err != nil { 119 | // return blkInfoCfg, err 120 | // } 121 | // if err = json.Unmarshal(data, blkInfoCfg); err != nil { 122 | // log.Debug("json unmarshal error:%s", err) 123 | // } 124 | // return blkInfoCfg, nil 125 | //} 126 | // 127 | //func WriteBlockInfoToFile(filePath string, blkInfoCfg *config.BlockInfoCfg) error { 128 | // noRWMutex.Lock() 129 | // defer noRWMutex.Unlock() 130 | // if data, err := json.Marshal(blkInfoCfg); err != nil { 131 | // log.Debug("json unmarshal error:%s", err) 132 | // } else { 133 | // ioutil.WriteFile(filePath, data, 0755) 134 | // } 135 | // return nil 136 | //} 137 | 138 | func GetCurrentIp() string { 139 | addrSlice, err := net.InterfaceAddrs() 140 | if nil != err { 141 | log.Error("Get local IP addr failed!!!") 142 | return "localhost" 143 | } 144 | for _, addr := range addrSlice { 145 | if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { 146 | if nil != ipnet.IP.To4() { 147 | return ipnet.IP.String() 148 | } 149 | } 150 | } 151 | return "localhost" 152 | } 153 | 154 | //var cbcKey string//default 155 | // 156 | //func GetAesKeyRandom() string { 157 | // if cbcKey == "" { 158 | // r := rand.New(rand.NewSource(time.Now().UnixNano())) //添加时间生成随机数 159 | // key := make([]byte, aes.BlockSize) 160 | // copy(key, []byte(string(r.Intn(100000)))) 161 | // cbcKey = string(key) 162 | // //更新key 163 | // } 164 | // return cbcKey 165 | //} 166 | 167 | //aes key 存入db 168 | func GetAesKeyRandomFromDb(db localdb.Database) []byte { 169 | if aesKeyBytes, err := db.Get([]byte(config.APP_KEY_PRIFIX)); err != nil { 170 | log.Info("get app key failed. err : %s", err) 171 | cbcKey := make([]byte, aes.BlockSize) 172 | 173 | r := rand.New(rand.NewSource(time.Now().UnixNano())) //添加时间生成随机数 174 | ri := r.Uint64() 175 | rstr := strconv.FormatUint(ri, 16) 176 | copy(cbcKey, []byte(rstr)) 177 | if err = db.Put([]byte(config.APP_KEY_PRIFIX), cbcKey); err != nil { 178 | log.Debug("land aes to db err: %s", err) 179 | return config.DefAesKey 180 | } 181 | return cbcKey 182 | } else { 183 | return aesKeyBytes 184 | } 185 | 186 | } 187 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | ///go:generate protoc --go_out=plugins=grpc:. pb/protocol.proto 16 | ///go:generate abigen -pkg trans -sol trans/contracts/sink.sol -out trans/sink.sol.go 17 | ///go:generate abigen -pkg trans -sol trans/contracts/wallet.sol -out trans/wallet.sol.go 18 | package main 19 | 20 | import ( 21 | "fmt" 22 | "os" 23 | "os/signal" 24 | "syscall" 25 | 26 | "encoding/json" 27 | "github.com/boxproject/voucher/common" 28 | "github.com/boxproject/voucher/config" 29 | //"github.com/boxproject/voucher/http" 30 | logger "github.com/alecthomas/log4go" 31 | "github.com/boxproject/voucher/localdb" 32 | "github.com/boxproject/voucher/node" 33 | "github.com/boxproject/voucher/operate" 34 | "github.com/boxproject/voucher/token" 35 | "github.com/boxproject/voucher/trans" 36 | "github.com/boxproject/voucher/util" 37 | "github.com/mdp/qrterminal" 38 | "gopkg.in/urfave/cli.v1" 39 | "github.com/awnumar/memguard" 40 | "path/filepath" 41 | "strings" 42 | ) 43 | 44 | func main() { 45 | app := newApp() 46 | if err := app.Run(os.Args); err != nil { 47 | logger.Error("start app failed. %v", err) 48 | } 49 | } 50 | 51 | func run(ctx *cli.Context) (err error) { 52 | defer memguard.DestroyAll() 53 | var ( 54 | cfg *config.Config 55 | db localdb.Database 56 | ) 57 | dir, err := filepath.Abs(filepath.Dir(os.Args[0])) 58 | if err != nil { 59 | logger.Error("get file path error",err) 60 | } 61 | execDir := strings.Replace(dir, "\\", "/", -1) 62 | logger.LoadConfiguration(execDir+"/log.xml") 63 | 64 | //config 65 | filePath := ctx.String("c") 66 | if cfg, err = common.LoadConfig(filePath); err != nil { 67 | logger.Error("load config failed. cause: %v", err) 68 | return err 69 | } 70 | 71 | //log 72 | //if err = log.InitLogger(&cfg.LogConfig); err != nil { 73 | // log.Errorf("init logger failed. cause: %v", err) 74 | // return err 75 | //} 76 | 77 | //defer log.Close() 78 | 79 | logger.Debug("config file:\n%s", cfg) 80 | 81 | quitCh := make(chan os.Signal, 1) 82 | signal.Notify(quitCh, 83 | syscall.SIGINT, syscall.SIGTERM, 84 | syscall.SIGHUP, syscall.SIGKILL, 85 | syscall.SIGUSR1, syscall.SIGUSR2) 86 | 87 | //db 88 | if db, err = localdb.RunDatabase(cfg); err != nil { 89 | fmt.Println("run database failed. cause: %v", err) 90 | //logger.Error("run database failed. cause: %v", err) 91 | return err 92 | } 93 | defer db.Close() 94 | 95 | printQrCode(cfg, db) 96 | 97 | //init status 98 | initStatus(cfg, db) 99 | 100 | //init eth token 101 | initEthToken(db) 102 | //grpc 103 | nodeApi, err := node.InitConn(cfg, db) 104 | if err != nil { 105 | fmt.Println("rpc client init failed. cause: %v", err) 106 | return err 107 | } 108 | defer nodeApi.CloseConn() 109 | /* 110 | //down web api 111 | downloader, err := http.NewCertService(cfg, db) 112 | if err != nil { 113 | log.Errorf("new cert download service failed. cause: %v", err) 114 | return err 115 | } 116 | 117 | if err = downloader.Start(); err != nil { 118 | log.Errorf("start cert download service failed. cause: %v", err) 119 | return 120 | } 121 | */ 122 | //eth conn 123 | ethHander, err := trans.NewEthHandler(cfg, db) 124 | defer ethHander.Stop() 125 | 126 | if err != nil { 127 | fmt.Println("new default handler config failed. cause: %v", err) 128 | return err 129 | } 130 | oHandler := operate.InitOperateHandler(cfg, db, ethHander) 131 | go oHandler.Start() 132 | defer oHandler.Close() 133 | 134 | ////web api 135 | //cliapi, err := http.NewClientAPI(cfg, db, nodeApi, ethHander) 136 | //if err != nil { 137 | // logger.Error("new client api service failed. cause: %v", err) 138 | // return err 139 | //} 140 | //if err = cliapi.Start(); err != nil { 141 | // logger.Error("start cert cliapi service failed. cause: %v", err) 142 | // return err 143 | //} 144 | 145 | //account rep 146 | 147 | //defer cliapi.Shutdown() 148 | 149 | //printQrCode(cfg) 150 | 151 | <-quitCh 152 | //downloader.Shutdown(context.Background()) 153 | 154 | return err 155 | } 156 | 157 | func newApp() *cli.App { 158 | app := cli.NewApp() 159 | app.Action = run 160 | 161 | app.Version = PrintVersion(gitCommit, stage, version) 162 | app.Name = "voucher" 163 | app.Usage = "command line interface" 164 | app.Author = "BOX.la" 165 | app.Copyright = "Copyright 2017-2019 The BOX.la Authors" 166 | app.Email = "develop@2se.com" 167 | app.Description = "The automatic teller machine for cryptocurrency" 168 | 169 | app.Flags = []cli.Flag{ 170 | cli.StringFlag{ 171 | Name: "c,config", 172 | Usage: "Path of the config.toml file", 173 | Value: "", 174 | }, 175 | } 176 | 177 | return app 178 | } 179 | 180 | //init eth tokens 181 | func initEthToken(db localdb.Database) { 182 | token.LoadTokenFrom(db) 183 | } 184 | 185 | //init status 186 | func initStatus(cfg *config.Config, db localdb.Database) { 187 | if statusBytes, err := db.Get([]byte(config.STATAUS_KEY)); err != nil { 188 | logger.Info("load status from db err:%s", err) 189 | config.RealTimeStatus.ServerStatus = config.VOUCHER_STATUS_UNCREATED //TODO 190 | config.RealTimeStatus.Total = cfg.Secret.AppNum 191 | 192 | config.RealTimeStatus.CoinStatus = append(config.RealTimeStatus.CoinStatus, config.CoinStatu{Name: config.COIN_NAME_BTC, Category: config.CATEGORY_BTC, Decimals: config.COIN_DECIMALS_BTC, Used: false}) 193 | //config.RealTimeStatus.CoinStatus = append(config.RealTimeStatus.CoinStatus, config.CoinStatu{Name: config.COIN_NAME_ETH, Category: config.CATEGORY_ETH, Decimals: config.COIN_DECIMALS_ETH, Used: true}) 194 | //config.RealTimeStatus.KeyStroeStatus = make([]config.KeyStroeStatu) 195 | } else { 196 | if err = json.Unmarshal(statusBytes, config.RealTimeStatus); err != nil { 197 | logger.Error("unmarshal status err: %s", err) 198 | } else { 199 | if config.RealTimeStatus.ServerStatus == config.VOUCHER_STATUS_STATED { 200 | config.RealTimeStatus.ServerStatus = config.VOUCHER_STATUS_PAUSED 201 | } 202 | if config.RealTimeStatus.CoinStatus == nil || len(config.RealTimeStatus.CoinStatus) == 0 { 203 | config.RealTimeStatus.CoinStatus = append(config.RealTimeStatus.CoinStatus, config.CoinStatu{Name: config.COIN_NAME_BTC, Category: config.CATEGORY_BTC, Decimals: config.COIN_DECIMALS_BTC, Used: false}) 204 | } 205 | config.RealTimeStatus.Status = config.PASSWORD_STATUS_OK 206 | config.RealTimeStatus.NodesAuthorized = nil 207 | } 208 | } 209 | } 210 | 211 | func PrintVersion(gitCommit, stage, version string) string { 212 | if gitCommit != "" { 213 | return fmt.Sprintf("%s-%s-%s", stage, version, gitCommit) 214 | } 215 | return fmt.Sprintf("%s-%s", stage, version) 216 | } 217 | 218 | func printQrCode(cfg *config.Config, db localdb.Database) { 219 | qrCodeArrays := []string{cfg.AgentSerCfg.IpAndPort, string(util.GetAesKeyRandomFromDb(db))} 220 | qrCodeByte, _ := json.Marshal(qrCodeArrays) 221 | qrterminal.Generate(string(qrCodeByte), qrterminal.L, os.Stdout) 222 | } 223 | -------------------------------------------------------------------------------- /token/token.go: -------------------------------------------------------------------------------- 1 | package token 2 | 3 | import ( 4 | "encoding/json" 5 | log "github.com/alecthomas/log4go" 6 | "github.com/boxproject/voucher/config" 7 | "github.com/boxproject/voucher/localdb" 8 | "sync" 9 | ) 10 | 11 | var tokenRWMutex sync.RWMutex 12 | 13 | var EthAddrTokenMap map[string]*config.TokenInfo = make(map[string]*config.TokenInfo) //eth addr-token map 14 | 15 | var EthCategoryTokenMap map[int64]*config.TokenInfo = make(map[int64]*config.TokenInfo) //eth category-token map 16 | 17 | //获取有效token 18 | func getEnableTokenMap(db localdb.Database) ([]*config.TokenInfo){ 19 | var TokenList []*config.TokenInfo 20 | if tokenMap, err := db.GetPrifix([]byte(config.TOKEN_PRIFIX)); err != nil { 21 | log.Error("get tokenlist err:%s", err) 22 | } else { 23 | for _, tokenBytes := range tokenMap { 24 | tokenInfo := &config.TokenInfo{} 25 | if err = json.Unmarshal([]byte(tokenBytes), tokenInfo); err != nil { 26 | log.Error("unmarshal err:%s", err) 27 | } else { 28 | if tokenInfo.Status == "true" { 29 | TokenList = append(TokenList, tokenInfo) 30 | } 31 | } 32 | } 33 | } 34 | return TokenList 35 | } 36 | // 37 | func getEthTokenCount(db localdb.Database)(allCount,enbleCount,disableCount int){ 38 | if tokenMap, err := db.GetPrifix([]byte(config.TOKEN_PRIFIX)); err != nil { 39 | log.Error("get tokenlist err:%s", err) 40 | } else { 41 | //allCount = len(tokenMap) 42 | for _, tokenBytes := range tokenMap { 43 | tokenInfo := &config.TokenInfo{} 44 | if err = json.Unmarshal([]byte(tokenBytes), tokenInfo); err != nil { 45 | log.Error("unmarshal err:%s", err) 46 | } else { 47 | if tokenInfo.Status == "true" { 48 | enbleCount++ 49 | }else { 50 | disableCount++ 51 | } 52 | } 53 | } 54 | } 55 | return disableCount+enbleCount,enbleCount,disableCount 56 | } 57 | 58 | 59 | //新增或编辑代币 60 | func AddTokenMap(tokenInfoD *config.TokenInfo, db localdb.Database) (bool, int) { 61 | tokenRWMutex.Lock() 62 | defer tokenRWMutex.Unlock() 63 | 64 | log.Debug("EthAddrTokenMap...", EthAddrTokenMap) 65 | tokenInfoD.Status = "true" 66 | tokenInfo := &config.TokenInfo{} 67 | tokenBytes,_ := db.Get([]byte(config.TOKEN_PRIFIX+tokenInfoD.ContractAddr)) 68 | if len(tokenBytes) > 0 { 69 | if err := json.Unmarshal([]byte(tokenBytes), tokenInfo); err != nil { 70 | log.Error("unmarshal err:%s", err) 71 | return false, len(EthAddrTokenMap) 72 | } else { 73 | log.Debug("get tokenInfo:",tokenInfo) 74 | tokenInfoD.Category = tokenInfo.Category 75 | } 76 | }else { 77 | //add new token 78 | tokenCount,_,_ := getEthTokenCount(db) 79 | tokenCount = tokenCount + 2 //start from 2 80 | tokenInfoD.Category = int64(tokenCount) 81 | } 82 | tokenData, err := json.Marshal(tokenInfoD) 83 | if err != nil { 84 | log.Error("token marshal err: %s", err) 85 | return false, len(EthAddrTokenMap) 86 | } 87 | if err = db.Put([]byte(config.TOKEN_PRIFIX+tokenInfoD.ContractAddr), tokenData); err != nil { 88 | log.Error("token db land err: %s", err) 89 | } else { 90 | EthAddrTokenMap[tokenInfoD.ContractAddr] = tokenInfoD 91 | EthCategoryTokenMap[tokenInfoD.Category] = tokenInfoD 92 | //TODO 上报 93 | reportInfo := &config.GrpcStream{Type: config.GRPC_TOKEN_LIST_WEB} 94 | reportInfo.TokenList = getEnableTokenMap(db) 95 | config.ReportedChan <- reportInfo 96 | 97 | return true, len(reportInfo.TokenList) 98 | } 99 | _,tokenEnableCount,_ := getEthTokenCount(db) 100 | return false, tokenEnableCount 101 | } 102 | 103 | func DelTokenMap(tokenInfoD *config.TokenInfo, db localdb.Database) (bool, int) { 104 | tokenRWMutex.Lock() 105 | defer tokenRWMutex.Unlock() 106 | 107 | tokenInfoS := EthAddrTokenMap[tokenInfoD.ContractAddr] 108 | if tokenInfoS != nil { //存在 109 | //db.Delete([]byte(config.TOKEN_PRIFIX + tokenInfoD.ContractAddr)) 110 | tokenInfoS.Status = "false" 111 | tokenData, err := json.Marshal(tokenInfoS) 112 | if err != nil { 113 | log.Error("token marshal err: %s", err) 114 | } 115 | db.Put([]byte(config.TOKEN_PRIFIX+tokenInfoS.ContractAddr), tokenData) 116 | 117 | delete(EthAddrTokenMap, tokenInfoS.ContractAddr) 118 | delete(EthCategoryTokenMap, tokenInfoS.Category) 119 | //TODO 上报 120 | reportInfo := &config.GrpcStream{Type: config.GRPC_TOKEN_LIST_WEB} 121 | reportInfo.TokenList = getEnableTokenMap(db) 122 | config.ReportedChan <- reportInfo 123 | 124 | return true, len(reportInfo.TokenList) 125 | } else { 126 | log.Error("del token failed. not found token address: %s", tokenInfoD.ContractAddr) 127 | } 128 | 129 | _,tokenEnableCount,_ := getEthTokenCount(db) 130 | return false, tokenEnableCount 131 | } 132 | 133 | func TokenList(db localdb.Database) bool { 134 | tokenRWMutex.Lock() 135 | defer tokenRWMutex.Unlock() 136 | 137 | reportInfo := &config.GrpcStream{Type: config.GRPC_TOKEN_LIST_WEB} 138 | if tokenMap, err := db.GetPrifix([]byte(config.TOKEN_PRIFIX)); err != nil { 139 | log.Error("get tokenlist err:%s", err) 140 | return false 141 | } else { 142 | for _, tokenBytes := range tokenMap { 143 | tokenInfo := &config.TokenInfo{} 144 | if err = json.Unmarshal([]byte(tokenBytes), tokenInfo); err != nil { 145 | log.Error("unmarshal err:%s", err) 146 | } else { 147 | if tokenInfo.Status == "true" { 148 | reportInfo.TokenList = append(reportInfo.TokenList, tokenInfo) 149 | } 150 | } 151 | } 152 | 153 | config.RealTimeStatus.TokenCount = len(tokenMap) 154 | if statusByte, err := json.Marshal(config.RealTimeStatus); err != nil { 155 | log.Error("hashList json marshal error:%v", err) 156 | } else { 157 | if err = db.Put([]byte(config.STATAUS_KEY), statusByte); err != nil { 158 | log.Error("landStatus err :%s", err) 159 | } 160 | } 161 | config.ReportedChan <- reportInfo 162 | } 163 | return true 164 | 165 | } 166 | 167 | //db加载token信息 168 | func LoadTokenFrom(db localdb.Database) { 169 | tokenRWMutex.Lock() 170 | defer tokenRWMutex.Unlock() 171 | log.Debug("LoadTokenFrom....", EthAddrTokenMap) 172 | if tokenMap, err := db.GetPrifix([]byte(config.TOKEN_PRIFIX)); err != nil { 173 | log.Error("get tokenlist err:%s", err) 174 | } else { 175 | for _, tokenBytes := range tokenMap { 176 | tokenInfo := &config.TokenInfo{} 177 | if err = json.Unmarshal([]byte(tokenBytes), tokenInfo); err != nil { 178 | log.Error("LoadTokenFrom unmarshal err: %s", err) 179 | } else { 180 | //db.Delete([]byte(config.TOKEN_PRIFIX+tokenInfo.ContractAddr)) 181 | //continue 182 | if tokenInfo.Status == "true" { 183 | EthAddrTokenMap[tokenInfo.ContractAddr] = tokenInfo 184 | EthCategoryTokenMap[tokenInfo.Category] = tokenInfo 185 | }else if len(tokenInfo.Status) == 0{ 186 | //DB兼容处理 187 | log.Info("[OLD TOKEN DB][%v]",tokenInfo) 188 | tokenInfo.Status = "true" 189 | EthAddrTokenMap[tokenInfo.ContractAddr] = tokenInfo 190 | EthCategoryTokenMap[tokenInfo.Category] = tokenInfo 191 | log.Info("[DO UPDATE][%v]",tokenInfo) 192 | tokenData, err := json.Marshal(tokenInfo) 193 | if err != nil { 194 | log.Error("token marshal err: %s", err) 195 | } 196 | if err = db.Put([]byte(config.TOKEN_PRIFIX+tokenInfo.ContractAddr), tokenData); err != nil { 197 | log.Error("token db land err: %s", err) 198 | } 199 | } 200 | } 201 | } 202 | } 203 | 204 | log.Debug("EthAddrTokenMap....", EthAddrTokenMap) 205 | } 206 | 207 | func GetTokenByAddr(addr string) *config.TokenInfo { 208 | tokenRWMutex.Lock() 209 | //log.Debug("EthAddrTokenMap....", EthAddrTokenMap) 210 | defer tokenRWMutex.Unlock() 211 | return EthAddrTokenMap[addr] 212 | } 213 | 214 | //根据category获取token address 215 | func GetTokenByCategory(category int64) *config.TokenInfo { 216 | tokenRWMutex.Lock() 217 | defer tokenRWMutex.Unlock() 218 | return EthCategoryTokenMap[category] 219 | } 220 | -------------------------------------------------------------------------------- /trans/account.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package trans 16 | 17 | import ( 18 | "bytes" 19 | "crypto/ecdsa" 20 | "errors" 21 | 22 | "crypto/md5" 23 | log "github.com/alecthomas/log4go" 24 | "github.com/awnumar/memguard" 25 | "github.com/boxproject/voucher/common" 26 | "github.com/boxproject/voucher/config" 27 | verror "github.com/boxproject/voucher/errors" 28 | "github.com/boxproject/voucher/localdb" 29 | "github.com/btcsuite/btcd/btcec" 30 | "github.com/btcsuite/btcd/chaincfg" 31 | "github.com/btcsuite/btcd/txscript" 32 | "github.com/btcsuite/btcutil" 33 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 34 | "github.com/ethereum/go-ethereum/core/types" 35 | "github.com/ethereum/go-ethereum/crypto" 36 | "github.com/ethereum/go-ethereum/crypto/sha3" 37 | "sort" 38 | "unsafe" 39 | ) 40 | 41 | var pk *memguard.LockedBuffer 42 | 43 | func GetSecret(db localdb.Database) (string, error) { 44 | v, err := db.Get(config.SECRET.Bytes()) 45 | if err != nil { 46 | log.Error("get secret from db failed. casue: %v", err) 47 | return "", err 48 | } 49 | //d := binary.LittleEndian.Uint32(v) 50 | return string(v), nil 51 | } 52 | 53 | func RecoverPrivateKey(db localdb.Database, args ...[]byte) error { 54 | sort.Sort(IntSlice(args)) 55 | v, err := db.Get(config.SECRET.Bytes()) 56 | if err != nil { 57 | log.Error("get secret from db failed. casue: %v", err) 58 | return err 59 | } 60 | 61 | d := sha3.NewKeccak512() 62 | d.Write(v) 63 | for _, b := range args { 64 | d.Write(b) 65 | } 66 | dBytes := d.Sum(nil) 67 | hahsByte := md5.Sum(dBytes) 68 | db.Put(config.PRIVATEKEYHASH.Bytes(), hahsByte[:]) //private key hash 69 | 70 | buf := bytes.NewBuffer(dBytes) 71 | 72 | privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), buf) 73 | if err != nil { 74 | log.Error("generate key error:%v", err) 75 | return err 76 | } 77 | pk, err = memguard.NewImmutableFromBytes(crypto.FromECDSA(privateKeyECDSA)) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | if err := setPubKeytoDB(db); err != nil { 83 | log.Error("generate pubkey error:%v", err) 84 | return err 85 | } 86 | return nil 87 | } 88 | 89 | //校验私钥hash 90 | //Verify private key hash 91 | func CheckPrivateKey(db localdb.Database, args ...[]byte) bool { 92 | sort.Sort(IntSlice(args)) 93 | if pvhash, err := db.Get(config.PRIVATEKEYHASH.Bytes()); err != nil { 94 | log.Error("CheckPrivateKey err: %s", err) 95 | return false 96 | } else { 97 | v, err := db.Get(config.SECRET.Bytes()) 98 | if err != nil { 99 | log.Error("get secret from db failed. casue: %v", err) 100 | return false 101 | } 102 | 103 | d := sha3.NewKeccak512() 104 | d.Write(v) 105 | sort.Sort(IntSlice(args)) 106 | for _, b := range args { 107 | d.Write(b) 108 | } 109 | pv := d.Sum(nil) 110 | hashBytes := md5.Sum(pv) 111 | if bytes.Equal(pvhash, hashBytes[:]) { 112 | log.Debug("pvhash is equal dbHash") 113 | buf := bytes.NewBuffer(pv) 114 | privateKeyECDSA, err := ecdsa.GenerateKey(crypto.S256(), buf) 115 | if err != nil { 116 | log.Error("generate key error:%v", err) 117 | return false 118 | } 119 | pk, err = memguard.NewImmutableFromBytes(crypto.FromECDSA(privateKeyECDSA)) 120 | if err != nil { 121 | return false 122 | } 123 | if err := setPubKeytoDB(db); err != nil { 124 | log.Error("generate pubkey error:%v", err) 125 | return false 126 | } 127 | 128 | return true 129 | } 130 | } 131 | return false 132 | } 133 | 134 | //验证密码 135 | func RecordPasswordHash(db localdb.Database, id ,pass string)(bool){ 136 | if pvhash, err := db.Get([]byte(id)); err != nil { 137 | log.Error("CheckPrivateKey err: %s", err) 138 | d := sha3.NewKeccak512() 139 | d.Write([]byte(pass)) 140 | pv := d.Sum(nil) 141 | hashBytes := md5.Sum(pv) 142 | if err := db.Put([]byte(id), hashBytes[:]); err != nil { 143 | log.Error("put hash db error:",err) 144 | return false 145 | } 146 | }else { 147 | //数据不为空则退出 148 | if len(pvhash) != 0 { 149 | return false 150 | } 151 | } 152 | return true 153 | } 154 | //验证密码 155 | func CheckPasswordHash(db localdb.Database, id ,pass string)(bool){ 156 | if pvhash, err := db.Get([]byte(id)); err != nil { 157 | log.Error("CheckPrivateKey err: %s", err) 158 | return false 159 | }else { 160 | d := sha3.NewKeccak512() 161 | d.Write([]byte(pass)) 162 | pv := d.Sum(nil) 163 | hashBytes := md5.Sum(pv) 164 | if bytes.Equal(pvhash, hashBytes[:]) { 165 | return true 166 | }else { 167 | return false 168 | } 169 | } 170 | return false 171 | } 172 | 173 | func setPubKeytoDB(db localdb.Database) error { 174 | if pk == nil { 175 | return errors.New("get eth privkey error, data is nil") 176 | } 177 | keyArrayPtr := (*[32]byte)(unsafe.Pointer(&pk.Buffer()[0])) 178 | privateKeyECDSA, err := crypto.ToECDSA(keyArrayPtr[:]) 179 | if err != nil { 180 | return err 181 | } 182 | ethPubKey := crypto.PubkeyToAddress(privateKeyECDSA.PublicKey).Hex() 183 | if err := db.Put([]byte(config.PUBKEY_ETH), []byte(ethPubKey)); err != nil { 184 | return err 185 | } 186 | 187 | pv := btcec.PublicKey(privateKeyECDSA.PublicKey) 188 | btcPubKey, _ := btcutil.NewAddressPubKey(pv.SerializeCompressed(), &chaincfg.MainNetParams) 189 | if err := db.Put([]byte(config.PUBKEY_BTC), []byte(btcPubKey.EncodeAddress())); err != nil { 190 | return err 191 | } 192 | 193 | return nil 194 | } 195 | 196 | //是否已创建过key 197 | //If the key has been created 198 | func ExistPrivateKeyHash(db localdb.Database) bool { 199 | data, err := db.Get(config.PRIVATEKEYHASH.Bytes()) 200 | if err == verror.NoDataErr { 201 | log.Info("ExistPrivateKeyHash no data") 202 | } else { 203 | log.Error("ExistPrivateKeyHash err: %s", err) 204 | } 205 | if len(data) > 0 { 206 | return true 207 | } 208 | return false 209 | } 210 | 211 | func NewKeyedTransactor() *bind.TransactOpts { 212 | if pk == nil { 213 | log.Error("get eth privkey error, data is nil") 214 | return nil 215 | } else { 216 | keyArrayPtr := (*[32]byte)(unsafe.Pointer(&pk.Buffer()[0])) 217 | privateKeyECDSA, err := crypto.ToECDSA(keyArrayPtr[:]) 218 | if err != nil { 219 | log.Error(err) 220 | return nil 221 | } 222 | keyAddr := crypto.PubkeyToAddress(privateKeyECDSA.PublicKey) 223 | return &bind.TransactOpts{ 224 | From: keyAddr, 225 | Signer: func(signer types.Signer, address common.Address, tx *types.Transaction) (*types.Transaction, error) { 226 | if address != keyAddr { 227 | return nil, errors.New("not authorized to sign this account") 228 | } 229 | signature, err := crypto.Sign(signer.Hash(tx).Bytes(), privateKeyECDSA) 230 | if err != nil { 231 | return nil, err 232 | } 233 | return tx.WithSignature(signer, signature) 234 | }, 235 | //GasLimit: uint64(config.DefPubEthGasLimit), 236 | } 237 | } 238 | } 239 | 240 | func GetBtcPrivKey() txscript.KeyDB { 241 | if pk == nil { 242 | err := errors.New("get btc privkey error, data is nil") 243 | log.Error(err) 244 | return txscript.KeyClosure(func(addr btcutil.Address) (*btcec.PrivateKey, bool, error) { 245 | return nil, true, err 246 | }) 247 | } else { 248 | return txscript.KeyClosure(func(a btcutil.Address) (*btcec.PrivateKey, bool, error) { 249 | keyArrayPtr := (*[32]byte)(unsafe.Pointer(&pk.Buffer()[0])) 250 | privateKeyECDSA, err := crypto.ToECDSA(keyArrayPtr[:]) 251 | if err != nil { 252 | log.Error("crypto.ToECDSA err:", err) 253 | return nil, true, err 254 | } else { 255 | pv := btcec.PrivateKey(*privateKeyECDSA) 256 | return &pv, true, nil 257 | } 258 | 259 | }) 260 | } 261 | } 262 | 263 | func GetBtcPubKey(db localdb.Database) (string, error) { 264 | if datas, err := db.Get([]byte(config.PUBKEY_BTC)); err == nil { 265 | return string(datas), nil 266 | } else { 267 | return "", err 268 | } 269 | } 270 | 271 | func PrivateKeyToHex(db localdb.Database) string { 272 | if datas, err := db.Get([]byte(config.PUBKEY_ETH)); err == nil { 273 | return string(datas) 274 | } else { 275 | return "" 276 | } 277 | } 278 | 279 | type IntSlice [][]byte 280 | 281 | func (s IntSlice) Len() int { return len(s) } 282 | func (s IntSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 283 | func (s IntSlice) Less(i, j int) bool { return string(s[i]) < string(s[j]) } 284 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018. box.la authors. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![architecture](https://github.com/boxproject/voucher/blob/master/banner.png) 2 | 3 | # PRODUCT INFORMATION 4 | 5 | **Product name**: BOX 6 | 7 | **Description**: Enterprise-grade security solution for digital assets custody, using a cryptographically secure offline network. 8 | 9 | **Version**: 1.0 10 | 11 | [中文版说明](https://github.com/boxproject/voucher/blob/master/README_CN.md) 12 | 13 | # BRIEF MARKET INTRODUCTION 14 | 15 | Banking and financial organizations are paying more attention to cryptocurrency, in particular hedge funds. The financing scale of VC investing in digital assets has shoot up from USD 2 million in 2012 to USD 3.4 billion, representing an increase by 1672.5 times in 6 years. The accumulative financing scale of VC digital assets investment climbed from 3 in 2012 to 182 in 2018. Institutional investors and businesses are now entering a crypto market becoming mature because their interest in funds dealing with blockchain technology and cryptocurrencies has grown steadily in recent months. 16 | 17 | As a consequence, it is expected that funding for cryptocurrency funds may grow by 1,500% by Q4 2018 as digital assets are now outperforming traditional financial assets and various banking and financial organizations invest massively in blockchain and altcoins projects. International payment systems providers are working on developing digital transactions. This will allow and push the world’s largest financial and banking organizations to invest big figures, further expanding the market for digital coins and impacting their value. 18 | 19 | # REGULATORY CONSTRAINTS 20 | 21 | For years the digital assets market has go on unregulated, and recent actions that some government have taken to 22 | Steps that some governments have taken to prevail the risks of the crypto-market seem to have increased the confidence of institutional investors. Institutional investors prefer investing in markets containing regulatory frameworks. 23 | 24 | The U.S. Securities and Exchange Commission (SEC) have started implementing securities laws to regulate the crypto-market and protect customers. This has created an environment where this investor class can access to capital-intensive, long-term investment opportunities. 25 | 26 | # PROBLEMS FACED BY INSTITUTIONS 27 | 28 | When it comes to safekeeping, the investor class is highly relying either on personal wallets or cold wallets, causing many unexpected problems. While Mobile wallets are more practical and easier to use than other crypto wallet types thanks to its features of accepting and sending payments on the fly, phones and laptops are insecure devices that can be stolen, maliciously compromised (malware, keyloggers, etc.) or rooted. When it comes to Cold wallets, they require more effort to move cryptocurrencies around, also more technical knowledge. 29 | 30 | The security of such private wallets cannot be fully guaranteed against malicious issues. As for the compliance side, the use of personal wallets by organizations for managing digital currencies is not compatible with any local standardized financial management process, leading to confusions and mistakes with accounting documents filing. 31 | 32 | For security reasons, organizations have preferred to gradually shift from hot wallets to cold wallets. Due to its trawl characteristics and problems explain above, its security level remains higher than software wallets. In order to ensure the absolute safety of the cold wallet, they are usually kept in banks’ safe, which causes great inconvenience for making multiple transfers daily. In addition, operating transfers with a cold wallet can be complicated for non-professionals, resulting in inefficient use of the cold wallet or worst, mistakes. If the cold wallet contains an embedded multi-signature technology providing co-management of the private key, it also has some defects because the main chain wallets do not support this technology. 33 | 34 | Under the circumstances that institutions usually hold different cryptocurrencies, multi-signature poses some obstacles to manage the institutional digital assets. In addition, the introduction of multi-signature technologies in different main chains results in non-portability of digital asset management. More critical, there are loopholes in some of the main chains using multiple signatures, such as Ethereum's PARITY event. In conclusion, the market is in need for a comprehensive solution which protects efficiently the company’s digital assets. 35 | 36 | # SOLUTION WITH ISOMETRIC DEVICE MOCKUP 37 | 38 | BOX provide an enterprise-grade security solution for digital assets custody, using cryptographically secure offline network including flow of approvals, private blockchain technology and communication security. BOX achieve integration of technologies and fundamentally solve the industry security issues such as the theft of private keys and the falsification of directives. 39 | 40 | ![architecture](https://github.com/boxproject/voucher/blob/master/architecture.png) 41 | 42 | ![example](https://github.com/boxproject/voucher/blob/master/process.png) 43 | 44 | **Owning the private key of an account gives full access to the fund.** The dynamic password provides shared authority, a one-click activation and ensures the security of private keys. The BOX system uses a single private key to manage all cryptocurrencies. Theoretically, all public chains that support the ECDSA elliptic curve algorithm can be controlled with the private key. At this point, the BOX system is more convenient than multi-signature. Meanwhile, BOX uses a multi-person multi-password method to automatically generate a private key by using an algorithm in a signature machine, and then generate a public key from the private key. The partners who have the highest authority only have a part of the dynamic password which provides him with a shared governance on the private key. 45 | 46 | In terms of storage, we put the private key in the memory of the signature machine, without any persistent storage, thus making it extremely difficult to be captured. We lock the private key in the memory to prevent bypass attacks. In the event of a power outage, the BOX’s signature machine will automatically shut down the memory and the private key will disappear. 47 | 48 | Therefore, even if the signature machine is streaking, the chance of obtaining the private key from the BOX system is almost zero. The partner with the highest authority can instantly restore the original dynamic password by putting it in banks’ safe, in order to prevent a partner from accidentally failing to perform duties. Unlike cold wallets, there is no need to move this backup frequently. Only when a partner has an accident will he vote via the board to decide whether to enable password backup. 49 | 50 | The custom approval flow template uses the features of the blockchain that cannot be tampered with to be stored in the private chain. The template of the approval flow is defined by the enterprise itself. The content mainly includes the hierarchy, the initiation (approval), the minimum number of employees at each level, and the employee's public key (address). As a result, the hash value of the custom template and the template on the private chain both ensure that the approval flow cannot be modified. The private key APP will confirm its validity. When an employee initiates an approval flow, if the employee's private key and the address corresponding to the private key are matched, the approval process is matched with the approval flow template stored on the private chain through the associated program on the private chain. If it is in full compliance with the approval flow template, and then through the proxy (private key app interface) flows to the signing machine, before the transfer of the private key in the signing machine, and the hash of the approval flow template stored in the public chain is matched twice (currently The secondary matching of the public chain only supports Ethereum). After ensuring that there is no mistake, the private key in the signature machine will be transferred for transfer. In addition, BOX provides a unified public account for each company, so that the company's assets can be managed under one account for effective management. All digital assets will be traded through the account, preventing the case that public and private accounts are not separated. The approval flow also provides the basis for the audit, the company's managers can also clearly understand the company's assets through these records, and conduct a corresponding analysis. 51 | 52 | On the hardware side, deploying a BOX system requires at least 3(2n+1) cloud servers. Each cloud server acts as a node and builds a private chain. An Apple MACBOOK as a signing machine, because IOS is more secure than Windows. Several iPhones are needed to load the private key APP & employee APP. 53 | 54 | **One-stop integrated solution, BOX system will safeguard the security of investment firms, crypto-exchange platforms and other digital assets.** At present, the most suitable companies for BOX investment firms interested in Blockchain, Blockchain companies with audit risk control/compliance requirements, and trading platforms. Blockchain investment firms often transfer funds and receive payments frequently. It is inconvenient to use cold wallets. Personal wallets are not suitable for institutions. 55 | 56 | **The BOX code has been uploaded to GitHub, the largest technology open source community in the world, and it is necessary to build a healthier and safer industry environment together with the you.** Any individual or enterprise can use and deploy the system free of charge. In order to stimulate the first contributors of the BOX 0.1.0 version, the BOX team launched the “BOX Super Partner” program. Up to now, more than 30 organizations have signed letters of intent with the BOX Foundation. In the future, the BOX team will focus on community building and system scalability, and work with many organizations to build a healthier and safer industry environment. BOX's open source code on github has multiple repositories providing a complete set of deployable solutions. Including agent - private key APP management server, box-Authorizer - private key APP client, boxguard - signature machine daemon, voucher - access layer, companion - private chain side companion program, box-Staff-Manager - employee APP client End, box-appServer - Employee APP Server. 57 | 58 | ## Main functionalites 59 | 60 | There are 5 main functionalites for this voucher progam: 61 | 62 | 1. Communicate with proxy server on private chain 63 | 2. Upload realtime status 64 | 3. Receive request from private key app, complete offline signature and publish smart contract etc. 65 | 4. Submitted data will only be confirmed when transctions or approval flows initialized from private blockchain, signed and verified using RSA. 66 | 5. Monitor event log of Ethereum, confirm approval flows, topup and withdrawl and transfer result to proxy server. 67 | 68 | **How to use:** 69 | 70 | 1. Initialization. 71 | Use the following command for the first time run 72 | ```sh 73 | make build 74 | cp config.toml.example config.toml 75 | cp log.xml.example log.xml 76 | ``` 77 | Otherwise, use this command 78 | ```sh 79 | make rebuild 80 | ``` 81 | Note: build command will clear all data. 82 | 83 | 2. Update parameters (address to associated program, port, public key and certificate) in config.json respectively. 84 | 3. Update rpc api address to config.json 85 | 4. Run program 86 | 87 | 88 | **Run command in cli:** 89 | 90 | ```bash 91 | ➜ ./voucher 92 | ``` 93 | 94 | # Legal Reminder 95 | 96 | Exporting/importing and/or use of strong cryptography software, providing cryptography hooks, or even just communicating technical details about cryptography software is illegal in some parts of the world. If you import this software to your country, re-distribute it from there or even just email technical suggestions or provide source patches to the authors or other people you are strongly advised to pay close attention to any laws or regulations which apply to you. The authors of this software are not liable for any violations you make - it is your responsibility to be aware of and comply with any laws or regulations which apply to you. 97 | 98 | We would like to hear critics and feedbacks from you as it will help us to improve this open source project. 99 | -------------------------------------------------------------------------------- /trans/contracts/sink.sol: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | pragma solidity ^0.4.10; 16 | 17 | import "./oracle.sol"; 18 | 19 | // 本合约将记录审批流程签名哈希值以及确认提现申请 20 | // 正常流程如下: 21 | // 1. 调用 addHash 将审批流程签名哈希上链; 22 | // 2. 由授权APP触发调用 enable 方法来确认本次哈希有效; 23 | // 3. 提交提现申请 调用 approve 方法。 24 | // 每次调用都需要经过2/3个系统签发者确认,才认可本次提交的数据正确性。 25 | // 提现申请只能在审批流哈希上链之后,并且经过了授权APP的确认,才可以进行。 26 | // 异常情况下,假如原审批流程其中一个环节发生了丢失密钥导致无法签名,可以启用 27 | // 授权APP进行禁用该流程,再由原过程产生一个新流程。 28 | // 禁用流程如下: 29 | // 1. 调用 disable 方法。 30 | // 提现申请需要传入本次提现的交易哈希和交易数额。由于需要经过2/3个系统签发者的确认,故需要每次都确认数字要对得上。 31 | // 如果遇到其中一个有对不上的情况,则本次交易失败。 32 | // This contract will save signature hash of approval flow and confirm withdrawal request 33 | // A normal flow looks like: 34 | // 1. Invoke addHash method to upload hash value of approval flow to private chain; 35 | // 2. Manager App confirm the hash by invoking 'enable' method 36 | // 3. Submit withdraw request by invoking 'approve' method 37 | // Every invoktion has to be confirmed by at least 2/3 signers, and then we can confirm correctness of submission 38 | // Withdrawl cannot be performed until approval flow has been uploaded to private chain and confirmed by manager APP 39 | // If there is anything happen on any of node which result in signature failure, we can disable current flow using manager APP, and start a new flow. 40 | // Disable flow works like as follows: 41 | // 1. Invoke disable method 42 | // We have to send hash and amount as input parameters. Request has to be confirmed by at least 2/3 of signers, so the parameters have to be matched every time. 43 | // request will fail if any of these above doesn't match 44 | contract Sink { 45 | 46 | Oracle oracle; 47 | 48 | // 审批流的四个状态 49 | // ADD 审批流哈希上链 50 | // ENABLE 确认审批流 51 | // DISABLE 禁用审批流 52 | // APPLY 申请提现 53 | // {-- 审批流哈希上链 --- 授权者确认上链的审批流哈希 --- 申请提现 --} 54 | // 申请提现必须是审批流哈希确认之后,未确认的审批流是不能通过申请提现的。 55 | // Status of approval flow 56 | // ADD upload request to private chain 57 | // ENABLE confirm request 58 | // DISABLE disable request 59 | // APPLY withdraw request 60 | // Withdraw request will not be accepted until approval flow on private chain has been confirmed 61 | enum Stage { ADD, ENABLE, DISABLE, APPLY } 62 | 63 | struct Counter { 64 | // msg.sender => bool 65 | mapping (address => bool) checked; 66 | address[] signers; 67 | // stage counter; 68 | uint count; 69 | 70 | // for apply withdraw 71 | uint amount; 72 | uint category; 73 | bytes32 txHash; 74 | } 75 | 76 | struct Journal { 77 | bytes32 hash; 78 | bool enabled; 79 | 80 | bool setuped; 81 | bool executed; 82 | 83 | // Stage => Counter 84 | mapping (uint8 => Counter) stages; 85 | // 申请过的相同交易不能再次重复申请,必须确保每次的体现申请交易号必须不一样 86 | // cannot have duplicated transactions, make sure transaction id is unique 87 | mapping (bytes32 => bool) trans; 88 | } 89 | 90 | // sign flow hash => journal index 91 | mapping (bytes32 => uint) ids; 92 | Journal[] journals; 93 | bool init; 94 | 95 | /* 系统签发事件 */ 96 | event SignflowAdded(bytes32 hash, address lastConfirmed); 97 | event SignflowEnabled(bytes32 hash, address lastConfirmed); 98 | event SignflowDisabled(bytes32 hash, address lastConfirmed); 99 | // lastConfirmed 最后一个确认上链信息的signer地址 100 | event WithdrawApplied(bytes32 indexed hash,bytes32 indexed txHash, uint amount, uint fee, address recipient, uint category, address lastConfirmed); 101 | 102 | 103 | modifier onlySigner { 104 | require(oracle.isSigner(msg.sender)); 105 | _; 106 | } 107 | 108 | // 本合约只能是n个节点中任意一个系统签名帐号来创建 109 | // Contract has to be created by any signature account in node 110 | function Sink(Oracle ref) public { 111 | oracle = ref; 112 | stageVerify(Stage.ADD, 0, 0, 0, 0, 0, 0); 113 | init = true; 114 | } 115 | 116 | function addHash(bytes32 hash) public onlySigner returns(bool) { 117 | return stageVerify(Stage.ADD, hash, 0, 0, 0, 0, 0); 118 | } 119 | 120 | function enable(bytes32 hash) public onlySigner returns(bool) { 121 | return stageVerify(Stage.ENABLE, hash, 0, 0, 0, 0, 0); 122 | } 123 | 124 | function disable(bytes32 hash) public onlySigner returns(bool) { 125 | return stageVerify(Stage.DISABLE, hash, 0, 0, 0, 0, 0); 126 | } 127 | 128 | // txHash 用于确定某一次的交易 129 | // category 转账的类别 130 | // txHash, used for confirm one transaction 131 | // category, transaction category 132 | function approve(bytes32 txHash, uint amount, uint fee, address recipient, bytes32 hash, uint category) public onlySigner returns(bool) { 133 | return stageVerify(Stage.APPLY, hash, txHash, amount, fee, recipient, category); 134 | } 135 | 136 | // 校验审批流程哈希是否有效,只记录已经上链的审批流程哈希,不确定未经过2/3系统签发者 137 | // Verify hash of approval flow is valid or not, only record those are on the private chain 138 | function available(bytes32 hash) constant public returns(bytes32, bool) { 139 | uint id = ids[hash]; 140 | if (id == 0) { 141 | return (0x0, false); 142 | } 143 | 144 | return (journals[id].hash, journals[id].enabled); 145 | } 146 | 147 | // 只记录交易成功的交易哈希 148 | // Only record successful transaction hash 149 | function txExists(bytes32 hash, bytes32 txHash) constant public returns(bool) { 150 | uint id = ids[hash]; 151 | if (id == 0) { 152 | return false; 153 | } 154 | 155 | Journal storage journal = journals[id]; 156 | return journal.trans[txHash]; 157 | } 158 | 159 | // 只有原始oracle中的boss才能更改oracle自身 160 | // 同时,新的oracle必须也要是boss才能生成 161 | // Only boss in original oracle can update itself 162 | // Only boss can create new oracle 163 | function changeOracle(address newOracle) public { 164 | require(oracle.boss() == msg.sender); 165 | oracle = Oracle(newOracle); 166 | } 167 | 168 | function stageVerify(Stage stage, bytes32 hash, bytes32 txHash, uint amount, uint fee, address recipient, uint category) internal returns(bool) { 169 | uint id = ids[hash]; 170 | if (id == 0) { 171 | if (stage != Stage.ADD) { 172 | return false; 173 | } 174 | 175 | if (hash == 0 && init) { 176 | return false; 177 | } 178 | 179 | ids[hash] = journals.length; 180 | id = journals.length++; 181 | } 182 | 183 | Journal storage journal = journals[id]; 184 | 185 | if (stage == Stage.ADD) { 186 | if (!journal.setuped) { 187 | journal.hash = hash; 188 | journal.setuped = true; 189 | if (!mark(journal, stage, txHash, amount, category)) { 190 | return false; 191 | } 192 | 193 | // only one signer 194 | if (totalChecked(journal, stage) >= marginOfVotes()) { 195 | SignflowAdded(hash, msg.sender); 196 | journal.executed = true; 197 | } 198 | 199 | return true; 200 | } 201 | 202 | if (journal.executed || !mark(journal, stage, txHash, amount, category)) { 203 | return false; 204 | } 205 | } 206 | 207 | var (mustReturn, flag) = checkStageConditions(journal, stage, txHash, amount, category); 208 | if (mustReturn) { 209 | return flag; 210 | } 211 | 212 | if (stage == Stage.ADD) { 213 | SignflowAdded(hash, msg.sender); 214 | journal.executed = true; 215 | } else if (stage == Stage.ENABLE) { 216 | journal.enabled = true; 217 | reset(journal, stage); 218 | SignflowEnabled(hash, msg.sender); 219 | } else if (stage == Stage.DISABLE) { 220 | journal.enabled = false; 221 | reset(journal, stage); 222 | SignflowDisabled(hash, msg.sender); 223 | } else if (stage == Stage.APPLY) { 224 | journal.trans[txHash] = true; 225 | reset(journal, stage); 226 | WithdrawApplied(hash, txHash, amount, fee, recipient, category, msg.sender); 227 | } else { 228 | return false; 229 | } 230 | 231 | return true; 232 | } 233 | 234 | function checkStageConditions(Journal storage journal, Stage stage, bytes32 txHash, uint amount, uint category) internal returns(bool, bool) { 235 | if (stage == Stage.APPLY && journal.trans[txHash]) { 236 | return (true, false); 237 | } 238 | 239 | if (stage != Stage.ADD && (!journal.setuped || !journal.executed) ) { 240 | return (true, false); 241 | } 242 | 243 | if (stage != Stage.ADD && !mark(journal, stage, txHash, amount, category)) { 244 | return (true, false); 245 | } 246 | 247 | if (totalChecked(journal, stage) < marginOfVotes()) { 248 | return (true, true); 249 | } 250 | 251 | return (false, true); 252 | } 253 | 254 | function mark(Journal storage journal, Stage stage, bytes32 txHash, uint amount, uint category) internal returns(bool) { 255 | Counter storage counter = journal.stages[uint8(stage)]; 256 | if ((stage == Stage.ENABLE && journal.enabled) || (stage == Stage.DISABLE && !journal.enabled) || (stage == Stage.APPLY && !journal.enabled)) { 257 | return false; 258 | } 259 | 260 | if (isChecked(journal, stage)) { 261 | return false; 262 | } 263 | 264 | if (stage == Stage.APPLY && counter.count == 0) { 265 | counter.amount = amount; 266 | counter.txHash = txHash; 267 | counter.category = category; 268 | } else if (stage == Stage.APPLY && counter.count > 0) { 269 | require(counter.amount == amount && counter.txHash == txHash && counter.category == category); 270 | } 271 | 272 | counter.checked[msg.sender] = true; 273 | counter.count++; 274 | 275 | if (stage != Stage.ADD) { 276 | uint len = counter.signers.length++; 277 | counter.signers[len] = msg.sender; 278 | } 279 | return true; 280 | } 281 | 282 | function reset(Journal storage journal, Stage stage) internal { 283 | Counter storage counter = journal.stages[uint8(stage)]; 284 | 285 | uint i; 286 | uint len = counter.signers.length; 287 | for (i = 0; i < len; i++) { 288 | delete counter.checked[counter.signers[i]]; 289 | } 290 | 291 | counter.count = 0; 292 | counter.signers.length = 0; 293 | counter.amount = 0; 294 | counter.txHash = 0x0; 295 | } 296 | 297 | function isChecked(Journal storage journal, Stage stage) constant internal returns(bool) { 298 | Counter storage counter = journal.stages[uint8(stage)]; 299 | return counter.checked[msg.sender]; 300 | } 301 | 302 | function totalChecked(Journal storage journal, Stage stage) constant internal returns(uint) { 303 | Counter storage counter = journal.stages[uint8(stage)]; 304 | return counter.count; 305 | } 306 | 307 | // 服务的确认边界 308 | // Confirm border of services 309 | function marginOfVotes() constant internal returns(uint data) { 310 | uint totalNodes = oracle.totalEnabledNodes(); 311 | // 只有一个节点或两个节点时 312 | // Only one or two nodes 313 | if (totalNodes == 1 || totalNodes == 2) { 314 | return totalNodes; 315 | } 316 | 317 | uint mod = totalNodes % 2; 318 | if (mod != 0) { 319 | // 奇数时,满足 2n + 1 320 | // Odd, satisfy 2n+1 321 | data = totalNodes/2 + mod; 322 | } else { 323 | data = totalNodes/2 + 1; 324 | } 325 | 326 | return data; 327 | } 328 | 329 | } -------------------------------------------------------------------------------- /config/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | package config 15 | 16 | import ( 17 | "bytes" 18 | 19 | "encoding/json" 20 | "github.com/ethereum/go-ethereum/common" 21 | "math/big" 22 | "time" 23 | ) 24 | 25 | type ( 26 | MetaKey []byte 27 | InitStage []byte 28 | ) 29 | 30 | var ( 31 | // 初始状态 32 | // Init state 33 | INIT = MetaKey{0} 34 | // 服务器端的d 35 | // Server side secret key 36 | SECRET = MetaKey{1} 37 | // 一次性密码,计数 38 | // One-time password 39 | PASS = MetaKey{2} 40 | // 当前区块号 41 | // Current block 42 | BLKNUMBER = MetaKey{3} 43 | // 公链上的智能合约地址 44 | // Address on public chain 45 | BANKADDRESS = MetaKey{5} 46 | 47 | PRIVATEKEYHASH = MetaKey{6} 48 | // bitcoin master pubkey addr 49 | BTCMASTERPUBADDRESS = MetaKey{7} 50 | 51 | Inited = InitStage{1} 52 | 53 | DefPubEthGasLimit = 3000000 //默认gaslimit 54 | DefAesKey = []byte("abcdefghijklmnop") 55 | ) 56 | 57 | const ( 58 | TYPE_CONTRACT = "contract" 59 | TYPE_SERVICE = "service" 60 | ) 61 | 62 | //pri db key 63 | const ( 64 | HASH_LIST_PRIFIX = "v_hl_" //hash list 65 | WITHDRAW_APPLY_PRIFIX = "v_wa_" //withdraw apply 66 | PENDING_PRIDFIX = "p_" //pending 67 | ROUTER_PRIDFIX = "r_" //grpc router 68 | ) 69 | 70 | const ( 71 | AES_KEY = "aes_key" 72 | APP_KEY_PRIFIX = "app_key_" 73 | STATAUS_KEY = "status_key" 74 | TOKEN_PRIFIX = "token_key_" 75 | ) 76 | 77 | //pub db key 78 | const ( 79 | PUB_DB_BAK_PRIFIX = "pub_db_bak_" 80 | PUBKEY_ETH = "pub_key_eth" 81 | PUBKEY_BTC = "pub_key_btc" 82 | DEPOSIT_PRIFIX = "pub_dp_" 83 | WITHDRAW_PRIFIX = "pub_wd_" 84 | ALLOWKP_PRIFIX = "pub_al_" 85 | DISALLOW_PRIFIX = "pub_da_" 86 | WITHDRAW_TX_PRIFIX = "pub_wd_tx_" 87 | ) 88 | 89 | //确认、禁用请求 已作废 90 | const ( 91 | REQ_HASH = "hash" 92 | //REQ_HASH_DISALLOW = "disallow" 93 | REQ_CREATE = "create" 94 | REQ_DEPLOY = "deploy" 95 | REQ_START = "start" 96 | REQ_PAUSE = "pause" 97 | ) 98 | 99 | const ( 100 | Err_OK = "0" //正确 101 | Err_UNKONWN = "1" //未知的错误 102 | Err_MARSHAL = "10" //josn打包失败 103 | Err_DB = "11" //db失败 104 | Err_SERVER = "12" //服务未启动 105 | Err_SERVER_ON = "13" //服务已启动 106 | Err_ADDR = "14" //addr不存在 107 | Err_CONTRACT = "15" //合约未发布 108 | 109 | Err_ETH = "100" //链处理失败 110 | Err_UNENABLE_PREFIX = "101" //非法hash前缀 111 | Err_UNENABLE_LENGTH = "102" //非法hash值长度 112 | Err_KEY_RECOVER = "103" //生成key失败 113 | Err_CODE_ROLE_NULL = "104" //code为空 114 | Err_CODE_INCONS = "105" //code不一致 115 | 116 | Err_BTC = "106" //BTC创建失败 117 | 118 | ) 119 | 120 | const ( 121 | LAST_BLOCK_PRIFX = "lbp_" //block前缀 122 | ) 123 | 124 | const ( 125 | CHAN_MAX_SIZE = 100000 //chan 默认大小 126 | POOL_SIZE = 2 //账户池大小 127 | NONCE_PLUS = 1 //nonce值偏移值 128 | ) 129 | 130 | const ( 131 | ROUTER_TYPE_WEB = "web" 132 | ROUTER_TYPE_GRPC = "grpc" 133 | ) 134 | 135 | //grpc流类型 136 | const ( 137 | GRPC_HASH_ADD_REQ = "1" //hash add申请 138 | GRPC_HASH_ADD_LOG = "2" //hans add 私链log 139 | GRPC_HASH_ENABLE_REQ = "3" //hash enable 申请 140 | GRPC_HASH_ENABLE_LOG = "4" //hash enable 私链log 141 | GRPC_HASH_DISABLE_REQ = "5" //hash disable 申请 142 | GRPC_HASH_DISABLE_LOG = "6" //hash disable 私链log 143 | GRPC_WITHDRAW_REQ = "7" //提现 申请 144 | GRPC_WITHDRAW_LOG = "8" //提现 私链log 145 | GRPC_DEPOSIT_WEB = "9" //充值上报 146 | GRPC_WITHDRAW_TX_WEB = "10" //提现tx上报 147 | GRPC_WITHDRAW_WEB = "11" // 提现结果上报 148 | GRPC_VOUCHER_OPR_REQ = "12" // 签名机操作处理 149 | //GRPC_HASH_LIST_REQ = "13" // 审批流查询 150 | //GRPC_HASH_LIST_WEB = "14" // 审批流上报 151 | GRPC_TOKEN_LIST_WEB = "15" // token上报 152 | GRPC_COIN_LIST_WEB = "16" //coin上报 153 | GRPC_HASH_ENABLE_WEB = "17" //hash enable 公链log 154 | GRPC_HASH_DISABLE_WEB = "18" //hash enable 公链log 155 | 156 | GRPC_CHECK_KEY_WEB = "19" //密码验证 157 | ) 158 | 159 | //公链操作类型 160 | const ( 161 | ECR20_TYPE_ALLOW = "1" 162 | ECR20_TYPE_DISALLOW = "2" 163 | ECR20_TYPE_APPROVE = "3" 164 | BTC_TYPE_APPROVE = ECR20_TYPE_APPROVE 165 | ) 166 | 167 | //上报 168 | const ( 169 | REP_ACCOUNT_ADD = "1" 170 | REP_DEPOSIT = "2" 171 | REP_WITHDRAW = "3" //提现tx成功上报 172 | REP_WITHDRAW_TX = "4" //提现txid上报 173 | ) 174 | 175 | //签名机状态 176 | const ( 177 | VOUCHER_STATUS_UNCONNETED = 0 //未连接 178 | VOUCHER_STATUS_UNCREATED = 1 //未创建 179 | VOUCHER_STATUS_CREATED = 2 //已创建 180 | VOUCHER_STATUS_DEPLOYED = 3 //已发布 181 | VOUCHER_STATUS_STATED = 4 //已启动 182 | VOUCHER_STATUS_PAUSED = 5 //已停止 183 | ) 184 | 185 | //关键句状态 186 | const ( 187 | PASSWORD_STATUS_OK = 0 //成功 188 | PASSWORD_STATUS_FAILED = 1 //密码错误 189 | PASSWORD_CODE_FAILED = 2 //code不一致 190 | PASSWORD_SYSTEM_FAILED = 3 //系统异常 191 | ) 192 | 193 | const ( 194 | VOUCHER_OPERATE_ADDKEY = "0" //添加公钥 195 | VOUCHER_OPERATE_CREATE = "1" //创建 196 | VOUCHER_OPERATE_DEPLOY = "2" //发布 197 | VOUCHER_OPERATE_START = "3" //启动 198 | VOUCHER_OPERATE_PAUSE = "4" //停止 199 | VOUCHER_OPERATE_HASH_ENABLE = "5" //hash同意 200 | VOUCHER_OPERATE_HASH_DISABLE = "6" //hash拒绝 201 | VOUCHER_OPERATE_HASH_LIST = "7" //hash list 查询 202 | VOUCHER_OPERATE_TOKEN_ADD = "8" //token 添加 203 | VOUCHER_OPERATE_TOKEN_DEL = "9" //token 删除 204 | VOUCHER_OPERATE_TOKEN_LIST = "10" //token list 查询 205 | VOUCHER_OPERATE_COIN = "11" //coin 操作 206 | VOUCHER_OPERATE_CHECK_KEY = "12" //密码验证 207 | ) 208 | 209 | type BoxRecord struct { 210 | Allowed bool 211 | Address string 212 | Amount int64 213 | Hash string 214 | } 215 | 216 | type SignHashRecord struct { 217 | SignTmpHash common.Hash 218 | } 219 | 220 | type ApplyRecord struct { 221 | Signflow string 222 | SignTmplHash common.Hash 223 | TxHash common.Hash 224 | Amount *big.Int 225 | Fee *big.Int 226 | To common.Address 227 | IsERC20 bool 228 | } 229 | 230 | //grpc stream 231 | type GrpcStream struct { 232 | Type string 233 | BlockNumber uint64 //区块号 234 | AppId string //申请人 235 | Hash common.Hash 236 | WdHash common.Hash 237 | TxHash string 238 | Amount *big.Int 239 | Fee *big.Int 240 | Account string 241 | From string 242 | To string 243 | Category *big.Int 244 | Flow string //原始内容 245 | Sign string //签名信息 246 | WdFlow string //提现原始数据 247 | Status string 248 | VoucherOperate *Operate 249 | ApplyTime time.Time //申请时间 250 | TokenList []*TokenInfo 251 | SignInfos []*SignInfo 252 | } 253 | 254 | type TokenInfo struct { 255 | Status string 256 | TokenName string 257 | Decimals int64 258 | ContractAddr string 259 | Category int64 260 | } 261 | 262 | type SignInfo struct { 263 | AppId string 264 | Sign string 265 | } 266 | 267 | type Ecr20Record struct { 268 | Type string 269 | Hash common.Hash 270 | WdHash common.Hash 271 | Token common.Address 272 | To common.Address 273 | Amount *big.Int 274 | Category *big.Int //转账类型 275 | } 276 | 277 | //上公链操作 278 | var Ecr20RecordChan chan *Ecr20Record = make(chan *Ecr20Record, CHAN_MAX_SIZE) 279 | 280 | type BtcRecord struct { 281 | Type string 282 | //Info string 283 | From string 284 | WdHash common.Hash 285 | Txid string 286 | Handlefee *big.Int 287 | To string 288 | Amount *big.Int 289 | //Category *big.Int //转账类型 290 | } 291 | 292 | var BtcRecordChan chan *BtcRecord = make(chan *BtcRecord, CHAN_MAX_SIZE) 293 | 294 | // 295 | func (box *BoxRecord) EncodeToBytes() ([]byte, error) { 296 | boxJson, err := json.Marshal(box) 297 | if err != nil { 298 | return nil, err 299 | } 300 | return boxJson, nil 301 | } 302 | 303 | func DecodeToBoxRecord(data []byte) (*BoxRecord, error) { 304 | var box BoxRecord 305 | if err := json.Unmarshal(data, &box); err != nil { 306 | return nil, err 307 | } 308 | return &box, nil 309 | } 310 | 311 | func (meta MetaKey) Bytes() []byte { 312 | return []byte(meta) 313 | } 314 | 315 | func (stage InitStage) Bytes() []byte { 316 | return []byte(stage) 317 | } 318 | 319 | func (stage InitStage) Equals(other []byte) bool { 320 | return bytes.Compare(stage, other) == 0 321 | } 322 | 323 | const ( 324 | ACCOUNT_NO_USED = "0" 325 | ACCOUNT_USED = "1" 326 | ACCOUNT_BTC = "BTC" 327 | ACCOUNT_BTC_NO_USED = "BTC_0_" 328 | ACCOUNT_BTC_USED = "BTC_1_" 329 | ACCOUNT_BTC_COUNT = "countbtc" 330 | ACCOUNT_BTC_IMPORT = "importbtc" 331 | 332 | //ACCOUNT_NONCE = "nonce" 333 | ) 334 | const ( 335 | BTC_CURSOR = "CURSORBTC" 336 | ) 337 | 338 | const ( 339 | BTC_DB_REC = "DBBTCREC_" //充值 340 | BTC_DB_WITHDRAW = "DBBTCWD_" //WITHDRAW 提现,提币 341 | BTC_DB_WITHDRAW_0 = "DBBTCWD_0_" //WITHDRAW 提现,提币 交易未被确认 342 | BTC_DB_WITHDRAW_1 = "DBBTCWD_1_" //WITHDRAW 提现,提币 交易被确认 343 | ) 344 | const ( 345 | ACCOUNT_TYPE_ETH = "0" 346 | ACCOUNT_TYPE_BTC = "1" 347 | ) 348 | 349 | //审批流状态 350 | const ( 351 | HASH_STATUS_APPLY = "1" //申请 352 | HASH_STATUS_ENABLE = "2" //确认 353 | HASH_STATUS_DISABLE = "3" //禁用 354 | ) 355 | 356 | //确认、禁用请求 357 | //const ( 358 | // PASS_STATUS_0 = "0" 359 | // PASS_STATUS_1 = "1" 360 | // PASS_STATUS_2 = "2" 361 | // PASS_STATUS_3 = "3" 362 | //) 363 | 364 | //TODO 365 | //category 366 | const ( 367 | COIN_NAME_BTC = "BTC" 368 | COIN_NAME_ETH = "ETH" 369 | CATEGORY_BTC int64 = 0 370 | CATEGORY_ETH int64 = 1 371 | COIN_DECIMALS_BTC int64 = 8 372 | COIN_DECIMALS_ETH int64 = 18 373 | ) 374 | 375 | var AccountUsedMap map[string]string = make(map[string]string) 376 | 377 | var AccountUsedChan chan common.Address = make(chan common.Address, CHAN_MAX_SIZE) 378 | 379 | var ReportedChan chan *GrpcStream = make(chan *GrpcStream, CHAN_MAX_SIZE) 380 | 381 | //[hash]txid 382 | var UnconfirmedTxidList map[string]string = make(map[string]string) 383 | 384 | //BTC 385 | type BTCAddrState struct { 386 | Addr string 387 | IsUsed bool 388 | Index uint32 389 | IsImport bool 390 | } 391 | 392 | var AccountMapBtc map[string]*BTCAddrState = make(map[string]*BTCAddrState) 393 | 394 | var AccountImpChanBtc chan *BTCAddrState = make(chan *BTCAddrState, CHAN_MAX_SIZE) 395 | 396 | var AccountUsedChanBtc chan string = make(chan string, CHAN_MAX_SIZE) 397 | 398 | //私钥-签名机操作 399 | type Operate struct { 400 | Type string 401 | AppId string //appid 402 | AppName string //app别名 403 | Hash string 404 | Password string 405 | ReqIpPort string 406 | Code string 407 | PublicKey string //加密后公钥 408 | TokenName string 409 | Decimals int64 410 | ContractAddr string 411 | CoinCategory int64 //币种分类 412 | CoinUsed bool //币种使用 413 | Sign string //签名 414 | PassSign string //密码签名 415 | } 416 | 417 | var OperateChan chan *Operate = make(chan *Operate, CHAN_MAX_SIZE) 418 | 419 | //签名机状态 420 | type VoucherStatus struct { 421 | ServerStatus int //系统状态 422 | Status int //错误码状态 423 | Total int //密钥数量 424 | HashCount int //hash数量 425 | TokenCount int //token数量 426 | Address string //账户地址 427 | ContractAddress string //合约地址 428 | BtcAddress string //比特币地址 429 | D string //随机数 430 | NodesAuthorized []NodeAuthorized //授权情况 431 | KeyStoreStatus []KeyStoreStatu //公约添加状态 432 | CoinStatus []CoinStatu //币种使用状态 433 | } 434 | 435 | type NodeAuthorized struct { 436 | ApplyerId string 437 | Authorized bool 438 | } 439 | 440 | type KeyStoreStatu struct { 441 | ApplyerId string 442 | ApplyerName string 443 | } 444 | 445 | type CoinStatu struct { 446 | Name string 447 | Category int64 448 | Decimals int64 449 | Used bool 450 | } 451 | 452 | //实时状态 453 | var RealTimeStatus *VoucherStatus = &VoucherStatus{} 454 | 455 | //hash审批流配置模版信息 456 | type HashFlow struct { 457 | Flow_name string 458 | Single_limit string 459 | Approval_info []ApprovalInfo 460 | } 461 | 462 | type ApprovalInfo struct { 463 | Require int64 464 | Total int64 465 | Approvers []Approver 466 | } 467 | 468 | type Approver struct { 469 | Account string 470 | ItemType int64 471 | Pub_key string 472 | App_account_id string 473 | } 474 | 475 | type EmployeeFlow struct { 476 | Appid string 477 | Sign string 478 | } 479 | -------------------------------------------------------------------------------- /pb/protocol.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: pb/protocol.proto 3 | 4 | /* 5 | Package pb is a generated protocol buffer package. 6 | 7 | It is generated from these files: 8 | pb/protocol.proto 9 | 10 | It has these top-level messages: 11 | RouterRequest 12 | RouterResponse 13 | HeartRequest 14 | HeartResponse 15 | ListenReq 16 | StreamRsp 17 | */ 18 | package pb 19 | 20 | import proto "github.com/golang/protobuf/proto" 21 | import fmt "fmt" 22 | import math "math" 23 | 24 | import ( 25 | context "golang.org/x/net/context" 26 | grpc "google.golang.org/grpc" 27 | ) 28 | 29 | // Reference imports to suppress errors if they are not otherwise used. 30 | var _ = proto.Marshal 31 | var _ = fmt.Errorf 32 | var _ = math.Inf 33 | 34 | // This is a compile-time assertion to ensure that this generated file 35 | // is compatible with the proto package it is being compiled against. 36 | // A compilation error at this line likely means your copy of the 37 | // proto package needs to be updated. 38 | const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package 39 | 40 | type RouterRequest struct { 41 | RouterType string `protobuf:"bytes,1,opt,name=routerType" json:"routerType,omitempty"` 42 | RouterName string `protobuf:"bytes,2,opt,name=routerName" json:"routerName,omitempty"` 43 | Msg []byte `protobuf:"bytes,3,opt,name=msg,proto3" json:"msg,omitempty"` 44 | } 45 | 46 | func (m *RouterRequest) Reset() { *m = RouterRequest{} } 47 | func (m *RouterRequest) String() string { return proto.CompactTextString(m) } 48 | func (*RouterRequest) ProtoMessage() {} 49 | func (*RouterRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } 50 | 51 | func (m *RouterRequest) GetRouterType() string { 52 | if m != nil { 53 | return m.RouterType 54 | } 55 | return "" 56 | } 57 | 58 | func (m *RouterRequest) GetRouterName() string { 59 | if m != nil { 60 | return m.RouterName 61 | } 62 | return "" 63 | } 64 | 65 | func (m *RouterRequest) GetMsg() []byte { 66 | if m != nil { 67 | return m.Msg 68 | } 69 | return nil 70 | } 71 | 72 | type RouterResponse struct { 73 | Code string `protobuf:"bytes,1,opt,name=code" json:"code,omitempty"` 74 | } 75 | 76 | func (m *RouterResponse) Reset() { *m = RouterResponse{} } 77 | func (m *RouterResponse) String() string { return proto.CompactTextString(m) } 78 | func (*RouterResponse) ProtoMessage() {} 79 | func (*RouterResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } 80 | 81 | func (m *RouterResponse) GetCode() string { 82 | if m != nil { 83 | return m.Code 84 | } 85 | return "" 86 | } 87 | 88 | type HeartRequest struct { 89 | RouterType string `protobuf:"bytes,1,opt,name=routerType" json:"routerType,omitempty"` 90 | ServerName string `protobuf:"bytes,2,opt,name=serverName" json:"serverName,omitempty"` 91 | Name string `protobuf:"bytes,3,opt,name=name" json:"name,omitempty"` 92 | Ip string `protobuf:"bytes,4,opt,name=ip" json:"ip,omitempty"` 93 | Msg []byte `protobuf:"bytes,5,opt,name=msg,proto3" json:"msg,omitempty"` 94 | } 95 | 96 | func (m *HeartRequest) Reset() { *m = HeartRequest{} } 97 | func (m *HeartRequest) String() string { return proto.CompactTextString(m) } 98 | func (*HeartRequest) ProtoMessage() {} 99 | func (*HeartRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} } 100 | 101 | func (m *HeartRequest) GetRouterType() string { 102 | if m != nil { 103 | return m.RouterType 104 | } 105 | return "" 106 | } 107 | 108 | func (m *HeartRequest) GetServerName() string { 109 | if m != nil { 110 | return m.ServerName 111 | } 112 | return "" 113 | } 114 | 115 | func (m *HeartRequest) GetName() string { 116 | if m != nil { 117 | return m.Name 118 | } 119 | return "" 120 | } 121 | 122 | func (m *HeartRequest) GetIp() string { 123 | if m != nil { 124 | return m.Ip 125 | } 126 | return "" 127 | } 128 | 129 | func (m *HeartRequest) GetMsg() []byte { 130 | if m != nil { 131 | return m.Msg 132 | } 133 | return nil 134 | } 135 | 136 | type HeartResponse struct { 137 | Code string `protobuf:"bytes,1,opt,name=code" json:"code,omitempty"` 138 | } 139 | 140 | func (m *HeartResponse) Reset() { *m = HeartResponse{} } 141 | func (m *HeartResponse) String() string { return proto.CompactTextString(m) } 142 | func (*HeartResponse) ProtoMessage() {} 143 | func (*HeartResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} } 144 | 145 | func (m *HeartResponse) GetCode() string { 146 | if m != nil { 147 | return m.Code 148 | } 149 | return "" 150 | } 151 | 152 | // The request listen 153 | type ListenReq struct { 154 | ServerName string `protobuf:"bytes,1,opt,name=serverName" json:"serverName,omitempty"` 155 | Name string `protobuf:"bytes,2,opt,name=name" json:"name,omitempty"` 156 | Ip string `protobuf:"bytes,3,opt,name=ip" json:"ip,omitempty"` 157 | } 158 | 159 | func (m *ListenReq) Reset() { *m = ListenReq{} } 160 | func (m *ListenReq) String() string { return proto.CompactTextString(m) } 161 | func (*ListenReq) ProtoMessage() {} 162 | func (*ListenReq) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} } 163 | 164 | func (m *ListenReq) GetServerName() string { 165 | if m != nil { 166 | return m.ServerName 167 | } 168 | return "" 169 | } 170 | 171 | func (m *ListenReq) GetName() string { 172 | if m != nil { 173 | return m.Name 174 | } 175 | return "" 176 | } 177 | 178 | func (m *ListenReq) GetIp() string { 179 | if m != nil { 180 | return m.Ip 181 | } 182 | return "" 183 | } 184 | 185 | // The stream rsp 186 | type StreamRsp struct { 187 | Msg []byte `protobuf:"bytes,1,opt,name=msg,proto3" json:"msg,omitempty"` 188 | } 189 | 190 | func (m *StreamRsp) Reset() { *m = StreamRsp{} } 191 | func (m *StreamRsp) String() string { return proto.CompactTextString(m) } 192 | func (*StreamRsp) ProtoMessage() {} 193 | func (*StreamRsp) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} } 194 | 195 | func (m *StreamRsp) GetMsg() []byte { 196 | if m != nil { 197 | return m.Msg 198 | } 199 | return nil 200 | } 201 | 202 | func init() { 203 | proto.RegisterType((*RouterRequest)(nil), "pb.RouterRequest") 204 | proto.RegisterType((*RouterResponse)(nil), "pb.RouterResponse") 205 | proto.RegisterType((*HeartRequest)(nil), "pb.HeartRequest") 206 | proto.RegisterType((*HeartResponse)(nil), "pb.HeartResponse") 207 | proto.RegisterType((*ListenReq)(nil), "pb.ListenReq") 208 | proto.RegisterType((*StreamRsp)(nil), "pb.StreamRsp") 209 | } 210 | 211 | // Reference imports to suppress errors if they are not otherwise used. 212 | var _ context.Context 213 | var _ grpc.ClientConn 214 | 215 | // This is a compile-time assertion to ensure that this generated file 216 | // is compatible with the grpc package it is being compiled against. 217 | const _ = grpc.SupportPackageIsVersion4 218 | 219 | // Client API for Synchronizer service 220 | 221 | type SynchronizerClient interface { 222 | Router(ctx context.Context, in *RouterRequest, opts ...grpc.CallOption) (*RouterResponse, error) 223 | Heart(ctx context.Context, in *HeartRequest, opts ...grpc.CallOption) (*HeartResponse, error) 224 | Listen(ctx context.Context, opts ...grpc.CallOption) (Synchronizer_ListenClient, error) 225 | } 226 | 227 | type synchronizerClient struct { 228 | cc *grpc.ClientConn 229 | } 230 | 231 | func NewSynchronizerClient(cc *grpc.ClientConn) SynchronizerClient { 232 | return &synchronizerClient{cc} 233 | } 234 | 235 | func (c *synchronizerClient) Router(ctx context.Context, in *RouterRequest, opts ...grpc.CallOption) (*RouterResponse, error) { 236 | out := new(RouterResponse) 237 | err := grpc.Invoke(ctx, "/pb.Synchronizer/router", in, out, c.cc, opts...) 238 | if err != nil { 239 | return nil, err 240 | } 241 | return out, nil 242 | } 243 | 244 | func (c *synchronizerClient) Heart(ctx context.Context, in *HeartRequest, opts ...grpc.CallOption) (*HeartResponse, error) { 245 | out := new(HeartResponse) 246 | err := grpc.Invoke(ctx, "/pb.Synchronizer/heart", in, out, c.cc, opts...) 247 | if err != nil { 248 | return nil, err 249 | } 250 | return out, nil 251 | } 252 | 253 | func (c *synchronizerClient) Listen(ctx context.Context, opts ...grpc.CallOption) (Synchronizer_ListenClient, error) { 254 | stream, err := grpc.NewClientStream(ctx, &_Synchronizer_serviceDesc.Streams[0], c.cc, "/pb.Synchronizer/listen", opts...) 255 | if err != nil { 256 | return nil, err 257 | } 258 | x := &synchronizerListenClient{stream} 259 | return x, nil 260 | } 261 | 262 | type Synchronizer_ListenClient interface { 263 | Send(*ListenReq) error 264 | Recv() (*StreamRsp, error) 265 | grpc.ClientStream 266 | } 267 | 268 | type synchronizerListenClient struct { 269 | grpc.ClientStream 270 | } 271 | 272 | func (x *synchronizerListenClient) Send(m *ListenReq) error { 273 | return x.ClientStream.SendMsg(m) 274 | } 275 | 276 | func (x *synchronizerListenClient) Recv() (*StreamRsp, error) { 277 | m := new(StreamRsp) 278 | if err := x.ClientStream.RecvMsg(m); err != nil { 279 | return nil, err 280 | } 281 | return m, nil 282 | } 283 | 284 | // Server API for Synchronizer service 285 | 286 | type SynchronizerServer interface { 287 | Router(context.Context, *RouterRequest) (*RouterResponse, error) 288 | Heart(context.Context, *HeartRequest) (*HeartResponse, error) 289 | Listen(Synchronizer_ListenServer) error 290 | } 291 | 292 | func RegisterSynchronizerServer(s *grpc.Server, srv SynchronizerServer) { 293 | s.RegisterService(&_Synchronizer_serviceDesc, srv) 294 | } 295 | 296 | func _Synchronizer_Router_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 297 | in := new(RouterRequest) 298 | if err := dec(in); err != nil { 299 | return nil, err 300 | } 301 | if interceptor == nil { 302 | return srv.(SynchronizerServer).Router(ctx, in) 303 | } 304 | info := &grpc.UnaryServerInfo{ 305 | Server: srv, 306 | FullMethod: "/pb.Synchronizer/Router", 307 | } 308 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 309 | return srv.(SynchronizerServer).Router(ctx, req.(*RouterRequest)) 310 | } 311 | return interceptor(ctx, in, info, handler) 312 | } 313 | 314 | func _Synchronizer_Heart_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 315 | in := new(HeartRequest) 316 | if err := dec(in); err != nil { 317 | return nil, err 318 | } 319 | if interceptor == nil { 320 | return srv.(SynchronizerServer).Heart(ctx, in) 321 | } 322 | info := &grpc.UnaryServerInfo{ 323 | Server: srv, 324 | FullMethod: "/pb.Synchronizer/Heart", 325 | } 326 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 327 | return srv.(SynchronizerServer).Heart(ctx, req.(*HeartRequest)) 328 | } 329 | return interceptor(ctx, in, info, handler) 330 | } 331 | 332 | func _Synchronizer_Listen_Handler(srv interface{}, stream grpc.ServerStream) error { 333 | return srv.(SynchronizerServer).Listen(&synchronizerListenServer{stream}) 334 | } 335 | 336 | type Synchronizer_ListenServer interface { 337 | Send(*StreamRsp) error 338 | Recv() (*ListenReq, error) 339 | grpc.ServerStream 340 | } 341 | 342 | type synchronizerListenServer struct { 343 | grpc.ServerStream 344 | } 345 | 346 | func (x *synchronizerListenServer) Send(m *StreamRsp) error { 347 | return x.ServerStream.SendMsg(m) 348 | } 349 | 350 | func (x *synchronizerListenServer) Recv() (*ListenReq, error) { 351 | m := new(ListenReq) 352 | if err := x.ServerStream.RecvMsg(m); err != nil { 353 | return nil, err 354 | } 355 | return m, nil 356 | } 357 | 358 | var _Synchronizer_serviceDesc = grpc.ServiceDesc{ 359 | ServiceName: "pb.Synchronizer", 360 | HandlerType: (*SynchronizerServer)(nil), 361 | Methods: []grpc.MethodDesc{ 362 | { 363 | MethodName: "router", 364 | Handler: _Synchronizer_Router_Handler, 365 | }, 366 | { 367 | MethodName: "heart", 368 | Handler: _Synchronizer_Heart_Handler, 369 | }, 370 | }, 371 | Streams: []grpc.StreamDesc{ 372 | { 373 | StreamName: "listen", 374 | Handler: _Synchronizer_Listen_Handler, 375 | ServerStreams: true, 376 | ClientStreams: true, 377 | }, 378 | }, 379 | Metadata: "pb/protocol.proto", 380 | } 381 | 382 | func init() { proto.RegisterFile("pb/protocol.proto", fileDescriptor0) } 383 | 384 | var fileDescriptor0 = []byte{ 385 | // 304 bytes of a gzipped FileDescriptorProto 386 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x92, 0xdf, 0x4a, 0xc3, 0x30, 387 | 0x14, 0xc6, 0x97, 0x76, 0x1b, 0xec, 0xb0, 0x8d, 0xed, 0x5c, 0x95, 0x81, 0x32, 0xa2, 0x17, 0xbb, 388 | 0x90, 0xfa, 0xef, 0x25, 0xbc, 0x10, 0x85, 0xcc, 0x17, 0x68, 0xeb, 0xc1, 0x15, 0xd6, 0x26, 0x4b, 389 | 0x32, 0x61, 0x5e, 0xfb, 0x1e, 0xbe, 0xaa, 0x24, 0xb5, 0x7f, 0xa6, 0x28, 0x78, 0xf7, 0xf5, 0xfb, 390 | 0x4a, 0xce, 0xef, 0x3b, 0x09, 0xcc, 0x55, 0x7a, 0xa9, 0xb4, 0xb4, 0x32, 0x93, 0xdb, 0xd8, 0x0b, 391 | 0x0c, 0x54, 0xca, 0x13, 0x98, 0x08, 0xb9, 0xb7, 0xa4, 0x05, 0xed, 0xf6, 0x64, 0x2c, 0x9e, 0x02, 392 | 0x68, 0x6f, 0x3c, 0x1d, 0x14, 0x45, 0x6c, 0xc9, 0x56, 0x23, 0xd1, 0x71, 0xda, 0xfc, 0x21, 0x29, 393 | 0x28, 0x0a, 0xba, 0xb9, 0x73, 0x70, 0x06, 0x61, 0x61, 0x5e, 0xa2, 0x70, 0xc9, 0x56, 0x63, 0xe1, 394 | 0x24, 0x3f, 0x87, 0x69, 0x3d, 0xc2, 0x28, 0x59, 0x1a, 0x42, 0x84, 0x7e, 0x26, 0x9f, 0xeb, 0xd3, 395 | 0xbd, 0xe6, 0xef, 0x0c, 0xc6, 0x77, 0x94, 0x68, 0xfb, 0x0f, 0x10, 0x43, 0xfa, 0xf5, 0x18, 0xa4, 396 | 0x75, 0xdc, 0x90, 0xd2, 0x25, 0x61, 0x35, 0xc4, 0x69, 0x9c, 0x42, 0x90, 0xab, 0xa8, 0xef, 0x9d, 397 | 0x20, 0x57, 0x35, 0xec, 0xa0, 0x85, 0x3d, 0x83, 0xc9, 0x17, 0xc5, 0x1f, 0xac, 0x8f, 0x30, 0xba, 398 | 0xcf, 0x8d, 0xa5, 0x52, 0xd0, 0xee, 0x1b, 0x07, 0xfb, 0x95, 0x23, 0xf8, 0xc1, 0x11, 0xd6, 0x1c, 399 | 0xfc, 0x04, 0x46, 0x6b, 0xab, 0x29, 0x29, 0x84, 0x69, 0xa0, 0x58, 0x03, 0x75, 0xf3, 0xc1, 0x60, 400 | 0xbc, 0x3e, 0x94, 0xd9, 0x46, 0xcb, 0x32, 0x7f, 0x23, 0x8d, 0xd7, 0x30, 0xac, 0x36, 0x81, 0xf3, 401 | 0x58, 0xa5, 0xf1, 0xd1, 0x0d, 0x2e, 0xb0, 0x6b, 0x55, 0x2d, 0x78, 0x0f, 0x63, 0x18, 0x6c, 0x5c, 402 | 0x31, 0x9c, 0xb9, 0xb8, 0xbb, 0xe9, 0xc5, 0xbc, 0xe3, 0x34, 0xff, 0x5f, 0xc0, 0x70, 0xeb, 0x3b, 403 | 0xe2, 0xc4, 0xc5, 0x4d, 0xdf, 0x85, 0xff, 0x6c, 0x68, 0x79, 0x6f, 0xc5, 0xae, 0x58, 0x3a, 0xf4, 404 | 0x2f, 0xea, 0xf6, 0x33, 0x00, 0x00, 0xff, 0xff, 0x4b, 0x48, 0xf9, 0x2a, 0x66, 0x02, 0x00, 0x00, 405 | } 406 | -------------------------------------------------------------------------------- /trans/watcher.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package trans 16 | 17 | import ( 18 | "context" 19 | "math/big" 20 | "time" 21 | 22 | log "github.com/alecthomas/log4go" 23 | "github.com/boxproject/voucher/common" 24 | "github.com/boxproject/voucher/config" 25 | "github.com/boxproject/voucher/localdb" 26 | "github.com/boxproject/voucher/token" 27 | "github.com/boxproject/voucher/util" 28 | "github.com/ethereum/go-ethereum" 29 | "github.com/ethereum/go-ethereum/core/types" 30 | "github.com/ethereum/go-ethereum/ethclient" 31 | "github.com/ethereum/go-ethereum/rpc" 32 | "github.com/pkg/errors" 33 | ) 34 | 35 | var ( 36 | depositEvent = common.SignEvent("Deposit(address,uint256)") 37 | withdrawEvent = common.SignEvent("Withdraw(address,uint256)") 38 | allowEvent = common.SignEvent("AllowFlow(bytes32)") 39 | disallowEvent = common.SignEvent("DisallowFlow(bytes32)") 40 | erc20Event = common.SignEvent("Transfer(address,address,uint256)") 41 | walletEvent = common.SignEvent("WalletS(address)") 42 | ) 43 | 44 | type EventHandler interface { 45 | Name() common.Hash 46 | Scan(log *common.EtherLog) error 47 | } 48 | 49 | type State interface { 50 | GetContractAddress() (common.Address, error) 51 | SetContractAddress(common.Address) error 52 | Save(key []byte, value []byte) error 53 | Load(key []byte) ([]byte, error) 54 | } 55 | 56 | type lvldbState struct { 57 | db localdb.Database 58 | } 59 | 60 | func (s *lvldbState) GetContractAddress() (addr common.Address, err error) { 61 | var bytes []byte 62 | bytes, err = s.db.Get(config.BANKADDRESS) 63 | //if err == verrors.NoDataErr { 64 | // return addr, nil 65 | //} 66 | if err != nil { 67 | return addr, err 68 | } 69 | addr.SetBytes(bytes) 70 | 71 | return addr, nil 72 | } 73 | 74 | func (s *lvldbState) SetContractAddress(addr common.Address) (err error) { 75 | return s.db.Put(config.BANKADDRESS, addr.Bytes()) 76 | } 77 | 78 | func (s *lvldbState) Save(key []byte, value []byte) error { 79 | return s.db.Put(key, value) 80 | } 81 | 82 | func (s *lvldbState) Load(key []byte) ([]byte, error) { 83 | return s.db.Get(key) 84 | } 85 | 86 | type HandlerContext struct { 87 | Events map[common.Hash]EventHandler 88 | State State 89 | EtherURL string 90 | Retries int 91 | DelayedBlocks *big.Int 92 | CursorBlocks *big.Int 93 | ContractAddress common.Address 94 | Db localdb.Database 95 | BlockNoFilePath string 96 | NonceFilePath string 97 | } 98 | 99 | func (hc *HandlerContext) addListener(handlers ...EventHandler) { 100 | for _, handler := range handlers { 101 | hc.Events[handler.Name()] = handler 102 | } 103 | } 104 | 105 | type EthHandler struct { 106 | client *ethclient.Client 107 | conf *HandlerContext 108 | ethQuitCh chan struct{} 109 | quitCh chan struct{} 110 | events map[common.Hash]EventHandler 111 | accountHandler *AccountHandler 112 | BtcHandler *BtcHandler 113 | } 114 | 115 | func (w *EthHandler) loadAddr() error { 116 | caddr, err := w.conf.State.GetContractAddress() 117 | log.Debug("loadAddr addr:", caddr.Hex()) 118 | if err != nil { 119 | return err 120 | } 121 | w.conf.ContractAddress = caddr 122 | rpcConn, err := rpc.Dial(w.conf.EtherURL) 123 | if err != nil { 124 | return err 125 | } 126 | 127 | w.client = ethclient.NewClient(rpcConn) 128 | if len(w.conf.Events) == 0 { 129 | w.conf.addListener( 130 | newDepositHandler(w.conf, w.client), 131 | newAllowHandler(w.conf), 132 | newDisallowHandler(w.conf), 133 | newWithdrawHandler(w.conf), 134 | newERC20Handler(w.conf, w.client)) 135 | } 136 | w.quitCh = make(chan struct{}, 1) 137 | w.ethQuitCh = make(chan struct{}, 1) 138 | 139 | addrByte ,_ := w.client.CodeAt(context.Background(),caddr,nil) 140 | if len(addrByte) <= 0 { 141 | return errors.New("contract not ready..") 142 | } 143 | 144 | return nil 145 | } 146 | 147 | func (w *EthHandler) Start() error { 148 | if err := w.loadAddr(); err != nil { 149 | return err 150 | } 151 | 152 | go w.asyStart() 153 | return nil 154 | } 155 | 156 | //TODO 157 | func (w *EthHandler) BtcStart() error { 158 | if err := w.BtcHandler.Start(); err != nil { 159 | return err 160 | } 161 | return nil 162 | } 163 | 164 | func (w *EthHandler) BtcStop() error { 165 | w.BtcHandler.Stop() 166 | return nil 167 | } 168 | 169 | func (w *EthHandler) asyStart() { 170 | w.reloadBlock() //加载未处理区块 171 | go w.listen() 172 | go w.ethChainHandler() //公链操作 173 | //go w.accountHandler.Start() //生成账号 174 | } 175 | 176 | func (w *EthHandler) reloadBlock() error { 177 | log.Debug("Block file: %s", w.conf.BlockNoFilePath) 178 | 179 | cursorBlkNumber, err := util.ReadNumberFromFile(w.conf.BlockNoFilePath) //block cfg 180 | if err != nil { 181 | //return err 182 | } 183 | 184 | // 获取当前节点上最大区块号 185 | blk, err := w.client.BlockByNumber(context.Background(), nil) 186 | if err != nil { 187 | return err 188 | } 189 | 190 | maxBlkNumber := blk.Number() 191 | 192 | nonce, err := w.client.NonceAt(context.Background(), common.HexToAddress(config.RealTimeStatus.Address), maxBlkNumber) 193 | if err != nil { //设置初始值nonce值 194 | log.Error("get nonce err: %s", err) 195 | } 196 | util.WriteNumberToFile(w.conf.NonceFilePath, big.NewInt(int64(nonce))) 197 | 198 | if cursorBlkNumber.Int64() <= 0 { //文件数据有误 199 | cursorBlkNumber = maxBlkNumber 200 | util.WriteNumberToFile(w.conf.BlockNoFilePath, cursorBlkNumber) 201 | } else { 202 | // 获取向前推N个区块的big.Int值 203 | checkBefore := w.conf.DelayedBlocks 204 | log.Debug("before:: max blkNumber: %s, cursor blkNumber: %s", maxBlkNumber.String(), cursorBlkNumber.String()) 205 | 206 | for big.NewInt(0).Sub(maxBlkNumber, cursorBlkNumber).Int64() >= checkBefore.Int64() { 207 | select { 208 | case _,isClose := <- w.ethQuitCh: 209 | if !isClose { 210 | log.Info("[CHANNEL CLOSED]reloadBlock thread exitCh!") 211 | return nil 212 | } 213 | default: 214 | if err = w.checkBlock(new(big.Int).Add(cursorBlkNumber, checkBefore)); err != nil { 215 | log.Error("checkBlock error:%v",err) 216 | continue 217 | //return err 218 | } 219 | cursorBlkNumber = new(big.Int).Add(cursorBlkNumber, big.NewInt(1)) 220 | } 221 | 222 | } 223 | } 224 | 225 | log.Debug("reloadBlock:: cursor blkNumber: %s", cursorBlkNumber.String()) 226 | return nil 227 | } 228 | 229 | //TODO 230 | // 上公链操作 231 | func (w *EthHandler) ethChainHandler() { 232 | log.Debug("ethChainHandler start...") 233 | //处理未完成pending 234 | loop := true 235 | for loop { 236 | select { 237 | case _,isClose := <- w.ethQuitCh: 238 | if !isClose { 239 | log.Info("[CHANNEL CLOSED]ethChainHandler thread exitCh!") 240 | loop = false 241 | } 242 | case data, ok := <-config.Ecr20RecordChan: 243 | if ok { 244 | switch data.Type { 245 | case config.ECR20_TYPE_ALLOW: 246 | w.ethAllowHandler(data) 247 | case config.ECR20_TYPE_DISALLOW: 248 | w.ethDisAllowHandler(data) 249 | case config.ECR20_TYPE_APPROVE: 250 | w.ethTransferHandler(data) 251 | default: 252 | log.Info("unknow req:%v", data) 253 | } 254 | } else { 255 | log.Error("read from channel failed") 256 | } 257 | } 258 | } 259 | } 260 | 261 | //上公链操作-同意 262 | func (w *EthHandler) ethAllowHandler(record *config.Ecr20Record) error { 263 | log.Debug("ethAllowHandler...hash:%v", record.Hash.Hex()) 264 | util.NoRWMutex.Lock() 265 | defer util.NoRWMutex.Unlock() 266 | bank, err := NewBank(w.conf.ContractAddress, w.client) 267 | 268 | if err != nil { 269 | log.Error("NewBank error:", err) 270 | return err 271 | } 272 | opts := NewKeyedTransactor() 273 | nonce, err := util.ReadNumberFromFile(w.conf.NonceFilePath) //nonce file 274 | if err != nil { 275 | log.Error("read block info err :%s", err) 276 | return err 277 | } 278 | 279 | opts.Nonce = nonce 280 | log.Debug("current nonce :%d", nonce.Int64()) 281 | if tx, err := bank.Allow(opts, record.Hash); err != nil { 282 | log.Error("bank allow err :%s", err) 283 | } else { 284 | log.Info("eth allow :%v", tx.Hash().Hex()) 285 | nonce = nonce.Add(nonce, big.NewInt(config.NONCE_PLUS)) 286 | util.WriteNumberToFile(w.conf.NonceFilePath, nonce) 287 | } 288 | 289 | return nil 290 | } 291 | 292 | //上公链操作-禁用 293 | func (w *EthHandler) ethDisAllowHandler(record *config.Ecr20Record) error { 294 | log.Debug("ethDisAllowHandler...hash:%v", record.Hash.Hex()) 295 | util.NoRWMutex.Lock() 296 | defer util.NoRWMutex.Unlock() 297 | bank, err := NewBank(w.conf.ContractAddress, w.client) 298 | if err != nil { 299 | log.Error("NewBank error:", err) 300 | return err 301 | } 302 | opts := NewKeyedTransactor() 303 | nonce, err := util.ReadNumberFromFile(w.conf.NonceFilePath) //nonce file 304 | if err != nil { 305 | log.Error("read block info err :%s", err) 306 | return err 307 | } 308 | 309 | opts.Nonce = nonce 310 | log.Debug("current nonce :%d", nonce.Int64()) 311 | if tx, err := bank.Disallow(opts, record.Hash); err != nil { 312 | log.Error("bank disallow err :%s", err) 313 | } else { 314 | log.Info("eth disallow :%v", tx.Hash().Hex()) 315 | nonce = nonce.Add(nonce, big.NewInt(config.NONCE_PLUS)) 316 | util.WriteNumberToFile(w.conf.NonceFilePath, nonce) 317 | } 318 | return nil 319 | } 320 | 321 | //上公链操作-转账 322 | func (w *EthHandler) ethTransferHandler(record *config.Ecr20Record) error { 323 | log.Debug("ethTransferHandler...hash:%v, wdHash:%v", record.Hash.Hex(), record.WdHash.Hex()) 324 | util.NoRWMutex.Lock() 325 | defer util.NoRWMutex.Unlock() 326 | bank, err := NewBank(w.conf.ContractAddress, w.client) 327 | 328 | if err != nil { 329 | log.Error("NewBank error:", err) 330 | return err 331 | } 332 | opts := NewKeyedTransactor() 333 | nonce, err := util.ReadNumberFromFile(w.conf.NonceFilePath) //nonce file 334 | if err != nil { 335 | log.Error("read block info err :%s", err) 336 | return err 337 | } 338 | 339 | opts.Nonce = nonce 340 | log.Debug("current nonce :%d", nonce.Int64()) 341 | if record.Category.Int64() == config.CATEGORY_ETH { //eth 342 | tx, err := bank.Withdraw(opts, record.To, record.Amount, record.Hash) 343 | if err != nil { 344 | log.Error("withDraw error:%s", err) 345 | config.ReportedChan <- &config.GrpcStream{Type: config.GRPC_WITHDRAW_TX_WEB, WdHash: record.WdHash, TxHash: ""} 346 | } else { 347 | log.Info("eth transfer tx:%v, wd:%v, to:%v", tx.Hash().Hex(), record.WdHash.Hex(), record.To.Hex()) 348 | nonce = nonce.Add(nonce, big.NewInt(config.NONCE_PLUS)) 349 | util.WriteNumberToFile(w.conf.NonceFilePath, nonce) 350 | 351 | if err := w.conf.Db.Put([]byte(config.WITHDRAW_TX_PRIFIX+tx.Hash().Hex()), []byte(record.WdHash.Hex())); err != nil { 352 | log.Error("CheckPrivateKey err: %s", err) 353 | } else { 354 | 355 | config.ReportedChan <- &config.GrpcStream{Type: config.GRPC_WITHDRAW_TX_WEB, WdHash: record.WdHash, TxHash: tx.Hash().Hex()} 356 | } 357 | } 358 | } else { //eth代币 359 | if token := token.GetTokenByCategory(record.Category.Int64()); token != nil { 360 | tx, err := bank.TransferERC20(opts, common.HexToAddress(token.ContractAddr), record.To, record.Amount, record.Hash) 361 | log.Debug("TransferERC20:[contract=%v][to=%v][amount=%v][hash=%v]",token.ContractAddr,record.To.Hex(),record.Amount.String(),record.Hash.Hex()) 362 | if err != nil { 363 | log.Error("withDraw error:%s", err) 364 | config.ReportedChan <- &config.GrpcStream{Type: config.GRPC_WITHDRAW_TX_WEB, WdHash: record.WdHash, TxHash: ""} 365 | } else { 366 | log.Info("token transfer tx:%v, wd:%v, to:%v", tx.Hash().Hex(), record.WdHash.Hex(), record.To.Hex()) 367 | nonce = nonce.Add(nonce, big.NewInt(config.NONCE_PLUS)) 368 | util.WriteNumberToFile(w.conf.NonceFilePath, nonce) 369 | if err := w.conf.Db.Put([]byte(config.WITHDRAW_TX_PRIFIX+tx.Hash().Hex()), []byte(record.WdHash.Hex())); err != nil { 370 | log.Error("CheckPrivateKey err: %s", err) 371 | } else { 372 | config.ReportedChan <- &config.GrpcStream{Type: config.GRPC_WITHDRAW_TX_WEB, WdHash: record.WdHash, TxHash: tx.Hash().Hex()} 373 | } 374 | } 375 | } else { 376 | log.Error("category: %d undifined err", record.Category.Int64()) 377 | return errors.New("category undifined") 378 | } 379 | } 380 | 381 | return nil 382 | } 383 | 384 | func (w *EthHandler) listen() { 385 | ch := make(chan *types.Header) 386 | sid, err := w.client.SubscribeNewHead(context.Background(), ch) 387 | if err == nil { 388 | err = w.recv(sid, ch) 389 | defer sid.Unsubscribe() 390 | } 391 | 392 | //retry connect 393 | if err != nil { 394 | log.Error("[ETH CONNECT ERROR]: %v", err) 395 | d := common.DefaultBackoff.Duration(common.RetryCount) 396 | if d > 0 { 397 | time.Sleep(d) 398 | log.Info("[RETRY ETH CONNECT][%v] sleep:%v", common.RetryCount, d) 399 | common.RetryCount++ 400 | go w.listen() 401 | return 402 | } 403 | } 404 | log.Debug("watcher listener stopped.") 405 | } 406 | 407 | func (w *EthHandler) Stop() { 408 | log.Info("close server...") 409 | if w.quitCh != nil { 410 | w.quitCh <- struct{}{} 411 | } 412 | if w.ethQuitCh != nil { 413 | //w.ethQuitCh <- struct{}{} 414 | close(w.ethQuitCh) 415 | } 416 | //if w.accountHandler.quitChannel != nil { 417 | // w.accountHandler.Stop() //停止生成账号 418 | //} 419 | 420 | //w.BtcHandler.Stop() 421 | log.Info("close server OK") 422 | } 423 | 424 | //recv 公链请求 425 | func (w *EthHandler) recv(sid ethereum.Subscription, ch <-chan *types.Header) error { 426 | log.Debug("EthHandler recv...") 427 | var err error 428 | for { 429 | select { 430 | case <-w.quitCh: 431 | log.Debug("EthHandler recv quit...") 432 | return nil 433 | case err = <-sid.Err(): 434 | if err != nil { 435 | log.Error("[ETH RECV]sid.Err:%v",err.Error()) 436 | return err 437 | } 438 | case head := <-ch: 439 | if common.RetryCount != 0 { 440 | //发现掉线,重新扫描 441 | w.reloadBlock() 442 | common.RetryCount = 0 443 | continue 444 | } 445 | if head.Number == nil { 446 | continue 447 | } 448 | if err = w.checkBlock(head.Number); err != nil { 449 | log.Error("[ETH RECV]checkBlock:%v",err.Error()) 450 | return err 451 | } 452 | } 453 | } 454 | } 455 | 456 | func (w *EthHandler) checkBlock(blkNumber *big.Int) error { 457 | checkPoint := new(big.Int).Sub(blkNumber, w.conf.DelayedBlocks) 458 | 459 | //log.Debug("[HEADER] FromBlock : %s, blkNumber: %s", checkPoint.String(), blkNumber.String()) 460 | log.Debug("[HEADER] blkNumber: %s, checkpoint: %s", blkNumber.String(), checkPoint.String()) 461 | 462 | if logs, err := w.client.FilterLogs( 463 | context.Background(), 464 | ethereum.FilterQuery{ 465 | FromBlock: checkPoint, 466 | ToBlock: checkPoint, 467 | }); err != nil { 468 | log.Debug("FilterLogs :%s", err) 469 | return err 470 | } else { 471 | if len(logs) != 0 { 472 | for _, enventLog := range logs { 473 | //log.Debug("enventLog: %s", enventLog.Topics[0].Hex()) 474 | if enventLog.Topics == nil || len(enventLog.Topics) == 0 { 475 | log.Debug("enventLog topics nil") 476 | continue 477 | } 478 | handler, ok := w.conf.Events[enventLog.Topics[0]] 479 | //log.Debug("enventLog ok: %s", ok) 480 | if !ok { 481 | continue 482 | } 483 | if err = handler.Scan(&enventLog); err != nil { 484 | return err 485 | } 486 | } 487 | } 488 | util.WriteNumberToFile(w.conf.BlockNoFilePath, checkPoint) 489 | } 490 | 491 | return nil 492 | } 493 | 494 | func NewEthHandler(cfg *config.Config, db localdb.Database) (*EthHandler, error) { 495 | state := &lvldbState{db} 496 | conf := &HandlerContext{ 497 | Events: make(map[common.Hash]EventHandler), 498 | State: state, 499 | EtherURL: cfg.EthereumConfig.Scheme, 500 | Retries: cfg.EthereumConfig.Retries, 501 | DelayedBlocks: big.NewInt(int64(cfg.EthereumConfig.DelayedBlocks)), 502 | CursorBlocks: big.NewInt(int64(cfg.EthereumConfig.CursorBlocks)), 503 | Db: db, 504 | BlockNoFilePath: cfg.EthereumConfig.BlockNoFilePath, 505 | NonceFilePath: cfg.EthereumConfig.NonceFilePath, 506 | } 507 | w := &EthHandler{conf: conf, quitCh: make(chan struct{}, 1)} 508 | w.BtcHandler, _ = NewBtcHandler(cfg, db) 509 | //w.accountHandler = NewAccountHandler(db, w, cfg.EthereumConfig.AccountPoolSize, cfg.EthereumConfig.NonceFilePath) 510 | return w, nil 511 | } 512 | 513 | //发布bank 514 | func (e *EthHandler) DeployBank() (addr common.Address, tx *types.Transaction, b *Bank, err error) { 515 | log.Debug("DeployBank..........") 516 | rpcConn, err := rpc.Dial(e.conf.EtherURL) 517 | if err != nil { 518 | return 519 | } 520 | defer rpcConn.Close() 521 | client := ethclient.NewClient(rpcConn) 522 | opts := NewKeyedTransactor() 523 | if addr, tx, b, err = DeployBank(opts, client); err == nil { 524 | e.conf.State.SetContractAddress(addr) //地址写入db 525 | } else { 526 | log.Error("deploy bank failed. cause: %v", err) 527 | } 528 | return 529 | } 530 | 531 | //发布地址合约 532 | func (e *EthHandler) DeployWallet(nonce *big.Int) (addr common.Address, err error) { 533 | log.Debug("DeployWallet..........") 534 | rpcConn, err := rpc.Dial(e.conf.EtherURL) 535 | if err != nil { 536 | return 537 | } 538 | defer rpcConn.Close() 539 | client := ethclient.NewClient(rpcConn) 540 | opts := NewKeyedTransactor() 541 | opts.Nonce = nonce 542 | if bankAddress, err := e.GetContractAddr(); err != nil { 543 | log.Error("get bankAddress failed. cause: %v", err) 544 | return addr, err 545 | } else { 546 | addr, tx, _, err := DeployWallet(opts, client, bankAddress) 547 | if err != nil { 548 | log.Error("deploy wallet failed. cause: %v", err) 549 | return addr, err 550 | } else { 551 | log.Debug("Successful deploy wallet addr: %s", addr.Hex()) 552 | log.Debug("Successful deploy wallet tx: %s", tx.Hash().Hex()) 553 | } 554 | return addr, nil 555 | } 556 | } 557 | 558 | //获取合约地址 559 | func (e *EthHandler) GetContractAddr() (common.Address, error) { 560 | return e.conf.State.GetContractAddress() 561 | } 562 | -------------------------------------------------------------------------------- /node/node.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package node 16 | 17 | import ( 18 | "context" 19 | "crypto/tls" 20 | "crypto/x509" 21 | "errors" 22 | "io/ioutil" 23 | "time" 24 | 25 | "encoding/json" 26 | 27 | log "github.com/alecthomas/log4go" 28 | "github.com/boxproject/voucher/config" 29 | "github.com/boxproject/voucher/localdb" 30 | "github.com/boxproject/voucher/pb" 31 | "github.com/boxproject/voucher/util" 32 | "github.com/ethereum/go-ethereum/common" 33 | "google.golang.org/grpc" 34 | "google.golang.org/grpc/credentials" 35 | ) 36 | 37 | //对外api 38 | type NodeApi struct { 39 | cfg config.AgentServiceConfig 40 | db localdb.Database 41 | conn *grpc.ClientConn 42 | heartQuitCh chan struct{} 43 | quitCh chan struct{} 44 | } 45 | 46 | type HashResult struct { 47 | NodeName string 48 | NodeResult bool 49 | } 50 | 51 | //init 52 | func InitConn(cfg *config.Config, db localdb.Database) (*NodeApi, error) { 53 | log.Info("InitConn....") 54 | nodeApi := &NodeApi{cfg: cfg.AgentSerCfg, db: db, heartQuitCh: make(chan struct{}, 1), quitCh: make(chan struct{}, 1)} 55 | cred, err := nodeApi.loadCredential(cfg.AgentSerCfg.ClientCertPath, cfg.AgentSerCfg.ClientKeyPath) 56 | if err != nil { 57 | log.Error("load tls cert failed. cause: %v\n", err) 58 | return nil, err 59 | } 60 | nodeApi.conn, err = grpc.Dial(cfg.AgentSerCfg.RPCAddr, grpc.WithTransportCredentials(cred)) 61 | if err != nil { 62 | log.Error("connect to the remote server failed. cause: %v", err) 63 | return nil, err 64 | } 65 | go nodeApi.streamRecv() 66 | go nodeApi.RepStart() //启动接受充值上报请求 67 | return nodeApi, nil 68 | } 69 | 70 | //close conn 71 | func (n *NodeApi) CloseConn() error { 72 | log.Info("conn closed begin") 73 | if n.conn == nil { 74 | return errors.New("conn nil") 75 | } else { 76 | if n.quitCh != nil { //流服务 77 | n.quitCh <- struct{}{} 78 | } 79 | if n.heartQuitCh != nil { //心跳 80 | n.heartQuitCh <- struct{}{} 81 | } 82 | n.conn.Close() 83 | } 84 | log.Info("conn closed end") 85 | return nil 86 | } 87 | 88 | //grpc失败重发 89 | func (n *NodeApi) RepReSend() { 90 | //get db 91 | if grpcReSendMap, err := n.db.GetPrifix([]byte(config.PUB_DB_BAK_PRIFIX)); err != nil { 92 | log.Error("db error:", err) 93 | } else { 94 | for _, value := range grpcReSendMap { 95 | grpcMsg := &config.GrpcStream{} 96 | if err := json.Unmarshal([]byte(value), grpcMsg); err != nil { 97 | log.Error("db unmarshal err: %v", err) 98 | } else { 99 | //resend grpc 100 | config.ReportedChan <- grpcMsg 101 | log.Debug("[GRPC RESEND][TYPE=%v]", grpcMsg.Type) 102 | } 103 | } 104 | } 105 | } 106 | 107 | //report 108 | func (n *NodeApi) RepStart() { 109 | log.Info("node rep started!") 110 | reSendTick := time.NewTicker(time.Second * 5) 111 | for true { 112 | select { 113 | case data, ok := <-config.ReportedChan: 114 | if ok { 115 | log.Debug("data.Type....", data.Type) 116 | switch data.Type { 117 | case config.GRPC_DEPOSIT_WEB: 118 | n.DepositRequest(data) 119 | break 120 | case config.GRPC_WITHDRAW_TX_WEB: 121 | n.WithDrewTxRequest(data) 122 | break 123 | case config.GRPC_WITHDRAW_WEB: 124 | n.WithDrewRequest(data) 125 | break 126 | //case config.GRPC_HASH_LIST_WEB: 127 | // n.HashListRequest(data) 128 | // break 129 | case config.GRPC_TOKEN_LIST_WEB: 130 | n.TokenListRequest(data) 131 | break 132 | case config.GRPC_COIN_LIST_WEB: 133 | n.CoinListRequest(data) 134 | break 135 | case config.GRPC_HASH_ENABLE_WEB: 136 | n.HashEnableRequest(data) 137 | break 138 | case config.GRPC_CHECK_KEY_WEB: 139 | n.CheckKeyRequest(data) 140 | break 141 | default: 142 | log.Info("unknown node req type:%v", data.Type) 143 | } 144 | } else { 145 | log.Error("read node from channel failed") 146 | } 147 | case <-reSendTick.C: 148 | n.RepReSend() 149 | } 150 | } 151 | } 152 | 153 | func (n *NodeApi) heart() { 154 | log.Info("nodeApi heart started!") 155 | ticker := time.NewTicker(5 * time.Second) 156 | LOOP: 157 | for { 158 | select { 159 | case <-ticker.C: 160 | client := pb.NewSynchronizerClient(n.conn) 161 | if statuBytes, err := json.Marshal(config.RealTimeStatus); err != nil { 162 | log.Error("json marshal err: %s", err) 163 | } else { 164 | if rsp, err := client.Heart(context.TODO(), &pb.HeartRequest{RouterType: "grpc", ServerName: n.cfg.Name, Name: n.cfg.Alias, Ip: util.GetCurrentIp(), Msg: statuBytes}); err != nil { 165 | log.Error("heart req failed %s\n", err) 166 | } else { 167 | log.Debug("heart response", rsp) 168 | } 169 | } 170 | 171 | case <-n.heartQuitCh: 172 | ticker.Stop() 173 | break LOOP 174 | } 175 | } 176 | 177 | log.Info("nodeApi heart stopped!") 178 | 179 | } 180 | 181 | ////同意 182 | //func (n *NodeApi) AllowRequest(msg []byte) bool { 183 | // n.router(config.ROUTER_TYPE_GRPC, n.cfg.CompanionName, msg) 184 | // return true 185 | //} 186 | // 187 | ////禁用 188 | //func (n *NodeApi) DisAllowRequest(msg []byte) bool { 189 | // n.router(config.ROUTER_TYPE_GRPC, n.cfg.CompanionName, msg) 190 | // return true 191 | //} 192 | 193 | //充值上报 194 | func (n *NodeApi) DepositRequest(grpcStream *config.GrpcStream) bool { 195 | log.Debug("DepositRequest:%v", grpcStream) 196 | msg, err := json.Marshal(grpcStream) 197 | if err != nil { 198 | log.Error("DepositRequest marshal err: %s", err) 199 | return false 200 | } 201 | 202 | dbLabel := config.ROUTER_PRIDFIX + config.DEPOSIT_PRIFIX + grpcStream.WdHash.Hex() + grpcStream.TxHash 203 | dbLabelBak := config.PUB_DB_BAK_PRIFIX + dbLabel 204 | dbData, _ := n.db.Get([]byte(dbLabel)) 205 | if len(dbData) > 0 { 206 | log.Debug("[ROUTER]:already have:%v", dbLabel) 207 | //重复上报限时,需要时放开下行return 代码 208 | //return false 209 | } else { 210 | //backup data 211 | n.db.Put([]byte(dbLabelBak), msg) 212 | } 213 | 214 | if _, err = n.router(config.ROUTER_TYPE_WEB, "", msg); err != nil { 215 | return false 216 | } else { 217 | //update db 218 | n.db.Put([]byte(dbLabel), msg) 219 | //delete db backup 220 | n.db.Delete([]byte(dbLabelBak)) 221 | } 222 | 223 | return true 224 | } 225 | 226 | //提现tx上报 227 | func (n *NodeApi) WithDrewTxRequest(grpcStream *config.GrpcStream) bool { 228 | log.Debug("WithDrewTxRequest:%v", grpcStream) 229 | msg, err := json.Marshal(grpcStream) 230 | if err != nil { 231 | log.Error("WithDrewTxRequest marshal err: %s", err) 232 | return false 233 | } 234 | dbLabel := config.ROUTER_PRIDFIX + config.WITHDRAW_TX_PRIFIX + grpcStream.WdHash.Hex() + grpcStream.TxHash 235 | dbLabelBak := config.PUB_DB_BAK_PRIFIX + dbLabel 236 | dbData, _ := n.db.Get([]byte(dbLabel)) 237 | if len(dbData) > 0 { 238 | log.Debug("[ROUTER]:already have:%v", dbLabel) 239 | //重复上报限时,需要时放开下行return 代码 240 | //return false 241 | } else { 242 | //backup data 243 | n.db.Put([]byte(dbLabelBak), msg) 244 | } 245 | 246 | if _, err = n.router(config.ROUTER_TYPE_WEB, "", msg); err != nil { 247 | return false 248 | } else { 249 | //update db 250 | n.db.Put([]byte(dbLabel), msg) 251 | //delete db backup 252 | n.db.Delete([]byte(dbLabelBak)) 253 | } 254 | 255 | return true 256 | } 257 | 258 | //提现上报 259 | func (n *NodeApi) WithDrewRequest(grpcStream *config.GrpcStream) bool { 260 | log.Debug("WithDrewRequest:%v", grpcStream) 261 | msg, err := json.Marshal(grpcStream) 262 | if err != nil { 263 | log.Error("WithDrewRequest marshal err: %s", err) 264 | return false 265 | } 266 | dbLabel := config.ROUTER_PRIDFIX + config.WITHDRAW_PRIFIX + grpcStream.WdHash.Hex() + grpcStream.TxHash 267 | dbLabelBak := config.PUB_DB_BAK_PRIFIX + dbLabel 268 | dbData, _ := n.db.Get([]byte(dbLabel)) 269 | if len(dbData) > 0 { 270 | log.Debug("[ROUTER]:already have:%v", dbLabel) 271 | //重复上报限时,需要时放开下行return 代码 272 | //return false 273 | } else { 274 | //backup data 275 | n.db.Put([]byte(dbLabelBak), msg) 276 | } 277 | 278 | if _, err = n.router(config.ROUTER_TYPE_WEB, "", msg); err != nil { 279 | return false 280 | } else { 281 | //update db 282 | n.db.Put([]byte(dbLabel), msg) 283 | //delete db backup 284 | n.db.Delete([]byte(dbLabelBak)) 285 | } 286 | 287 | return true 288 | } 289 | 290 | //hash上报 291 | func (n *NodeApi) HashListRequest(grpcStream *config.GrpcStream) bool { 292 | log.Debug("HashListRequest........") 293 | if msg, err := json.Marshal(grpcStream); err != nil { 294 | log.Error("HashListRequest marshal err: %s", err) 295 | return false 296 | } else { 297 | if _, err = n.router(config.ROUTER_TYPE_WEB, "", msg); err != nil { 298 | return false 299 | } 300 | } 301 | return true 302 | } 303 | 304 | //token上报 305 | func (n *NodeApi) TokenListRequest(grpcStream *config.GrpcStream) bool { 306 | log.Debug("TokenListRequest......") 307 | if msg, err := json.Marshal(grpcStream); err != nil { 308 | log.Error("TokenListRequest marshal err: %s", err) 309 | return false 310 | } else { 311 | if _, err = n.router(config.ROUTER_TYPE_WEB, "", msg); err != nil { 312 | return false 313 | } 314 | } 315 | return true 316 | } 317 | 318 | //list上报 319 | func (n *NodeApi) CoinListRequest(grpcStream *config.GrpcStream) bool { 320 | log.Debug("CoinListRequest......") 321 | if msg, err := json.Marshal(grpcStream); err != nil { 322 | log.Error("CoinListRequest marshal err: %s", err) 323 | return false 324 | } else { 325 | if _, err = n.router(config.ROUTER_TYPE_WEB, "", msg); err != nil { 326 | return false 327 | } 328 | } 329 | return true 330 | } 331 | 332 | //hash 确认上报 333 | func (n *NodeApi) HashEnableRequest(grpcStream *config.GrpcStream) bool { 334 | log.Debug("HashEnableRequest......") 335 | if msg, err := json.Marshal(grpcStream); err != nil { 336 | log.Error("HashEnableRequest marshal err: %s", err) 337 | return false 338 | } else { 339 | if _, err = n.router(config.ROUTER_TYPE_WEB, "", msg); err != nil { 340 | return false 341 | } 342 | } 343 | return true 344 | } 345 | //hash 确认上报 346 | func (n *NodeApi) CheckKeyRequest(grpcStream *config.GrpcStream) bool { 347 | log.Debug("CheckKeyRequest......") 348 | if msg, err := json.Marshal(grpcStream); err != nil { 349 | log.Error("CheckKeyRequest marshal err: %s", err) 350 | return false 351 | } else { 352 | if _, err = n.router(config.ROUTER_TYPE_WEB, "", msg); err != nil { 353 | return false 354 | } 355 | } 356 | return true 357 | } 358 | 359 | func (n *NodeApi) router(routerType string, routerName string, msg []byte) (string, error) { 360 | if n.conn != nil { 361 | client := pb.NewSynchronizerClient(n.conn) 362 | if rsp, err := client.Router(context.TODO(), &pb.RouterRequest{routerType, routerName, msg}); err != nil { 363 | log.Error("Router req failed %v\n", err) 364 | return "", err 365 | } else { 366 | return rsp.Code, nil 367 | } 368 | } 369 | return "", errors.New("conn is nil") 370 | } 371 | 372 | //stream recv 373 | func (n *NodeApi) streamRecv() { 374 | timeCount := 1 375 | for { //5秒尝试连接一次 TODO 376 | log.Info("try...", timeCount) 377 | client := pb.NewSynchronizerClient(n.conn) 378 | stream, err := client.Listen(context.TODO()) 379 | if err != nil { //5秒尝试连接一次 380 | log.Error("[STREAM ERR] %v\n", err) 381 | } else { 382 | //获取上次block number 383 | waitc := make(chan struct{}) 384 | stream.Send(&pb.ListenReq{ServerName: n.cfg.Name, Name: n.cfg.Alias, Ip: util.GetCurrentIp()}) 385 | go func() { 386 | for { 387 | if resp, err := stream.Recv(); err != nil { //rec error 388 | log.Error("[STREAM ERR] %v\n", err) 389 | close(waitc) 390 | return 391 | } else { 392 | log.Debug("stream Recv: %s\n", resp) 393 | n.handleStream(resp) 394 | } 395 | } 396 | }() 397 | go n.heart() //启动心跳 398 | <-waitc 399 | if err = stream.CloseSend(); err != nil { 400 | log.Error("%v.CloseAndRecv() got error %v, want %v", stream, err, nil) 401 | } 402 | } 403 | time.Sleep(5 * time.Second) 404 | timeCount++ 405 | } 406 | log.Debug("streamRecv stop") 407 | } 408 | 409 | //cert 410 | func (n *NodeApi) loadCredential(clientCertPath, clientKeyPath string) (credentials.TransportCredentials, error) { 411 | cert, err := tls.LoadX509KeyPair(clientCertPath, clientKeyPath) 412 | if err != nil { 413 | return nil, err 414 | } 415 | 416 | certBytes, err := ioutil.ReadFile(clientCertPath) 417 | if err != nil { 418 | return nil, err 419 | } 420 | 421 | clientCertPool := x509.NewCertPool() 422 | ok := clientCertPool.AppendCertsFromPEM(certBytes) 423 | if !ok { 424 | return nil, err 425 | } 426 | 427 | config := &tls.Config{ 428 | RootCAs: clientCertPool, 429 | InsecureSkipVerify: true, 430 | Certificates: []tls.Certificate{cert}, 431 | } 432 | 433 | return credentials.NewTLS(config), nil 434 | } 435 | 436 | //处理流 437 | func (n *NodeApi) handleStream(streamRsp *pb.StreamRsp) { 438 | streamModel := &config.GrpcStream{} 439 | if err := json.Unmarshal(streamRsp.Msg, streamModel); err != nil { 440 | log.Error("json marshal error:%v", err) 441 | return 442 | } 443 | 444 | switch streamModel.Type { 445 | //case config.GRPC_HASH_ADD_REQ: //新加hash 446 | // n.db.Put([]byte(config.HASH_LIST_PRIFIX+streamModel.Hash.Hex()), streamRsp.Msg) //供查询使用 447 | //hash.HashList(n.db) 448 | //case config.GRPC_HASH_ENABLE_REQ: //确认hash req 449 | // n.AllowRequest(streamRsp.Msg) 450 | //case config.GRPC_HASH_DISABLE_REQ: //拒绝hash req 451 | // n.DisAllowRequest(streamRsp.Msg) 452 | case config.GRPC_HASH_ADD_LOG: //hash log 453 | n.db.Put([]byte(config.HASH_LIST_PRIFIX+streamModel.Hash.Hex()), []byte(streamModel.Flow)) //供查询使用 454 | case config.GRPC_HASH_ENABLE_LOG: //同意 log 455 | if checkSigns(streamModel.Flow, streamModel.SignInfos, n.db) { 456 | if data, _ := n.db.Get([]byte(config.HASH_LIST_PRIFIX+streamModel.Hash.Hex())); data == nil || len(data) == 0 { 457 | n.db.Put([]byte(config.HASH_LIST_PRIFIX+streamModel.Hash.Hex()), []byte(streamModel.Flow)) //供查询使用 458 | n.db.Put([]byte(config.PENDING_PRIDFIX+streamModel.Hash.Hex()), streamRsp.Msg) //pending 459 | config.Ecr20RecordChan <- &config.Ecr20Record{Type: config.ECR20_TYPE_ALLOW, Hash: streamModel.Hash} //上公链 460 | } else { 461 | log.Debug("multi hash allow :%v", streamModel.Hash.Hex()) 462 | break 463 | } 464 | } 465 | case config.GRPC_HASH_DISABLE_LOG: //禁用 log 466 | if checkSigns(streamModel.Flow, streamModel.SignInfos, n.db) { //pending 467 | n.db.Put([]byte(config.PENDING_PRIDFIX+streamModel.Hash.Hex()), streamRsp.Msg) 468 | config.Ecr20RecordChan <- &config.Ecr20Record{Type: config.ECR20_TYPE_DISALLOW, Hash: streamModel.Hash} //上公链 469 | } 470 | case config.GRPC_WITHDRAW_LOG: //提现 log 471 | //TODO 验签 472 | if checkWithdrawSign(streamModel.Hash.Hex(), streamModel.Flow, streamModel.WdFlow, n.db) { 473 | log.Debug("check sign success!") 474 | if data, _ := n.db.Get([]byte(config.WITHDRAW_APPLY_PRIFIX + streamModel.WdHash.Hex())); data == nil || len(data) == 0 { 475 | n.db.Put([]byte(config.WITHDRAW_APPLY_PRIFIX+streamModel.WdHash.Hex()), streamRsp.Msg) // for view 476 | } else { 477 | log.Debug("multi withdraw wdHash:%s", streamModel.WdHash.Hex()) 478 | break 479 | } 480 | 481 | n.db.Put([]byte(config.PENDING_PRIDFIX+streamModel.WdHash.Hex()), streamRsp.Msg) //pending 482 | category := streamModel.Category 483 | if category != nil { 484 | switch streamModel.Category.Int64() { 485 | case config.CATEGORY_BTC: 486 | //bitcoin 487 | config.BtcRecordChan <- &config.BtcRecord{Type: config.BTC_TYPE_APPROVE, WdHash: streamModel.WdHash, Handlefee: streamModel.Fee, To: streamModel.To, Amount: streamModel.Amount} 488 | break 489 | case config.CATEGORY_ETH: 490 | //上公链 491 | config.Ecr20RecordChan <- &config.Ecr20Record{Type: config.ECR20_TYPE_APPROVE, Hash: streamModel.Hash, WdHash: streamModel.WdHash, To: common.HexToAddress(streamModel.To), Amount: streamModel.Amount, Category: streamModel.Category} 492 | break 493 | default: //ETH 代币 494 | config.Ecr20RecordChan <- &config.Ecr20Record{Type: config.ECR20_TYPE_APPROVE, Hash: streamModel.Hash, WdHash: streamModel.WdHash, To: common.HexToAddress(streamModel.To), Amount: streamModel.Amount, Category: streamModel.Category} 495 | } 496 | } 497 | } 498 | case config.GRPC_VOUCHER_OPR_REQ: //签名机操作 499 | config.OperateChan <- streamModel.VoucherOperate 500 | default: 501 | 502 | } 503 | } 504 | 505 | //验证签名 506 | func checkSigns(flow string, signInfos []*config.SignInfo, db localdb.Database) bool { 507 | var appMap map[string]interface{} = make(map[string]interface{}) 508 | if len(signInfos) == config.RealTimeStatus.Total { 509 | for _, signInfo := range signInfos { 510 | appMap[signInfo.AppId] = nil 511 | if !checkSign(flow, signInfo.Sign, signInfo.AppId, db) { 512 | return false 513 | } 514 | } 515 | if len(appMap) == config.RealTimeStatus.Total { //私钥app都填完方通过 516 | log.Info("sign passed!") 517 | return true 518 | } 519 | } else { 520 | log.Error("sign failed!") 521 | } 522 | return false 523 | } 524 | 525 | func checkSign(flow, sign, appId string, db localdb.Database) bool { 526 | if err := util.RsaSignVer([]byte(flow), []byte(sign), db, appId); err != nil { 527 | log.Info("check sign failed.") 528 | return false 529 | } 530 | return true 531 | } 532 | 533 | //提现验签 534 | func checkWithdrawSign(hashStr string, wdFlow, wdSign string, db localdb.Database) bool { 535 | var pubKeyMap map[string]string = make(map[string]string) 536 | if flowMsg, err := db.Get([]byte(config.HASH_LIST_PRIFIX + hashStr)); err != nil { //查询hash模板 537 | log.Error("db get err: %s", err) 538 | } else { 539 | hashFlow := &config.HashFlow{} 540 | if err := json.Unmarshal(flowMsg, hashFlow); err != nil { 541 | log.Error("json unmarshal error:%v", err) 542 | return false 543 | } else { 544 | for _, approvalInfo := range hashFlow.Approval_info { 545 | for _, approver := range approvalInfo.Approvers { 546 | pubKeyMap[approver.App_account_id] = approver.Pub_key 547 | } 548 | } 549 | } 550 | } 551 | 552 | var employeeFlows []config.EmployeeFlow 553 | if err := json.Unmarshal([]byte(wdSign), &employeeFlows); err != nil { 554 | log.Error("json unmarshal error:%v", err) 555 | return false 556 | } else { 557 | if employeeFlows == nil { 558 | log.Error("sign data nil!") 559 | return false 560 | } 561 | for _, employeeFlow := range employeeFlows { 562 | if pubKeyMap[employeeFlow.Appid] == "" { 563 | return false 564 | } else { 565 | if err = util.RsaSignVerWithKey([]byte(wdFlow), []byte(employeeFlow.Sign), []byte(pubKeyMap[employeeFlow.Appid])); err != nil { 566 | log.Error("sign check err: %s. [appId:%s]", err, employeeFlow.Appid) 567 | return false 568 | } 569 | } 570 | } 571 | } 572 | return true 573 | } 574 | -------------------------------------------------------------------------------- /operate/operate.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017. box.la authors. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package operate 16 | 17 | import ( 18 | "strings" 19 | "sync" 20 | 21 | "encoding/json" 22 | log "github.com/alecthomas/log4go" 23 | "github.com/boxproject/voucher/config" 24 | verrors "github.com/boxproject/voucher/errors" 25 | "github.com/boxproject/voucher/localdb" 26 | "github.com/boxproject/voucher/token" 27 | "github.com/boxproject/voucher/trans" 28 | "github.com/boxproject/voucher/util" 29 | "github.com/awnumar/memguard" 30 | "github.com/ethereum/go-ethereum/common" 31 | ) 32 | 33 | //prefix+host ->pass 34 | var passMap map[string]string = make(map[string]string) 35 | var codeMap map[string]string = make(map[string]string) 36 | 37 | //prefix+host ->role 38 | //var roleMap map[string]string = make(map[string]string) 39 | 40 | //var roleStartMap map[string]string = make(map[string]string) 41 | 42 | //首次code 43 | //var firstCode string = "" 44 | 45 | var batchCountMap map[string]int = make(map[string]int) 46 | 47 | var authorizedMap map[string]bool = make(map[string]bool) 48 | 49 | var apiMu sync.Mutex 50 | 51 | type OperateHandler struct { 52 | quitChannel chan int 53 | cfg *config.Config 54 | db localdb.Database 55 | ethHander *trans.EthHandler 56 | } 57 | 58 | func InitOperateHandler(cfg *config.Config, ldb localdb.Database, ethHander *trans.EthHandler) *OperateHandler { 59 | return &OperateHandler{quitChannel: make(chan int, 1), cfg: cfg, db: ldb, ethHander: ethHander} 60 | } 61 | 62 | //签名机操作处理 63 | func (handler *OperateHandler) Start() { 64 | log.Info("OperateHandler start...") 65 | loop := true 66 | for loop { 67 | select { 68 | case <-handler.quitChannel: 69 | log.Info("OperateHandler::SendMessage thread exitCh!") 70 | loop = false 71 | case data, ok := <-config.OperateChan: 72 | if ok { 73 | switch data.Type { 74 | case config.VOUCHER_OPERATE_ADDKEY: 75 | handler.addKey(data) 76 | break 77 | case config.VOUCHER_OPERATE_CREATE: 78 | handler.create(data) 79 | break 80 | case config.VOUCHER_OPERATE_DEPLOY: 81 | handler.deploy(data) 82 | break 83 | case config.VOUCHER_OPERATE_START: 84 | handler.start(data) 85 | break 86 | case config.VOUCHER_OPERATE_PAUSE: 87 | handler.pause(data) 88 | break 89 | case config.VOUCHER_OPERATE_HASH_ENABLE: 90 | handler.hashAllow(data) 91 | break 92 | case config.VOUCHER_OPERATE_HASH_DISABLE: 93 | handler.hashDisAllow(data) 94 | break 95 | case config.VOUCHER_OPERATE_HASH_LIST: 96 | handler.hashList(data) 97 | break 98 | case config.VOUCHER_OPERATE_TOKEN_ADD: 99 | handler.addToken(data) 100 | break 101 | case config.VOUCHER_OPERATE_TOKEN_DEL: 102 | handler.delToken(data) 103 | break 104 | case config.VOUCHER_OPERATE_TOKEN_LIST: 105 | handler.tokenList(data) 106 | break 107 | case config.VOUCHER_OPERATE_COIN: 108 | handler.coin(data) 109 | break 110 | case config.VOUCHER_OPERATE_CHECK_KEY: 111 | handler.operateCheckKeys(data) 112 | default: 113 | log.Info("unknow asy req: %s", data.Type) 114 | } 115 | } else { 116 | log.Error("OperateHandler read from channel failed") 117 | } 118 | } 119 | } 120 | } 121 | 122 | //关闭签名机操作处理 123 | func (handler *OperateHandler) Close() { 124 | if handler.quitChannel != nil { 125 | close(handler.quitChannel) 126 | } 127 | log.Info("OperateHandler closed") 128 | } 129 | 130 | func (handler *OperateHandler) addKey(operate *config.Operate) { 131 | publicKey := operate.PublicKey 132 | log.Debug("addKey...") 133 | 134 | if dDate := util.CBCDecrypter([]byte(publicKey), util.GetAesKeyRandomFromDb(handler.db)); len(dDate) == 0 { //解密 135 | log.Error("key decrypter failed.") 136 | return 137 | } else { 138 | //db 139 | if err := handler.db.Put([]byte(config.APP_KEY_PRIFIX+operate.AppId), dDate); err != nil { 140 | log.Error("app keystore err: %s", err) 141 | } else { 142 | config.RealTimeStatus.KeyStoreStatus = append(config.RealTimeStatus.KeyStoreStatus, config.KeyStoreStatu{ApplyerId: operate.AppId, ApplyerName: operate.AppName}) 143 | landStatus(handler.db, "") 144 | } 145 | } 146 | } 147 | 148 | func (handler *OperateHandler) hashAllow(operate *config.Operate) { 149 | hash := operate.Hash 150 | log.Debug("hashAllow: %s", hash) 151 | passWord := getKey(operate.Password, operate.Sign, handler.db, operate.AppId) 152 | if passWord == "" { 153 | log.Error("password get failed.") 154 | return 155 | } 156 | if config.RealTimeStatus.ServerStatus == config.VOUCHER_STATUS_STATED { //服务已启动 157 | b, passWordStatus := checkKey(handler.db, config.REQ_HASH, handler.cfg.Secret.AppNum, operate.AppId, passWord, true) 158 | config.RealTimeStatus.Status = passWordStatus 159 | if !b { 160 | log.Info("check failed") 161 | config.RealTimeStatus.Status = passWordStatus 162 | } 163 | landStatus(handler.db, config.REQ_HASH) 164 | } else { 165 | log.Error("hash allow failed: server not started") 166 | } 167 | } 168 | 169 | func (handler *OperateHandler) hashDisAllow(operate *config.Operate) { 170 | hash := operate.Hash 171 | log.Debug("hashDisAllow: %s", hash) 172 | passWord := getKey(operate.Password, operate.Sign, handler.db, operate.AppId) 173 | if passWord == "" { 174 | log.Error("password get failed.") 175 | return 176 | } 177 | if config.RealTimeStatus.ServerStatus == config.VOUCHER_STATUS_STATED { //服务已启动 178 | b, passWordStatus := checkKey(handler.db, config.REQ_HASH, handler.cfg.Secret.AppNum, operate.AppId, passWord, false) 179 | config.RealTimeStatus.Status = passWordStatus 180 | if !b { 181 | log.Info("check failed") 182 | config.RealTimeStatus.Status = config.PASSWORD_STATUS_FAILED 183 | } 184 | landStatus(handler.db, config.REQ_HASH) 185 | } else { 186 | log.Error("hash allow failed: server not started") 187 | } 188 | } 189 | 190 | func (handler *OperateHandler) create(operate *config.Operate) { 191 | log.Debug("createHandler...") 192 | passWord := getKey(operate.Password, operate.Sign, handler.db, operate.AppId) 193 | //passWord := getKey("xLGBJ4JCmTQCVzrIBEnZGw==", "n0OVW9KHnwmGXJ5rCH/eyFFec4EKdgV+bKZT/jkaJj3q/Z2Gw4wAPE5Si7GPh6PSasQ4E0jBb6dlxciQj7PX+buVXgNevzibUF7hXSK3Fnb182oI6H8hevRJD9FDT4T/5/rRJ375P2Z8NzGXDqEfrcij9O3f5g6nhetF+egTbhLph8MvNxdOZ6NDKBOYeYS2oOCVu52ba2bI509w98V7X9MHLV4HPT3bZwtGlRawnaCJ+UDODmw3LdzF3rRlsi447EBpdzWiKvXqAYHy0lcu0eNpPPkc+5NzFvu9AQWdWRgfpXWx/BmZuUun+lhaH7c/8uVS3Urkearcsfy5oiaa+g==", handler.db, operate.AppId) 194 | if passWord == "" { 195 | log.Error("password get failed.") 196 | return 197 | } 198 | if config.RealTimeStatus.ServerStatus == config.VOUCHER_STATUS_UNCREATED { //服务未创建 199 | if d, err := trans.GetSecret(handler.db); err != nil { 200 | log.Error("get secret error:%v", err) 201 | } else { 202 | config.RealTimeStatus.D = d 203 | b, passWordStatus := generateKey(handler.db, config.REQ_CREATE, handler.cfg.Secret.AppNum, operate.AppId, passWord, operate.Code, true) 204 | config.RealTimeStatus.Status = passWordStatus 205 | if b { 206 | config.RealTimeStatus.ServerStatus = config.VOUCHER_STATUS_CREATED 207 | config.RealTimeStatus.Address = trans.PrivateKeyToHex(handler.db) 208 | log.Debug("privateKey generated....") 209 | } else { 210 | log.Info("create failed: %s") 211 | } 212 | landStatus(handler.db, config.REQ_CREATE) 213 | } 214 | } else { 215 | log.Error("cannot create, status :%s", config.RealTimeStatus.Status) 216 | } 217 | } 218 | 219 | func (handler *OperateHandler) deploy(operate *config.Operate) { 220 | log.Debug("deployHandler...") 221 | passWord := getKey(operate.Password, operate.Sign, handler.db, operate.AppId) 222 | if passWord == "" { 223 | log.Error("password get failed.") 224 | return 225 | } 226 | b, passWordStatus := checkKey(handler.db, config.REQ_DEPLOY, handler.cfg.Secret.AppNum, operate.AppId, passWord, true) 227 | config.RealTimeStatus.Status = passWordStatus 228 | if b { 229 | if contractAddress, txHash, _, err := handler.ethHander.DeployBank(); err != nil { //发布合约 230 | log.Error("deploy failed error:", err) 231 | } else { 232 | config.RealTimeStatus.ServerStatus = config.VOUCHER_STATUS_DEPLOYED 233 | config.RealTimeStatus.ContractAddress = contractAddress.Hex() 234 | 235 | log.Info("deploy success contractAddress:%v, txHash:%v", contractAddress.Hash().Hex(), txHash.Hash().Hex()) 236 | } 237 | } else { 238 | log.Info("deploy failed!") 239 | } 240 | landStatus(handler.db, config.REQ_DEPLOY) 241 | } 242 | 243 | func (handler *OperateHandler) start(operate *config.Operate) { 244 | log.Debug("startHandler...") 245 | passWord := getKey(operate.Password, operate.Sign, handler.db, operate.AppId) 246 | if passWord == "" { 247 | log.Error("password get failed.") 248 | return 249 | } 250 | if config.RealTimeStatus.ServerStatus == config.VOUCHER_STATUS_DEPLOYED || config.RealTimeStatus.ServerStatus == config.VOUCHER_STATUS_PAUSED { //服务已发布或停止 251 | b, passWordStatus := checkKey(handler.db, config.REQ_START, handler.cfg.Secret.AppNum, operate.AppId, passWord, true) 252 | config.RealTimeStatus.Status = passWordStatus 253 | if b { 254 | if err := handler.ethHander.Start(); err == verrors.NoDataErr { 255 | } else if err != nil { 256 | config.RealTimeStatus.Status = config.PASSWORD_SYSTEM_FAILED 257 | log.Error("start err:%v", err) 258 | } else { 259 | config.RealTimeStatus.ServerStatus = config.VOUCHER_STATUS_STATED 260 | for _, coinStatu := range config.RealTimeStatus.CoinStatus { 261 | if coinStatu.Category == config.CATEGORY_BTC && coinStatu.Used == true { 262 | if err := handler.ethHander.BtcStart(); err != nil { 263 | log.Error("btc start failed. err: %s", err) 264 | } else { 265 | log.Info("btc start success. ") 266 | } 267 | } 268 | 269 | } 270 | log.Info("start success!") 271 | } 272 | } else { 273 | log.Info("start failed.") 274 | } 275 | landStatus(handler.db, config.REQ_START) 276 | } else { 277 | log.Error("cannot start, status :%s", config.RealTimeStatus.Status) 278 | } 279 | } 280 | 281 | func (handler *OperateHandler) pause(operate *config.Operate) { 282 | log.Debug("pauseHandler...") 283 | if config.RealTimeStatus.ServerStatus == config.VOUCHER_STATUS_STATED { //服务未启动 284 | //_, b := checkKey(handler.db, config.REQ_PAUSE, handler.cfg.Secret.AppNum, operate.AppId, operate.Password, true) 285 | //if b { 286 | handler.ethHander.Stop() 287 | config.RealTimeStatus.Status = config.PASSWORD_STATUS_OK 288 | config.RealTimeStatus.ServerStatus = config.VOUCHER_STATUS_PAUSED 289 | //distory memory 290 | memguard.DestroyAll() 291 | 292 | log.Info("stop success!") 293 | landStatus(handler.db, config.REQ_PAUSE) 294 | } 295 | //} else { 296 | // config.RealTimeStatus.Status = config.PASSWORD_STATUS_FAILED 297 | // 298 | // log.Info("pause failed.") 299 | //} 300 | //} else { 301 | // log.Error("cannot deploy, status :%s", config.RealTimeStatus.Status) 302 | //} 303 | } 304 | 305 | func (handler *OperateHandler) hashList(operate *config.Operate) { 306 | log.Debug("hashList...") 307 | config.RealTimeStatus.Status = config.PASSWORD_STATUS_OK 308 | config.RealTimeStatus.ServerStatus = config.VOUCHER_STATUS_PAUSED 309 | landStatus(handler.db, config.REQ_PAUSE) 310 | } 311 | 312 | func (handler *OperateHandler) addToken(operate *config.Operate) { 313 | log.Debug("addToken...") 314 | if !checkSign(operate.ContractAddr, operate.Sign, operate.AppId, handler.db) { 315 | log.Info("token add sign check err") 316 | return 317 | } 318 | tokenInfo := &config.TokenInfo{TokenName: operate.TokenName, Decimals: operate.Decimals, ContractAddr: strings.ToLower(operate.ContractAddr)} 319 | 320 | if b, l := token.AddTokenMap(tokenInfo, handler.db); !b { 321 | log.Info("add token failed.") 322 | return 323 | } else { 324 | config.RealTimeStatus.TokenCount = l 325 | } 326 | landStatus(handler.db, "") 327 | } 328 | 329 | func (handler *OperateHandler) delToken(operate *config.Operate) { 330 | log.Debug("delToken...") 331 | if !checkSign(operate.ContractAddr, operate.Sign, operate.AppId, handler.db) { 332 | log.Info("token add sign check err") 333 | return 334 | } 335 | tokenInfo := &config.TokenInfo{TokenName: operate.TokenName, Decimals: operate.Decimals, ContractAddr: strings.ToLower(operate.ContractAddr)} 336 | 337 | if b, l := token.DelTokenMap(tokenInfo, handler.db); !b { 338 | log.Info("add token failed.") 339 | } else { 340 | config.RealTimeStatus.TokenCount = l 341 | } 342 | landStatus(handler.db, "") 343 | } 344 | 345 | func (handler *OperateHandler) tokenList(operate *config.Operate) { 346 | log.Debug("tokenList...") 347 | token.TokenList(handler.db) 348 | } 349 | 350 | func (handler *OperateHandler) coin(operate *config.Operate) { 351 | log.Debug("coin...") 352 | 353 | opSuccess := false 354 | if operate.CoinCategory == config.CATEGORY_BTC { 355 | if operate.CoinUsed { 356 | if err := handler.ethHander.BtcStart(); err != nil { 357 | log.Error("btc start failed. err: %s", err) 358 | } else { 359 | opSuccess = true 360 | config.RealTimeStatus.BtcAddress = handler.ethHander.BtcHandler.GetAccount() 361 | log.Info("btc start success.") 362 | } 363 | } else { 364 | if err := handler.ethHander.BtcStop(); err != nil { 365 | log.Error("btc stop failed. err: %s", err) 366 | } else { 367 | opSuccess = true 368 | log.Info("btc stop success.") 369 | } 370 | } 371 | } 372 | if opSuccess { 373 | var newCoinStatus []config.CoinStatu 374 | isNew := true 375 | for _, coinstatu := range config.RealTimeStatus.CoinStatus { 376 | if coinstatu.Category == operate.CoinCategory { 377 | isNew = false 378 | coinstatu.Used = operate.CoinUsed 379 | } 380 | newCoinStatus = append(newCoinStatus, coinstatu) 381 | } 382 | 383 | if isNew { 384 | //TODO 385 | newCoinStatus = append(newCoinStatus, config.CoinStatu{Name: config.COIN_NAME_BTC, Category: operate.CoinCategory, Decimals: config.COIN_DECIMALS_BTC, Used: operate.CoinUsed}) 386 | } 387 | reportInfo := &config.GrpcStream{Type: config.GRPC_COIN_LIST_WEB} //上报 388 | config.ReportedChan <- reportInfo 389 | 390 | config.RealTimeStatus.CoinStatus = newCoinStatus 391 | landStatus(handler.db, "") 392 | } 393 | 394 | } 395 | 396 | type SHashPass struct { 397 | hash string 398 | pass string 399 | } 400 | 401 | func (handler *OperateHandler) operateCheckKeys(operate *config.Operate) { 402 | log.Debug("operateCheckKeys...") 403 | isTure := false 404 | 405 | if !checkSign(operate.Hash, operate.Sign, operate.AppId, handler.db){ 406 | log.Info("check hash failed") 407 | isTure = false 408 | }else { 409 | passWord := getKey(operate.Password, operate.PassSign, handler.db, operate.AppId) 410 | if passWord == "" { 411 | log.Error("password get failed.") 412 | isTure = false 413 | }else { 414 | isTure = trans.CheckPasswordHash(handler.db,operate.AppId,passWord) 415 | } 416 | } 417 | 418 | rep := &config.GrpcStream{ 419 | Type: config.GRPC_CHECK_KEY_WEB, 420 | Hash:common.HexToHash(operate.Hash), 421 | AppId:operate.AppId} 422 | if isTure { 423 | rep.Status = "TRUE" 424 | }else { 425 | rep.Status = "FALSE" 426 | } 427 | config.ReportedChan <- rep 428 | 429 | } 430 | 431 | //key generate 432 | func generateKey(db localdb.Database, reqType string, appNum int, appId string, pass string, code string, opinion bool) (bool, int) { 433 | 434 | defer apiMu.Unlock() 435 | apiMu.Lock() 436 | 437 | batchCount := batchCountMap[reqType] 438 | 439 | if batchCount == appNum { //上次输入完整 440 | batchCount = 0 441 | delete(batchCountMap, reqType) 442 | for k, _ := range authorizedMap { //删除授权信息 443 | if strings.HasPrefix(k, reqType) { 444 | delete(authorizedMap, k) 445 | } 446 | } 447 | for k, _ := range passMap { //删除 448 | if strings.HasPrefix(k, reqType) { 449 | delete(passMap, k) 450 | } 451 | } 452 | } 453 | 454 | batchCount++ 455 | batchCountMap[reqType] = batchCount 456 | 457 | passMap[reqType+appId] = pass 458 | 459 | if reqType == config.REQ_CREATE { //创建 460 | codeMap[reqType+appId] = code 461 | } 462 | 463 | authorizedMap[reqType+appId] = opinion 464 | 465 | //check passwd HASH 466 | if reqType != config.REQ_CREATE { 467 | //update 468 | authorizedMap[reqType+appId] = trans.CheckPasswordHash(db,appId,pass) 469 | } 470 | 471 | if batchCount < appNum { //未输入完整密码 472 | 473 | } else if batchCount == appNum { //全部输入完成 474 | if reqType == config.REQ_HASH { //hash确认 475 | for k, b := range authorizedMap { 476 | if strings.HasPrefix(k, reqType) { 477 | if !b { //有一个拒绝即失效 478 | return false, config.PASSWORD_STATUS_OK 479 | } 480 | } 481 | } 482 | } 483 | 484 | var passBytes [][]byte = make([][]byte, appNum) 485 | count := 0 486 | for k, v := range passMap { 487 | if strings.HasPrefix(k, reqType) { 488 | passBytes[count] = []byte(v) 489 | count++ 490 | } 491 | } 492 | 493 | if reqType == config.REQ_CREATE { //创建 494 | firstCode := "" 495 | for _, code := range codeMap { 496 | if firstCode == "" { 497 | firstCode = code 498 | } else if firstCode != code { 499 | for k, _ := range codeMap { //删除 500 | if strings.HasPrefix(k, reqType) { 501 | delete(codeMap, k) 502 | } 503 | } 504 | return false, config.PASSWORD_CODE_FAILED 505 | } 506 | } 507 | 508 | if !trans.ExistPrivateKeyHash(db) { //不存在 509 | if trans.RecoverPrivateKey(db, passBytes...) == nil { 510 | //record passwd HASH 511 | for k, v := range passMap { 512 | //get appid 513 | appid := strings.Replace(k,config.REQ_CREATE,"",-1) 514 | trans.RecordPasswordHash(db, appid, v) 515 | } 516 | return true, config.PASSWORD_STATUS_FAILED 517 | } 518 | } 519 | } else { //校验 520 | if trans.CheckPrivateKey(db, passBytes...) { 521 | //兼容老的DB操作,每次校验正确刷新用户的密码HASH 522 | for k, v := range passMap { 523 | if strings.HasPrefix(k, reqType) { 524 | //get appid 525 | appid := strings.Replace(k,reqType,"",-1) 526 | trans.RecordPasswordHash(db, appid, v) 527 | } 528 | } 529 | 530 | return true, config.PASSWORD_STATUS_OK 531 | } else { 532 | return false, config.PASSWORD_STATUS_FAILED 533 | } 534 | } 535 | } 536 | return false, config.PASSWORD_STATUS_FAILED 537 | } 538 | 539 | //校验key 540 | func checkKey(db localdb.Database, reqType string, appNum int, appId string, pass string, opinion bool) (bool, int) { 541 | return generateKey(db, reqType, appNum, appId, pass, "", opinion) 542 | } 543 | 544 | //状态持久化 545 | func landStatus(db localdb.Database, reqType string) { 546 | if reqType != "" { 547 | var nodesAuthorized []config.NodeAuthorized //从新 548 | for k, v := range authorizedMap { 549 | if strings.HasPrefix(k, reqType) { 550 | nodesAuthorized = append(nodesAuthorized, config.NodeAuthorized{ApplyerId: strings.Trim(k, reqType), Authorized: v}) 551 | } 552 | } 553 | config.RealTimeStatus.NodesAuthorized = nodesAuthorized 554 | } 555 | 556 | if statusByte, err := json.Marshal(config.RealTimeStatus); err != nil { 557 | log.Error("disallowHandler json marshal error:%v", err) 558 | } else { 559 | if err = db.Put([]byte(config.STATAUS_KEY), statusByte); err != nil { 560 | log.Error("landStatus err :%s", err) 561 | } 562 | } 563 | } 564 | 565 | func getKey(data, sign string, db localdb.Database, appId string) string { 566 | if err := util.RsaSignVer([]byte(data), []byte(sign), db, appId); err != nil { 567 | log.Error("get key err: %s", err) 568 | } else { 569 | return string(util.CBCDecrypter([]byte(data), util.GetAesKeyRandomFromDb(db))) 570 | } 571 | return "" 572 | } 573 | 574 | func checkSign(flow, sign, appId string, db localdb.Database) bool { 575 | if err := util.RsaSignVer([]byte(flow), []byte(sign), db, appId); err != nil { 576 | log.Info("check sign failed, err:%v",err) 577 | return false 578 | } 579 | return true 580 | } 581 | --------------------------------------------------------------------------------