├── .github └── workflows │ ├── build-and-test.yml │ └── golangci-lint.yml ├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── build └── ci.go ├── cmd ├── swaporacle │ └── main.go ├── swapserver │ └── main.go └── utils │ ├── flags.go │ ├── licensecmd.go │ ├── utils.go │ └── versioncmd.go ├── common ├── big.go ├── bytes.go ├── ethaddress.go ├── hash.go ├── hexutil │ ├── hexutil.go │ ├── hexutil_test.go │ ├── json.go │ ├── json_example_test.go │ └── json_test.go ├── math │ ├── big.go │ ├── big_test.go │ ├── integer.go │ └── integer_test.go ├── path.go └── utils.go ├── dcrm ├── accept.go ├── api.go ├── init.go ├── sign.go └── types.go ├── go.mod ├── go.sum ├── gofmt.sh ├── internal ├── build │ ├── env.go │ └── util.go └── swapapi │ ├── api.go │ ├── converts.go │ └── types.go ├── log ├── logger.go └── logger_test.go ├── mongodb ├── api.go ├── dbinit.go ├── errors.go ├── status.go └── tables.go ├── params ├── config.go ├── config.toml └── version.go ├── rpc ├── client │ ├── client.go │ ├── get.go │ └── post.go ├── restapi │ └── api.go ├── rpcapi │ ├── api.go │ └── buildtx.go └── server │ └── server.go ├── tokens ├── bridge │ └── bridge.go ├── btc │ ├── address.go │ ├── aggregate.go │ ├── bridge.go │ ├── buildaggregatetx.go │ ├── buildtx.go │ ├── callapi.go │ ├── electrs │ │ ├── callapi.go │ │ └── types.go │ ├── p2shaddress.go │ ├── printtx.go │ ├── processtx.go │ ├── scanchaintx.go │ ├── scanpooltx.go │ ├── scanswaphistory.go │ ├── sendtx.go │ ├── signtx.go │ ├── verifyp2shtx.go │ └── verifytx.go ├── eth │ ├── abipack.go │ ├── address.go │ ├── bridge.go │ ├── buildswapouttx.go │ ├── buildtx.go │ ├── callapi.go │ ├── callcontract.go │ ├── processtx.go │ ├── scanchaintx.go │ ├── scanpooltx.go │ ├── scanswaphistory.go │ ├── sendtx.go │ ├── signtx.go │ ├── verifycontractaddress.go │ ├── verifyerc20tx.go │ ├── verifyswapouttx.go │ └── verifytx.go ├── fsn │ ├── bridge.go │ └── callapi.go ├── interfaces.go ├── tools │ ├── cachedscanblocks.go │ ├── cachedscantxs.go │ └── swaptools.go └── types.go ├── tools ├── crypto │ ├── crypto.go │ ├── crypto_test.go │ ├── signature.go │ └── signature_test.go ├── keystore │ ├── aes.go │ ├── key.go │ └── passphrase.go ├── loadkeystore.go └── rlp │ ├── decode.go │ ├── decode_tail_test.go │ ├── decode_test.go │ ├── doc.go │ ├── encode.go │ ├── encode_test.go │ ├── encoder_example_test.go │ ├── raw.go │ ├── raw_test.go │ └── typecache.go ├── types ├── gen_tx_json.go ├── rpctypes.go ├── transaction.go └── transaction_signing.go └── worker ├── accept.go ├── aggregate.go ├── common.go ├── recall.go ├── scan.go ├── stable.go ├── swap.go ├── updatelatest.go ├── utils.go ├── verify.go └── worker.go /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - master 8 | pull_request: 9 | jobs: 10 | test: 11 | name: run 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@master 15 | - name: run test 16 | uses: cedrickring/golang-action@1.5.2 17 | with: 18 | args: make test 19 | -------------------------------------------------------------------------------- /.github/workflows/golangci-lint.yml: -------------------------------------------------------------------------------- 1 | name: golangci-lint 2 | on: 3 | push: 4 | tags: 5 | - v* 6 | branches: 7 | - master 8 | pull_request: 9 | jobs: 10 | golangci: 11 | name: lint 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: golangci-lint 16 | uses: golangci/golangci-lint-action@v1 17 | with: 18 | # Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version. 19 | version: v1.26 20 | 21 | # Optional: working directory, useful for monorepos 22 | # working-directory: somedir 23 | 24 | # Optional: golangci-lint command line arguments. 25 | # args: --issues-exit-code=0 26 | 27 | # Optional: show only new issues if it's a pull request. The default value is `false`. 28 | # only-new-issues: true 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all test testv clean fmt 2 | .PHONY: swapserver swaporacle 3 | 4 | GOBIN = ./build/bin 5 | GOCMD = env GO111MODULE=on GOPROXY=https://goproxy.io go 6 | 7 | swapserver: 8 | $(GOCMD) run build/ci.go install ./cmd/swapserver 9 | @echo "Done building." 10 | @echo "Run \"$(GOBIN)/swapserver\" to launch swapserver." 11 | 12 | swaporacle: 13 | $(GOCMD) run build/ci.go install ./cmd/swaporacle 14 | @echo "Done building." 15 | @echo "Run \"$(GOBIN)/swaporacle\" to launch swaporacle." 16 | 17 | all: 18 | $(GOCMD) build -v ./... 19 | $(GOCMD) run build/ci.go install ./cmd/... 20 | @echo "Done building." 21 | @echo "Find binaries in \"$(GOBIN)\" directory." 22 | @echo "" 23 | @echo "Copy example config.toml to \"$(GOBIN)\" directory (no overwrite if exist)." 24 | @cp -n params/config.toml $(GOBIN) 25 | 26 | test: all 27 | $(GOCMD) test ./... 28 | 29 | testv: all 30 | $(GOCMD) test -v ./... 31 | 32 | clean: 33 | $(GOCMD) clean -cache 34 | rm -fr $(GOBIN)/* 35 | 36 | fmt: 37 | ./gofmt.sh 38 | -------------------------------------------------------------------------------- /build/ci.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "log" 7 | "os" 8 | "os/exec" 9 | "path/filepath" 10 | "runtime" 11 | "strings" 12 | 13 | "github.com/fsn-dev/crossChain-Bridge/internal/build" 14 | ) 15 | 16 | var gobin, _ = filepath.Abs(filepath.Join("build", "bin")) 17 | 18 | func main() { 19 | log.SetFlags(log.Lshortfile) 20 | 21 | if _, err := os.Stat(filepath.Join("build", "ci.go")); os.IsNotExist(err) { 22 | log.Fatal("this script must be run from the root of the repository") 23 | } 24 | if len(os.Args) < 2 { 25 | log.Fatal("need subcommand as first argument") 26 | } 27 | switch os.Args[1] { 28 | case "install": 29 | doInstall(os.Args[2:]) 30 | default: 31 | log.Fatal("unknown command ", os.Args[1]) 32 | } 33 | } 34 | 35 | // Compiling 36 | 37 | func doInstall(cmdline []string) { 38 | _ = flag.CommandLine.Parse(cmdline) 39 | env := build.Env() 40 | 41 | // Check Go version. People regularly open issues about compilation 42 | // failure with outdated Go. This should save them the trouble. 43 | if !strings.Contains(runtime.Version(), "devel") { 44 | // Figure out the minor version number since we can't textually compare (1.10 < 1.9) 45 | var minor int 46 | fmt.Sscanf(strings.TrimPrefix(runtime.Version(), "go1."), "%d", &minor) 47 | 48 | if minor < 12 { 49 | log.Println("You have Go version", runtime.Version()) 50 | log.Println("requires at least Go version 1.12 and cannot") 51 | log.Println("be compiled with an earlier version. Please upgrade your Go installation.") 52 | os.Exit(1) 53 | } 54 | } 55 | // Compile packages given as arguments, or everything if there are no arguments. 56 | packages := []string{"./..."} 57 | if flag.NArg() > 0 { 58 | packages = flag.Args() 59 | } 60 | 61 | goinstall := goTool("install", buildFlags(env)...) 62 | if runtime.GOARCH == "arm64" { 63 | goinstall.Args = append(goinstall.Args, "-p", "1") 64 | } 65 | goinstall.Args = append(goinstall.Args, "-v") 66 | goinstall.Args = append(goinstall.Args, packages...) 67 | build.MustRun(goinstall) 68 | } 69 | 70 | func buildFlags(env *build.Environment) (flags []string) { 71 | var ld []string 72 | if env.Commit != "" { 73 | ld = append(ld, 74 | "-X", "main.gitCommit="+env.Commit, 75 | "-X", "main.gitDate="+env.Date, 76 | ) 77 | } 78 | if runtime.GOOS == "darwin" { 79 | ld = append(ld, "-s") 80 | } 81 | 82 | if len(ld) > 0 { 83 | flags = append(flags, "-ldflags", strings.Join(ld, " ")) 84 | } 85 | return flags 86 | } 87 | 88 | func goTool(subcmd string, args ...string) *exec.Cmd { 89 | return goToolArch(runtime.GOARCH, os.Getenv("CC"), subcmd, args...) 90 | } 91 | 92 | func goToolArch(arch, cc, subcmd string, args ...string) *exec.Cmd { 93 | cmd := build.GoTool(subcmd, args...) 94 | if arch == "" || arch == runtime.GOARCH { 95 | cmd.Env = append(cmd.Env, "GOBIN="+gobin) 96 | } else { 97 | cmd.Env = append(cmd.Env, "CGO_ENABLED=1", "GOARCH="+arch) 98 | } 99 | if cc != "" { 100 | cmd.Env = append(cmd.Env, "CC="+cc) 101 | } 102 | for _, e := range os.Environ() { 103 | if strings.HasPrefix(e, "GOBIN=") { 104 | continue 105 | } 106 | cmd.Env = append(cmd.Env, e) 107 | } 108 | return cmd 109 | } 110 | -------------------------------------------------------------------------------- /cmd/swaporacle/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "sort" 7 | 8 | "github.com/fsn-dev/crossChain-Bridge/cmd/utils" 9 | "github.com/fsn-dev/crossChain-Bridge/log" 10 | "github.com/fsn-dev/crossChain-Bridge/params" 11 | "github.com/fsn-dev/crossChain-Bridge/worker" 12 | "github.com/urfave/cli/v2" 13 | ) 14 | 15 | var ( 16 | clientIdentifier = "swaporacle" 17 | // Git SHA1 commit hash of the release (set via linker flags) 18 | gitCommit = "" 19 | // The app that holds all commands and flags. 20 | app = utils.NewApp(clientIdentifier, gitCommit, "the swaporacle command line interface") 21 | ) 22 | 23 | func initApp() { 24 | // Initialize the CLI app and start action 25 | app.Action = swaporacle 26 | app.HideVersion = true // we have a command to print the version 27 | app.Copyright = "Copyright 2017-2020 The crossChain-Bridge Authors" 28 | app.Commands = []*cli.Command{ 29 | utils.LicenseCommand, 30 | utils.VersionCommand, 31 | } 32 | app.Flags = []cli.Flag{ 33 | utils.DataDirFlag, 34 | utils.ConfigFileFlag, 35 | utils.LogFileFlag, 36 | utils.LogRotationFlag, 37 | utils.LogMaxAgeFlag, 38 | utils.VerbosityFlag, 39 | utils.JSONFormatFlag, 40 | utils.ColorFormatFlag, 41 | } 42 | sort.Sort(cli.CommandsByName(app.Commands)) 43 | } 44 | 45 | func main() { 46 | initApp() 47 | if err := app.Run(os.Args); err != nil { 48 | log.Println(err) 49 | os.Exit(1) 50 | } 51 | } 52 | 53 | func swaporacle(ctx *cli.Context) error { 54 | utils.SetLogger(ctx) 55 | if ctx.NArg() > 0 { 56 | return fmt.Errorf("invalid command: %q", ctx.Args().Get(0)) 57 | } 58 | exitCh := make(chan struct{}) 59 | configFile := utils.GetConfigFilePath(ctx) 60 | params.LoadConfig(configFile, false) 61 | 62 | params.SetDataDir(ctx.String(utils.DataDirFlag.Name)) 63 | 64 | worker.StartWork(false) 65 | 66 | <-exitCh 67 | return nil 68 | } 69 | -------------------------------------------------------------------------------- /cmd/swapserver/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "sort" 7 | "time" 8 | 9 | "github.com/fsn-dev/crossChain-Bridge/cmd/utils" 10 | "github.com/fsn-dev/crossChain-Bridge/log" 11 | "github.com/fsn-dev/crossChain-Bridge/mongodb" 12 | "github.com/fsn-dev/crossChain-Bridge/params" 13 | rpcserver "github.com/fsn-dev/crossChain-Bridge/rpc/server" 14 | "github.com/fsn-dev/crossChain-Bridge/worker" 15 | "github.com/urfave/cli/v2" 16 | ) 17 | 18 | var ( 19 | clientIdentifier = "swapserver" 20 | // Git SHA1 commit hash of the release (set via linker flags) 21 | gitCommit = "" 22 | // The app that holds all commands and flags. 23 | app = utils.NewApp(clientIdentifier, gitCommit, "the swapserver command line interface") 24 | ) 25 | 26 | func initApp() { 27 | // Initialize the CLI app and start action 28 | app.Action = swapserver 29 | app.HideVersion = true // we have a command to print the version 30 | app.Copyright = "Copyright 2017-2020 The crossChain-Bridge Authors" 31 | app.Commands = []*cli.Command{ 32 | utils.LicenseCommand, 33 | utils.VersionCommand, 34 | } 35 | app.Flags = []cli.Flag{ 36 | utils.DataDirFlag, 37 | utils.ConfigFileFlag, 38 | utils.LogFileFlag, 39 | utils.LogRotationFlag, 40 | utils.LogMaxAgeFlag, 41 | utils.VerbosityFlag, 42 | utils.JSONFormatFlag, 43 | utils.ColorFormatFlag, 44 | } 45 | sort.Sort(cli.CommandsByName(app.Commands)) 46 | } 47 | 48 | func main() { 49 | initApp() 50 | if err := app.Run(os.Args); err != nil { 51 | log.Println(err) 52 | os.Exit(1) 53 | } 54 | } 55 | 56 | func swapserver(ctx *cli.Context) error { 57 | utils.SetLogger(ctx) 58 | if ctx.NArg() > 0 { 59 | return fmt.Errorf("invalid command: %q", ctx.Args().Get(0)) 60 | } 61 | exitCh := make(chan struct{}) 62 | configFile := utils.GetConfigFilePath(ctx) 63 | config := params.LoadConfig(configFile, true) 64 | 65 | params.SetDataDir(ctx.String(utils.DataDirFlag.Name)) 66 | 67 | dbConfig := config.MongoDB 68 | mongoURL := dbConfig.GetURL() 69 | dbName := dbConfig.DBName 70 | mongodb.MongoServerInit(mongoURL, dbName) 71 | 72 | worker.StartWork(true) 73 | time.Sleep(100 * time.Millisecond) 74 | rpcserver.StartAPIServer() 75 | 76 | <-exitCh 77 | return nil 78 | } 79 | -------------------------------------------------------------------------------- /cmd/utils/flags.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "github.com/fsn-dev/crossChain-Bridge/log" 5 | "github.com/urfave/cli/v2" 6 | ) 7 | 8 | var ( 9 | // DataDirFlag --datadir 10 | DataDirFlag = &cli.StringFlag{ 11 | Name: "datadir", 12 | Usage: "Data directory (default in the execute directory)", 13 | Value: "", 14 | } 15 | // ConfigFileFlag -c|--config 16 | ConfigFileFlag = &cli.StringFlag{ 17 | Name: "config", 18 | Aliases: []string{"c"}, 19 | Usage: "Specify config file", 20 | } 21 | // LogFileFlag --log 22 | LogFileFlag = &cli.StringFlag{ 23 | Name: "log", 24 | Usage: "Specify log file, support rotate", 25 | } 26 | // LogRotationFlag --rotate 27 | LogRotationFlag = &cli.Uint64Flag{ 28 | Name: "rotate", 29 | Usage: "log rotation time (unit hour)", 30 | Value: 24, 31 | } 32 | // LogMaxAgeFlag --maxage 33 | LogMaxAgeFlag = &cli.Uint64Flag{ 34 | Name: "maxage", 35 | Usage: "log max age (unit hour)", 36 | Value: 720, 37 | } 38 | // VerbosityFlag -v|--verbosity 39 | VerbosityFlag = &cli.Uint64Flag{ 40 | Name: "verbosity", 41 | Aliases: []string{"v"}, 42 | Usage: "log verbosity (0:panic, 1:fatal, 2:error, 3:warn, 4:info, 5:debug, 6:trace)", 43 | Value: 4, 44 | } 45 | // JSONFormatFlag --json 46 | JSONFormatFlag = &cli.BoolFlag{ 47 | Name: "json", 48 | Usage: "output log in json format", 49 | } 50 | // ColorFormatFlag --color 51 | ColorFormatFlag = &cli.BoolFlag{ 52 | Name: "color", 53 | Usage: "output log in color text format", 54 | Value: true, 55 | } 56 | ) 57 | 58 | // SetLogger set log level, json format, color, rotate ... 59 | func SetLogger(ctx *cli.Context) { 60 | logLevel := ctx.Uint64(VerbosityFlag.Name) 61 | jsonFormat := ctx.Bool(JSONFormatFlag.Name) 62 | colorFormat := ctx.Bool(ColorFormatFlag.Name) 63 | log.SetLogger(uint32(logLevel), jsonFormat, colorFormat) 64 | 65 | logFile := ctx.String(LogFileFlag.Name) 66 | if logFile != "" { 67 | logRotation := ctx.Uint64(LogRotationFlag.Name) 68 | logMaxAge := ctx.Uint64(LogMaxAgeFlag.Name) 69 | log.SetLogFile(logFile, logRotation, logMaxAge) 70 | } 71 | } 72 | 73 | // GetConfigFilePath specified by `-c|--config` 74 | func GetConfigFilePath(ctx *cli.Context) string { 75 | return ctx.String(ConfigFileFlag.Name) 76 | } 77 | -------------------------------------------------------------------------------- /cmd/utils/licensecmd.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/urfave/cli/v2" 7 | ) 8 | 9 | var ( 10 | // LicenseCommand license cubcommonad 11 | LicenseCommand = &cli.Command{ 12 | Action: license, 13 | Name: "license", 14 | Usage: "Display license information", 15 | ArgsUsage: " ", 16 | } 17 | ) 18 | 19 | func license(_ *cli.Context) error { 20 | fmt.Println(`crossChain-Bridge is free software: you can redistribute it and/or modify 21 | it under the terms of the GNU General Public License as published by 22 | the Free Software Foundation, either version 3 of the License, or 23 | (at your option) any later version. 24 | 25 | crossChain-Bridge is distributed in the hope that it will be useful, 26 | but WITHOUT ANY WARRANTY; without even the implied warranty of 27 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 28 | GNU General Public License for more details. 29 | 30 | You should have received a copy of the GNU General Public License 31 | along with efsn. If not, see .`) 32 | return nil 33 | } 34 | -------------------------------------------------------------------------------- /cmd/utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | 7 | "github.com/fsn-dev/crossChain-Bridge/params" 8 | "github.com/urfave/cli/v2" 9 | ) 10 | 11 | var ( 12 | clientIdentifier string 13 | gitCommit string 14 | ) 15 | 16 | // NewApp creates an app with sane defaults. 17 | func NewApp(identifier, gitcommit, usage string) *cli.App { 18 | clientIdentifier = identifier 19 | gitCommit = gitcommit 20 | app := cli.NewApp() 21 | app.Name = filepath.Base(os.Args[0]) 22 | app.Version = params.VersionWithMeta 23 | if len(gitCommit) >= 8 { 24 | app.Version += "-" + gitCommit[:8] 25 | } 26 | app.Usage = usage 27 | return app 28 | } 29 | -------------------------------------------------------------------------------- /cmd/utils/versioncmd.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "runtime" 7 | "strings" 8 | 9 | "github.com/fsn-dev/crossChain-Bridge/params" 10 | "github.com/urfave/cli/v2" 11 | ) 12 | 13 | var ( 14 | // VersionCommand version subcommand 15 | VersionCommand = &cli.Command{ 16 | Action: version, 17 | Name: "version", 18 | Usage: "Print version numbers", 19 | ArgsUsage: " ", 20 | Description: ` 21 | The output of this command is supposed to be machine-readable. 22 | `, 23 | } 24 | ) 25 | 26 | func version(ctx *cli.Context) error { 27 | fmt.Println(strings.Title(clientIdentifier)) 28 | fmt.Println("Version:", params.VersionWithMeta) 29 | if gitCommit != "" { 30 | fmt.Println("Git Commit:", gitCommit) 31 | } 32 | fmt.Println("Architecture:", runtime.GOARCH) 33 | fmt.Println("Go Version:", runtime.Version()) 34 | fmt.Println("Operating System:", runtime.GOOS) 35 | fmt.Printf("GOPATH=%s\n", os.Getenv("GOPATH")) 36 | fmt.Printf("GOROOT=%s\n", runtime.GOROOT()) 37 | return nil 38 | } 39 | -------------------------------------------------------------------------------- /common/big.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | 17 | package common 18 | 19 | import ( 20 | "math" 21 | "math/big" 22 | ) 23 | 24 | // Common big integers often used 25 | var ( 26 | Big1 = big.NewInt(1) 27 | Big2 = big.NewInt(2) 28 | Big3 = big.NewInt(3) 29 | Big0 = big.NewInt(0) 30 | Big32 = big.NewInt(32) 31 | Big256 = big.NewInt(256) 32 | Big257 = big.NewInt(257) 33 | 34 | BigMaxUint64 = new(big.Int).SetUint64(math.MaxUint64) 35 | ) 36 | -------------------------------------------------------------------------------- /common/bytes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | 17 | // Package common contains various helper functions. 18 | package common 19 | 20 | import "encoding/hex" 21 | 22 | // ToHex returns the hex representation of b, prefixed with '0x'. 23 | func ToHex(b []byte) string { 24 | enc := make([]byte, len(b)*2+2) 25 | copy(enc, "0x") 26 | hex.Encode(enc[2:], b) 27 | return string(enc) 28 | } 29 | 30 | // FromHex returns the bytes represented by the hexadecimal string s. 31 | // s may be prefixed with "0x". 32 | func FromHex(s string) []byte { 33 | if len(s) > 1 { 34 | if s[0:2] == "0x" || s[0:2] == "0X" { 35 | s = s[2:] 36 | } 37 | } 38 | if len(s)%2 == 1 { 39 | s = "0" + s 40 | } 41 | return Hex2Bytes(s) 42 | } 43 | 44 | // CopyBytes returns an exact copy of the provided bytes. 45 | func CopyBytes(b []byte) (copiedBytes []byte) { 46 | if b == nil { 47 | return nil 48 | } 49 | copiedBytes = make([]byte, len(b)) 50 | copy(copiedBytes, b) 51 | 52 | return 53 | } 54 | 55 | // HasHexPrefix validates str begins with '0x' or '0X'. 56 | func HasHexPrefix(str string) bool { 57 | return len(str) >= 2 && str[0] == '0' && (str[1] == 'x' || str[1] == 'X') 58 | } 59 | 60 | // IsHexCharacter returns bool of c being a valid hexadecimal. 61 | func IsHexCharacter(c byte) bool { 62 | return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') 63 | } 64 | 65 | // IsUpperHexCharacter returns bool of c being a valid uppercase hexadecimal. 66 | func IsUpperHexCharacter(c byte) bool { 67 | return c >= 'A' && c <= 'F' 68 | } 69 | 70 | // IsHex validates whether each byte is valid hexadecimal string. 71 | func IsHex(str string) bool { 72 | if len(str)%2 != 0 { 73 | return false 74 | } 75 | for _, c := range []byte(str) { 76 | if !IsHexCharacter(c) { 77 | return false 78 | } 79 | } 80 | return true 81 | } 82 | 83 | // GetUnprefixedHex returns (unprefixed hex, is hex string flag, if has uppercase hexadecimal) 84 | func GetUnprefixedHex(str string) (unprefixedHex string, ok, hasUpperChar bool) { 85 | if len(str)%2 != 0 { 86 | return 87 | } 88 | if HasHexPrefix(str) { 89 | str = str[2:] 90 | } 91 | for _, c := range []byte(str) { 92 | if !IsHexCharacter(c) { 93 | return 94 | } 95 | if !hasUpperChar && IsUpperHexCharacter(c) { 96 | hasUpperChar = true 97 | } 98 | } 99 | unprefixedHex = str 100 | ok = true 101 | return 102 | } 103 | 104 | // Bytes2Hex returns the hexadecimal encoding of d. 105 | func Bytes2Hex(d []byte) string { 106 | return hex.EncodeToString(d) 107 | } 108 | 109 | // Hex2Bytes returns the bytes represented by the hexadecimal string str. 110 | func Hex2Bytes(str string) []byte { 111 | h, _ := hex.DecodeString(str) 112 | return h 113 | } 114 | 115 | // Hex2BytesFixed returns bytes of a specified fixed length flen. 116 | func Hex2BytesFixed(str string, flen int) []byte { 117 | h, _ := hex.DecodeString(str) 118 | if len(h) == flen { 119 | return h 120 | } 121 | if len(h) > flen { 122 | return h[len(h)-flen:] 123 | } 124 | hh := make([]byte, flen) 125 | copy(hh[flen-len(h):flen], h) 126 | return hh 127 | } 128 | 129 | // RightPadBytes zero-pads slice to the right up to length l. 130 | func RightPadBytes(slice []byte, l int) []byte { 131 | if l <= len(slice) { 132 | return slice 133 | } 134 | 135 | padded := make([]byte, l) 136 | copy(padded, slice) 137 | 138 | return padded 139 | } 140 | 141 | // LeftPadBytes zero-pads slice to the left up to length l. 142 | func LeftPadBytes(slice []byte, l int) []byte { 143 | if l <= len(slice) { 144 | return slice 145 | } 146 | 147 | padded := make([]byte, l) 148 | copy(padded[l-len(slice):], slice) 149 | 150 | return padded 151 | } 152 | -------------------------------------------------------------------------------- /common/hash.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "encoding/hex" 5 | "fmt" 6 | "math/big" 7 | "math/rand" 8 | "reflect" 9 | 10 | "github.com/fsn-dev/crossChain-Bridge/common/hexutil" 11 | ) 12 | 13 | // Lengths of hashes in bytes. 14 | const ( 15 | // HashLength is the expected length of the hash 16 | HashLength = 32 17 | ) 18 | 19 | var ( 20 | hashT = reflect.TypeOf(Hash{}) 21 | ) 22 | 23 | // Hash represents the 32 byte Keccak256 hash of arbitrary data. 24 | type Hash [HashLength]byte 25 | 26 | // BytesToHash sets b to hash. 27 | // If b is larger than len(h), b will be cropped from the left. 28 | func BytesToHash(b []byte) Hash { 29 | var h Hash 30 | h.SetBytes(b) 31 | return h 32 | } 33 | 34 | // BigToHash sets byte representation of b to hash. 35 | // If b is larger than len(h), b will be cropped from the left. 36 | func BigToHash(b *big.Int) Hash { return BytesToHash(b.Bytes()) } 37 | 38 | // HexToHash sets byte representation of s to hash. 39 | // If b is larger than len(h), b will be cropped from the left. 40 | func HexToHash(s string) Hash { return BytesToHash(FromHex(s)) } 41 | 42 | // Bytes gets the byte representation of the underlying hash. 43 | func (h Hash) Bytes() []byte { return h[:] } 44 | 45 | // Big converts a hash to a big integer. 46 | func (h Hash) Big() *big.Int { return new(big.Int).SetBytes(h[:]) } 47 | 48 | // Hex converts a hash to a hex string. 49 | func (h Hash) Hex() string { return hexutil.Encode(h[:]) } 50 | 51 | // TerminalString implements log.TerminalStringer, formatting a string for console 52 | // output during logging. 53 | func (h Hash) TerminalString() string { 54 | return fmt.Sprintf("%x…%x", h[:3], h[29:]) 55 | } 56 | 57 | // String implements the stringer interface and is used also by the logger when 58 | // doing full logging into a file. 59 | func (h Hash) String() string { 60 | return h.Hex() 61 | } 62 | 63 | // Format implements fmt.Formatter, forcing the byte slice to be formatted as is, 64 | // without going through the stringer interface used for logging. 65 | func (h Hash) Format(s fmt.State, c rune) { 66 | fmt.Fprintf(s, "%"+string(c), h[:]) 67 | } 68 | 69 | // UnmarshalText parses a hash in hex syntax. 70 | func (h *Hash) UnmarshalText(input []byte) error { 71 | return hexutil.UnmarshalFixedText("Hash", input, h[:]) 72 | } 73 | 74 | // UnmarshalJSON parses a hash in hex syntax. 75 | func (h *Hash) UnmarshalJSON(input []byte) error { 76 | return hexutil.UnmarshalFixedJSON(hashT, input, h[:]) 77 | } 78 | 79 | // MarshalText returns the hex representation of h. 80 | func (h Hash) MarshalText() ([]byte, error) { 81 | return hexutil.Bytes(h[:]).MarshalText() 82 | } 83 | 84 | // SetBytes sets the hash to the value of b. 85 | // If b is larger than len(h), b will be cropped from the left. 86 | func (h *Hash) SetBytes(b []byte) { 87 | if len(b) > len(h) { 88 | b = b[len(b)-HashLength:] 89 | } 90 | 91 | copy(h[HashLength-len(b):], b) 92 | } 93 | 94 | // Generate implements testing/quick.Generator. 95 | func (h Hash) Generate(rander *rand.Rand, size int) reflect.Value { 96 | m := rander.Intn(len(h)) 97 | for i := len(h) - 1; i > m; i-- { 98 | h[i] = byte(rander.Uint32()) 99 | } 100 | return reflect.ValueOf(h) 101 | } 102 | 103 | // Scan implements Scanner for database/sql. 104 | func (h *Hash) Scan(src interface{}) error { 105 | srcB, ok := src.([]byte) 106 | if !ok { 107 | return fmt.Errorf("can't scan %T into Hash", src) 108 | } 109 | if len(srcB) != HashLength { 110 | return fmt.Errorf("can't scan []byte of len %d into Hash, want %d", len(srcB), HashLength) 111 | } 112 | copy(h[:], srcB) 113 | return nil 114 | } 115 | 116 | // ImplementsGraphQLType returns true if Hash implements the specified GraphQL type. 117 | func (Hash) ImplementsGraphQLType(name string) bool { return name == "Bytes32" } 118 | 119 | // UnmarshalGraphQL unmarshals the provided GraphQL query data. 120 | func (h *Hash) UnmarshalGraphQL(input interface{}) error { 121 | var err error 122 | switch input := input.(type) { 123 | case string: 124 | err = h.UnmarshalText([]byte(input)) 125 | default: 126 | err = fmt.Errorf("unexpected type %T for Hash", input) 127 | } 128 | return err 129 | } 130 | 131 | // UnprefixedHash allows marshaling a Hash without 0x prefix. 132 | type UnprefixedHash Hash 133 | 134 | // UnmarshalText decodes the hash from hex. The 0x prefix is optional. 135 | func (h *UnprefixedHash) UnmarshalText(input []byte) error { 136 | return hexutil.UnmarshalFixedUnprefixedText("UnprefixedHash", input, h[:]) 137 | } 138 | 139 | // MarshalText encodes the hash as hex. 140 | func (h UnprefixedHash) MarshalText() ([]byte, error) { 141 | return []byte(hex.EncodeToString(h[:])), nil 142 | } 143 | -------------------------------------------------------------------------------- /common/hexutil/json_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | 17 | package hexutil_test 18 | 19 | import ( 20 | "encoding/json" 21 | "fmt" 22 | 23 | "github.com/fsn-dev/crossChain-Bridge/common/hexutil" 24 | ) 25 | 26 | type MyType [5]byte 27 | 28 | func (v *MyType) UnmarshalText(input []byte) error { 29 | return hexutil.UnmarshalFixedText("MyType", input, v[:]) 30 | } 31 | 32 | func (v MyType) String() string { 33 | return hexutil.Bytes(v[:]).String() 34 | } 35 | 36 | func ExampleUnmarshalFixedText() { 37 | var v1, v2 MyType 38 | fmt.Println("v1 error:", json.Unmarshal([]byte(`"0x01"`), &v1)) 39 | fmt.Println("v2 error:", json.Unmarshal([]byte(`"0x0101010101"`), &v2)) 40 | fmt.Println("v2:", v2) 41 | // Output: 42 | // v1 error: hex string has length 2, want 10 for MyType 43 | // v2 error: 44 | // v2: 0x0101010101 45 | } 46 | -------------------------------------------------------------------------------- /common/math/integer.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | 17 | package math 18 | 19 | import ( 20 | "fmt" 21 | "strconv" 22 | ) 23 | 24 | // Integer limit values. 25 | const ( 26 | MaxInt8 = 1<<7 - 1 27 | MinInt8 = -1 << 7 28 | MaxInt16 = 1<<15 - 1 29 | MinInt16 = -1 << 15 30 | MaxInt32 = 1<<31 - 1 31 | MinInt32 = -1 << 31 32 | MaxInt64 = 1<<63 - 1 33 | MinInt64 = -1 << 63 34 | MaxUint8 = 1<<8 - 1 35 | MaxUint16 = 1<<16 - 1 36 | MaxUint32 = 1<<32 - 1 37 | MaxUint64 = 1<<64 - 1 38 | ) 39 | 40 | // HexOrDecimal64 marshals uint64 as hex or decimal. 41 | type HexOrDecimal64 uint64 42 | 43 | // UnmarshalText implements encoding.TextUnmarshaler. 44 | func (i *HexOrDecimal64) UnmarshalText(input []byte) error { 45 | num, ok := ParseUint64(string(input)) 46 | if !ok { 47 | return fmt.Errorf("invalid hex or decimal integer %q", input) 48 | } 49 | *i = HexOrDecimal64(num) 50 | return nil 51 | } 52 | 53 | // MarshalText implements encoding.TextMarshaler. 54 | func (i HexOrDecimal64) MarshalText() ([]byte, error) { 55 | return []byte(fmt.Sprintf("%#x", uint64(i))), nil 56 | } 57 | 58 | // ParseInt parses s as an integer in decimal syntax 59 | func ParseInt(s string) (int, error) { 60 | return strconv.Atoi(s) 61 | } 62 | 63 | // MustParseInt parses s as an integer and panics if the string is invalid. 64 | func MustParseInt(s string) int { 65 | v, err := ParseInt(s) 66 | if err != nil { 67 | panic("invalid signed integer: " + s) 68 | } 69 | return v 70 | } 71 | 72 | // ParseUint64 parses s as an integer in decimal or hexadecimal syntax. 73 | // Leading zeros are accepted. The empty string parses as zero. 74 | func ParseUint64(s string) (uint64, bool) { 75 | if s == "" { 76 | return 0, true 77 | } 78 | if len(s) >= 2 && (s[:2] == "0x" || s[:2] == "0X") { 79 | v, err := strconv.ParseUint(s[2:], 16, 64) 80 | return v, err == nil 81 | } 82 | v, err := strconv.ParseUint(s, 10, 64) 83 | return v, err == nil 84 | } 85 | 86 | // MustParseUint64 parses s as an integer and panics if the string is invalid. 87 | func MustParseUint64(s string) uint64 { 88 | v, ok := ParseUint64(s) 89 | if !ok { 90 | panic("invalid unsigned 64 bit integer: " + s) 91 | } 92 | return v 93 | } 94 | 95 | // NOTE: The following methods need to be optimized using either bit checking or asm 96 | 97 | // SafeSub returns subtraction result and whether overflow occurred. 98 | func SafeSub(x, y uint64) (uint64, bool) { 99 | return x - y, x < y 100 | } 101 | 102 | // SafeAdd returns the result and whether overflow occurred. 103 | func SafeAdd(x, y uint64) (uint64, bool) { 104 | return x + y, y > MaxUint64-x 105 | } 106 | 107 | // SafeMul returns multiplication result and whether overflow occurred. 108 | func SafeMul(x, y uint64) (uint64, bool) { 109 | if x == 0 || y == 0 { 110 | return 0, false 111 | } 112 | return x * y, y > MaxUint64/x 113 | } 114 | -------------------------------------------------------------------------------- /common/math/integer_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | 17 | package math 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | type operation byte 24 | 25 | const ( 26 | sub operation = iota 27 | add 28 | mul 29 | ) 30 | 31 | func TestOverflow(t *testing.T) { 32 | for i, test := range []struct { 33 | x uint64 34 | y uint64 35 | overflow bool 36 | op operation 37 | }{ 38 | // add operations 39 | {MaxUint64, 1, true, add}, 40 | {MaxUint64 - 1, 1, false, add}, 41 | 42 | // sub operations 43 | {0, 1, true, sub}, 44 | {0, 0, false, sub}, 45 | 46 | // mul operations 47 | {0, 0, false, mul}, 48 | {10, 10, false, mul}, 49 | {MaxUint64, 2, true, mul}, 50 | {MaxUint64, 1, false, mul}, 51 | } { 52 | var overflows bool 53 | switch test.op { 54 | case sub: 55 | _, overflows = SafeSub(test.x, test.y) 56 | case add: 57 | _, overflows = SafeAdd(test.x, test.y) 58 | case mul: 59 | _, overflows = SafeMul(test.x, test.y) 60 | } 61 | 62 | if test.overflow != overflows { 63 | t.Errorf("%d failed. Expected test to be %v, got %v", i, test.overflow, overflows) 64 | } 65 | } 66 | } 67 | 68 | func TestHexOrDecimal64(t *testing.T) { 69 | tests := []struct { 70 | input string 71 | num uint64 72 | ok bool 73 | }{ 74 | {"", 0, true}, 75 | {"0", 0, true}, 76 | {"0x0", 0, true}, 77 | {"12345678", 12345678, true}, 78 | {"0x12345678", 0x12345678, true}, 79 | {"0X12345678", 0x12345678, true}, 80 | // Tests for leading zero behaviour: 81 | {"0123456789", 123456789, true}, // note: not octal 82 | {"0x00", 0, true}, 83 | {"0x012345678abc", 0x12345678abc, true}, 84 | // Invalid syntax: 85 | {"abcdef", 0, false}, 86 | {"0xgg", 0, false}, 87 | // Doesn't fit into 64 bits: 88 | {"18446744073709551617", 0, false}, 89 | } 90 | for _, test := range tests { 91 | var num HexOrDecimal64 92 | err := num.UnmarshalText([]byte(test.input)) 93 | if (err == nil) != test.ok { 94 | t.Errorf("ParseUint64(%q) -> (err == nil) = %t, want %t", test.input, err == nil, test.ok) 95 | continue 96 | } 97 | if err == nil && uint64(num) != test.num { 98 | t.Errorf("ParseUint64(%q) -> %d, want %d", test.input, num, test.num) 99 | } 100 | } 101 | } 102 | 103 | func TestMustParseUint64(t *testing.T) { 104 | if v := MustParseUint64("12345"); v != 12345 { 105 | t.Errorf(`MustParseUint64("12345") = %d, want 12345`, v) 106 | } 107 | } 108 | 109 | func TestMustParseUint64Panic(t *testing.T) { 110 | defer func() { 111 | if recover() == nil { 112 | t.Error("MustParseBig should've panicked") 113 | } 114 | }() 115 | MustParseUint64("ggg") 116 | } 117 | -------------------------------------------------------------------------------- /common/path.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | 17 | package common 18 | 19 | import ( 20 | "fmt" 21 | "os" 22 | "path/filepath" 23 | "runtime" 24 | ) 25 | 26 | // MakeName creates a node name that follows the ethereum convention 27 | // for such names. It adds the operation system name and Go runtime version 28 | // the name. 29 | func MakeName(name, version string) string { 30 | return fmt.Sprintf("%s/v%s/%s/%s", name, version, runtime.GOOS, runtime.Version()) 31 | } 32 | 33 | // FileExist checks if a file exists at filePath. 34 | func FileExist(filePath string) bool { 35 | _, err := os.Stat(filePath) 36 | if err != nil && os.IsNotExist(err) { 37 | return false 38 | } 39 | 40 | return true 41 | } 42 | 43 | // AbsolutePath returns datadir + filename, or filename if it is absolute. 44 | func AbsolutePath(datadir, filename string) string { 45 | if filepath.IsAbs(filename) { 46 | return filename 47 | } 48 | return filepath.Join(datadir, filename) 49 | } 50 | 51 | // ExecuteDir returns the execute directory 52 | func ExecuteDir() (string, error) { 53 | return filepath.Abs(filepath.Dir(os.Args[0])) 54 | } 55 | -------------------------------------------------------------------------------- /common/utils.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "math/big" 7 | "strconv" 8 | "strings" 9 | "time" 10 | 11 | cmath "github.com/fsn-dev/crossChain-Bridge/common/math" 12 | "golang.org/x/crypto/sha3" 13 | ) 14 | 15 | // ToJSONString to json string 16 | func ToJSONString(content interface{}, pretty bool) string { 17 | var data []byte 18 | if pretty { 19 | data, _ = json.MarshalIndent(content, "", " ") 20 | } else { 21 | data, _ = json.Marshal(content) 22 | } 23 | return string(data) 24 | } 25 | 26 | // Keccak256Hash calc keccak hash. 27 | func Keccak256Hash(data ...[]byte) (h Hash) { 28 | d := sha3.NewLegacyKeccak256() 29 | for _, b := range data { 30 | _, _ = d.Write(b) 31 | } 32 | d.Sum(h[:0]) 33 | return h 34 | } 35 | 36 | // IsEqualIgnoreCase returns if s1 and s2 are equal ignore case. 37 | func IsEqualIgnoreCase(s1, s2 string) bool { 38 | return strings.EqualFold(s1, s2) 39 | } 40 | 41 | // BigFromUint64 new big int from uint64 value. 42 | func BigFromUint64(value uint64) *big.Int { 43 | return new(big.Int).SetUint64(value) 44 | } 45 | 46 | // GetBigIntFromStr new big int from string. 47 | func GetBigIntFromStr(str string) (*big.Int, error) { 48 | bi, ok := cmath.ParseBig256(str) 49 | if !ok { 50 | return nil, errors.New("invalid 256 bit integer: " + str) 51 | } 52 | return bi, nil 53 | } 54 | 55 | // GetIntFromStr get int from string. 56 | func GetIntFromStr(str string) (int, error) { 57 | res, err := cmath.ParseInt(str) 58 | if err != nil { 59 | return 0, errors.New("invalid signed integer: " + str) 60 | } 61 | return res, nil 62 | } 63 | 64 | // GetUint64FromStr get uint64 from string. 65 | func GetUint64FromStr(str string) (uint64, error) { 66 | res, ok := cmath.ParseUint64(str) 67 | if !ok { 68 | return 0, errors.New("invalid unsigned 64 bit integer: " + str) 69 | } 70 | return res, nil 71 | } 72 | 73 | // Now returns timestamp of the point of calling. 74 | func Now() int64 { 75 | return time.Now().Unix() 76 | } 77 | 78 | // NowStr returns now timestamp of string format. 79 | func NowStr() string { 80 | return strconv.FormatInt((time.Now().Unix()), 10) 81 | } 82 | 83 | // NowMilli returns now timestamp in miliseconds 84 | func NowMilli() int64 { 85 | return time.Now().UnixNano() / 1e6 86 | } 87 | 88 | // NowMilliStr returns now timestamp in miliseconds of string format. 89 | func NowMilliStr() string { 90 | return strconv.FormatInt((time.Now().UnixNano() / 1e6), 10) 91 | } 92 | 93 | // MinUint64 get minimum value of x and y 94 | func MinUint64(x, y uint64) uint64 { 95 | if x <= y { 96 | return x 97 | } 98 | return y 99 | } 100 | 101 | // MaxUint64 get maximum calue of x and y. 102 | func MaxUint64(x, y uint64) uint64 { 103 | if x < y { 104 | return y 105 | } 106 | return x 107 | } 108 | 109 | // GetData get data[start:start+size] (won't out of index range), 110 | // and right padding the bytes to size long 111 | func GetData(data []byte, start, size uint64) []byte { 112 | length := uint64(len(data)) 113 | if start > length { 114 | start = length 115 | } 116 | end := start + size 117 | if end > length { 118 | end = length 119 | } 120 | return RightPadBytes(data[start:end], int(size)) 121 | } 122 | 123 | // BigUint64 big to uint64 and an overflow flag. 124 | func BigUint64(v *big.Int) (uint64, bool) { 125 | return v.Uint64(), !v.IsUint64() 126 | } 127 | 128 | // GetBigInt get big int from data[start:start+size] 129 | func GetBigInt(data []byte, start, size uint64) *big.Int { 130 | return new(big.Int).SetBytes(GetData(data, start, size)) 131 | } 132 | 133 | // GetUint64 get uint64 from data[start:start+size] 134 | func GetUint64(data []byte, start, size uint64) (uint64, bool) { 135 | return BigUint64(GetBigInt(data, start, size)) 136 | } 137 | -------------------------------------------------------------------------------- /dcrm/accept.go: -------------------------------------------------------------------------------- 1 | package dcrm 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/fsn-dev/crossChain-Bridge/common" 7 | ) 8 | 9 | // DoAcceptSign accept sign 10 | func DoAcceptSign(keyID, agreeResult string, msgHash, msgContext []string) (string, error) { 11 | nonce := uint64(0) 12 | data := AcceptData{ 13 | TxType: "ACCEPTSIGN", 14 | Key: keyID, 15 | Accept: agreeResult, 16 | MsgHash: msgHash, 17 | //MsgContext: msgContext, // context is verified on top level 18 | TimeStamp: common.NowMilliStr(), 19 | } 20 | payload, err := json.Marshal(data) 21 | if err != nil { 22 | return "", err 23 | } 24 | rawTX, err := BuildDcrmRawTx(nonce, payload) 25 | if err != nil { 26 | return "", err 27 | } 28 | return AcceptSign(rawTX) 29 | } 30 | -------------------------------------------------------------------------------- /dcrm/api.go: -------------------------------------------------------------------------------- 1 | package dcrm 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | 8 | "github.com/fsn-dev/crossChain-Bridge/common" 9 | "github.com/fsn-dev/crossChain-Bridge/log" 10 | "github.com/fsn-dev/crossChain-Bridge/rpc/client" 11 | ) 12 | 13 | // get dcrm sign status error 14 | var ( 15 | ErrGetSignStatusTimeout = errors.New("getSignStatus timeout") 16 | ErrGetSignStatusFailed = errors.New("getSignStatus failure") 17 | ) 18 | 19 | const ( 20 | successStatus = "Success" 21 | ) 22 | 23 | func newWrongStatusError(subject, status, errInfo string) error { 24 | return fmt.Errorf("[%v] Wrong status \"%v\", err=\"%v\"", subject, status, errInfo) 25 | } 26 | 27 | func wrapPostError(method string, err error) error { 28 | return fmt.Errorf("[post] %v error, %v", method, err) 29 | } 30 | 31 | func httpPost(result interface{}, method string, params ...interface{}) error { 32 | return client.RPCPost(&result, dcrmRPCAddress, method, params...) 33 | } 34 | 35 | // GetEnode call dcrm_getEnode 36 | func GetEnode() (string, error) { 37 | var result GetEnodeResp 38 | err := httpPost(&result, "dcrm_getEnode") 39 | if err != nil { 40 | return "", wrapPostError("dcrm_getEnode", err) 41 | } 42 | if result.Status != successStatus { 43 | return "", newWrongStatusError("getEnode", result.Status, result.Error) 44 | } 45 | return result.Data.Enode, nil 46 | } 47 | 48 | // GetSignNonce call dcrm_getSignNonce 49 | func GetSignNonce() (uint64, error) { 50 | var result DataResultResp 51 | err := httpPost(&result, "dcrm_getSignNonce", keyWrapper.Address.String()) 52 | if err != nil { 53 | return 0, wrapPostError("dcrm_getSignNonce", err) 54 | } 55 | if result.Status != successStatus { 56 | return 0, newWrongStatusError("getSignNonce", result.Status, result.Error) 57 | } 58 | bi, err := common.GetBigIntFromStr(result.Data.Result) 59 | if err != nil { 60 | return 0, fmt.Errorf("getSignNonce can't parse result as big int, %v", err) 61 | } 62 | return bi.Uint64(), nil 63 | } 64 | 65 | // GetSignStatus call dcrm_getSignStatus 66 | func GetSignStatus(key string) (*SignStatus, error) { 67 | var result DataResultResp 68 | err := httpPost(&result, "dcrm_getSignStatus", key) 69 | if err != nil { 70 | return nil, wrapPostError("dcrm_getSignStatus", err) 71 | } 72 | if result.Status != successStatus { 73 | return nil, newWrongStatusError("getSignStatus", result.Status, "response error "+result.Error) 74 | } 75 | data := result.Data.Result 76 | var signStatus SignStatus 77 | err = json.Unmarshal([]byte(data), &signStatus) 78 | if err != nil { 79 | return nil, wrapPostError("dcrm_getSignStatus", err) 80 | } 81 | switch signStatus.Status { 82 | case "Failure": 83 | log.Info("getSignStatus Failure", "keyID", key, "status", data) 84 | return nil, ErrGetSignStatusFailed 85 | case "Timeout": 86 | log.Info("getSignStatus Timeout", "keyID", key, "status", data) 87 | return nil, ErrGetSignStatusTimeout 88 | case successStatus: 89 | return &signStatus, nil 90 | default: 91 | return nil, newWrongStatusError("getSignStatus", signStatus.Status, "sign status error "+signStatus.Error) 92 | } 93 | } 94 | 95 | // GetCurNodeSignInfo call dcrm_getCurNodeSignInfo 96 | func GetCurNodeSignInfo() ([]*SignInfoData, error) { 97 | var result SignInfoResp 98 | err := httpPost(&result, "dcrm_getCurNodeSignInfo", keyWrapper.Address.String()) 99 | if err != nil { 100 | return nil, wrapPostError("dcrm_getCurNodeSignInfo", err) 101 | } 102 | if result.Status != successStatus { 103 | return nil, newWrongStatusError("getCurNodeSignInfo", result.Status, result.Error) 104 | } 105 | return result.Data, nil 106 | } 107 | 108 | // Sign call dcrm_sign 109 | func Sign(raw string) (string, error) { 110 | var result DataResultResp 111 | err := httpPost(&result, "dcrm_sign", raw) 112 | if err != nil { 113 | return "", wrapPostError("dcrm_sign", err) 114 | } 115 | if result.Status != successStatus { 116 | return "", newWrongStatusError("sign", result.Status, result.Error) 117 | } 118 | return result.Data.Result, nil 119 | } 120 | 121 | // AcceptSign call dcrm_acceptSign 122 | func AcceptSign(raw string) (string, error) { 123 | var result DataResultResp 124 | err := httpPost(&result, "dcrm_acceptSign", raw) 125 | if err != nil { 126 | return "", wrapPostError("dcrm_acceptSign", err) 127 | } 128 | if result.Status != successStatus { 129 | return "", newWrongStatusError("acceptSign", result.Status, result.Error) 130 | } 131 | return result.Data.Result, nil 132 | } 133 | 134 | // GetGroupByID call dcrm_getGroupByID 135 | func GetGroupByID(groupID string) (*GroupInfo, error) { 136 | var result GetGroupByIDResp 137 | err := httpPost(&result, "dcrm_getGroupByID", groupID) 138 | if err != nil { 139 | return nil, wrapPostError("dcrm_getGroupByID", err) 140 | } 141 | if result.Status != successStatus { 142 | return nil, newWrongStatusError("getGroupByID", result.Status, result.Error) 143 | } 144 | return result.Data, nil 145 | } 146 | -------------------------------------------------------------------------------- /dcrm/init.go: -------------------------------------------------------------------------------- 1 | package dcrm 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/fsn-dev/crossChain-Bridge/common" 7 | "github.com/fsn-dev/crossChain-Bridge/tools" 8 | "github.com/fsn-dev/crossChain-Bridge/tools/keystore" 9 | "github.com/fsn-dev/crossChain-Bridge/types" 10 | ) 11 | 12 | const ( 13 | // DcrmToAddress used in dcrm sign and accept 14 | DcrmToAddress = "0x00000000000000000000000000000000000000dc" 15 | // DcrmWalletServiceID to make dcrm signer 16 | DcrmWalletServiceID = 30400 17 | ) 18 | 19 | var ( 20 | dcrmSigner = types.MakeSigner("EIP155", big.NewInt(DcrmWalletServiceID)) 21 | dcrmToAddr = common.HexToAddress(DcrmToAddress) 22 | signGroups []string // sub groups for sign 23 | 24 | keyWrapper *keystore.Key 25 | dcrmUser common.Address 26 | dcrmRPCAddress string 27 | 28 | signPubkey string 29 | groupID string 30 | threshold string 31 | mode string 32 | 33 | // ServerDcrmUser dcrm initiator for sign 34 | ServerDcrmUser common.Address 35 | ) 36 | 37 | // SetDcrmRPCAddress set dcrm node rpc address 38 | func SetDcrmRPCAddress(url string) { 39 | dcrmRPCAddress = url 40 | } 41 | 42 | // SetSignPubkey set dcrm account public key 43 | func SetSignPubkey(pubkey string) { 44 | signPubkey = pubkey 45 | } 46 | 47 | // SetDcrmGroup set dcrm group 48 | func SetDcrmGroup(group, thresh, mod string) { 49 | groupID = group 50 | threshold = thresh 51 | mode = mod 52 | } 53 | 54 | // GetGroupID return dcrm group id 55 | func GetGroupID() string { 56 | return groupID 57 | } 58 | 59 | // SetSignGroups set sign subgroups 60 | func SetSignGroups(groups []string) { 61 | signGroups = groups 62 | } 63 | 64 | // GetSignGroups get sign subgroups 65 | func GetSignGroups() []string { 66 | return signGroups 67 | } 68 | 69 | // LoadKeyStore load keystore 70 | func LoadKeyStore(keyfile, passfile string) error { 71 | key, err := tools.LoadKeyStore(keyfile, passfile) 72 | if err != nil { 73 | return err 74 | } 75 | keyWrapper = key 76 | dcrmUser = keyWrapper.Address 77 | return nil 78 | } 79 | 80 | // GetDcrmUser returns the dcrm user of specified keystore 81 | func GetDcrmUser() common.Address { 82 | return dcrmUser 83 | } 84 | 85 | // IsSwapServer returns if this dcrm user is the swap server 86 | func IsSwapServer() bool { 87 | return GetDcrmUser() == ServerDcrmUser 88 | } 89 | -------------------------------------------------------------------------------- /dcrm/sign.go: -------------------------------------------------------------------------------- 1 | package dcrm 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/json" 6 | "math/big" 7 | 8 | "github.com/fsn-dev/crossChain-Bridge/common" 9 | "github.com/fsn-dev/crossChain-Bridge/log" 10 | "github.com/fsn-dev/crossChain-Bridge/tools/crypto" 11 | "github.com/fsn-dev/crossChain-Bridge/tools/rlp" 12 | "github.com/fsn-dev/crossChain-Bridge/types" 13 | ) 14 | 15 | // DoSignOne dcrm sign single msgHash with context msgContext 16 | func DoSignOne(msgHash, msgContext string) (string, error) { 17 | return DoSign([]string{msgHash}, []string{msgContext}) 18 | } 19 | 20 | // DoSign dcrm sign msgHash with context msgContext 21 | func DoSign(msgHash, msgContext []string) (string, error) { 22 | log.Debug("dcrm DoSign", "msgHash", msgHash, "msgContext", msgContext) 23 | nonce, err := GetSignNonce() 24 | if err != nil { 25 | return "", err 26 | } 27 | // randomly pick sub-group to sign 28 | randIndex, _ := rand.Int(rand.Reader, big.NewInt(int64(len(signGroups)))) 29 | signGroup := signGroups[randIndex.Int64()] 30 | txdata := SignData{ 31 | TxType: "SIGN", 32 | PubKey: signPubkey, 33 | MsgHash: msgHash, 34 | MsgContext: msgContext, 35 | Keytype: "ECDSA", 36 | GroupID: signGroup, 37 | ThresHold: threshold, 38 | Mode: mode, 39 | TimeStamp: common.NowMilliStr(), 40 | } 41 | payload, _ := json.Marshal(txdata) 42 | rawTX, err := BuildDcrmRawTx(nonce, payload) 43 | if err != nil { 44 | return "", err 45 | } 46 | return Sign(rawTX) 47 | } 48 | 49 | // BuildDcrmRawTx build dcrm raw tx 50 | func BuildDcrmRawTx(nonce uint64, payload []byte) (string, error) { 51 | tx := types.NewTransaction( 52 | nonce, // nonce 53 | dcrmToAddr, // to address 54 | big.NewInt(0), // value 55 | 100000, // gasLimit 56 | big.NewInt(80000), // gasPrice 57 | payload, // data 58 | ) 59 | signature, err := crypto.Sign(dcrmSigner.Hash(tx).Bytes(), keyWrapper.PrivateKey) 60 | if err != nil { 61 | return "", err 62 | } 63 | sigTx, err := tx.WithSignature(dcrmSigner, signature) 64 | if err != nil { 65 | return "", err 66 | } 67 | txdata, err := rlp.EncodeToBytes(sigTx) 68 | if err != nil { 69 | return "", err 70 | } 71 | rawTX := common.ToHex(txdata) 72 | return rawTX, nil 73 | } 74 | -------------------------------------------------------------------------------- /dcrm/types.go: -------------------------------------------------------------------------------- 1 | package dcrm 2 | 3 | // DataEnode enode 4 | type DataEnode struct { 5 | Enode string 6 | } 7 | 8 | // GetEnodeResp enode response 9 | type GetEnodeResp struct { 10 | Status string 11 | Tip string 12 | Error string 13 | Data *DataEnode 14 | } 15 | 16 | // DataResult result 17 | type DataResult struct { 18 | Result string `json:"result"` 19 | } 20 | 21 | // DataResultResp result response 22 | type DataResultResp struct { 23 | Status string 24 | Tip string 25 | Error string 26 | Data *DataResult 27 | } 28 | 29 | // SignReply sign reply 30 | type SignReply struct { 31 | Enode string 32 | Status string 33 | TimeStamp string 34 | Initiator string 35 | } 36 | 37 | // SignStatus sign status 38 | type SignStatus struct { 39 | Status string 40 | Rsv []string 41 | Tip string 42 | Error string 43 | AllReply []*SignReply 44 | TimeStamp string 45 | } 46 | 47 | // SignInfoData sign info 48 | type SignInfoData struct { 49 | Account string 50 | GroupID string 51 | Key string 52 | KeyType string 53 | Mode string 54 | MsgHash []string 55 | MsgContext []string 56 | Nonce string 57 | PubKey string 58 | ThresHold string 59 | TimeStamp string 60 | } 61 | 62 | // SignInfoResp sign info response 63 | type SignInfoResp struct { 64 | Status string 65 | Tip string 66 | Error string 67 | Data []*SignInfoData 68 | } 69 | 70 | // SignData sign data 71 | type SignData struct { 72 | TxType string 73 | PubKey string 74 | MsgHash []string 75 | MsgContext []string 76 | Keytype string 77 | GroupID string 78 | ThresHold string 79 | Mode string 80 | TimeStamp string 81 | } 82 | 83 | // AcceptData accpet data 84 | type AcceptData struct { 85 | TxType string 86 | Key string 87 | Accept string 88 | MsgHash []string 89 | MsgContext []string 90 | TimeStamp string 91 | } 92 | 93 | // GroupInfo group info 94 | type GroupInfo struct { 95 | GID string 96 | Count int 97 | Enodes []string 98 | } 99 | 100 | // GetGroupByIDResp group response 101 | type GetGroupByIDResp struct { 102 | Status string 103 | Tip string 104 | Error string 105 | Data *GroupInfo 106 | } 107 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/fsn-dev/crossChain-Bridge 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/BurntSushi/toml v0.3.1 7 | github.com/btcsuite/btcd v0.20.1-beta 8 | github.com/btcsuite/btcutil v1.0.2 9 | github.com/btcsuite/btcwallet/wallet/txauthor v1.0.0 10 | github.com/btcsuite/btcwallet/wallet/txrules v1.0.0 11 | github.com/btcsuite/btcwallet/wallet/txsizes v1.0.0 12 | github.com/gorilla/handlers v1.4.2 13 | github.com/gorilla/mux v1.7.4 14 | github.com/gorilla/rpc v1.2.0 15 | github.com/lestrrat-go/file-rotatelogs v2.3.0+incompatible 16 | github.com/lestrrat-go/strftime v1.0.1 // indirect 17 | github.com/pborman/uuid v1.2.0 18 | github.com/pkg/errors v0.9.1 // indirect 19 | github.com/sirupsen/logrus v1.5.0 20 | github.com/stretchr/testify v1.5.1 21 | github.com/urfave/cli/v2 v2.2.0 22 | golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 23 | gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 24 | ) 25 | -------------------------------------------------------------------------------- /gofmt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | find_files() { 4 | find . ! \( \ 5 | \( \ 6 | -path '.github' \ 7 | -o -path '.git' \ 8 | -o -path './bin' \ 9 | -o -path '*/vendor/*' \ 10 | \) -prune \ 11 | \) -name '*.go' 12 | } 13 | 14 | GOFMT="gofmt -s -w" 15 | GOIMPORTS="goimports -w" 16 | find_files | xargs $GOFMT 17 | find_files | xargs $GOIMPORTS 18 | -------------------------------------------------------------------------------- /internal/build/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | 17 | package build 18 | 19 | import ( 20 | "bytes" 21 | "flag" 22 | "fmt" 23 | "io/ioutil" 24 | "log" 25 | "os" 26 | "os/exec" 27 | "path" 28 | "path/filepath" 29 | "runtime" 30 | "strings" 31 | ) 32 | 33 | // DryRunFlag dry run flag 34 | var DryRunFlag = flag.Bool("n", false, "dry run, don't execute commands") 35 | 36 | // MustRun executes the given command and exits the host process for 37 | // any error. 38 | func MustRun(cmd *exec.Cmd) { 39 | fmt.Println(">>>", strings.Join(cmd.Args, " ")) 40 | if !*DryRunFlag { 41 | cmd.Stderr = os.Stderr 42 | cmd.Stdout = os.Stdout 43 | if err := cmd.Run(); err != nil { 44 | log.Fatal(err) 45 | } 46 | } 47 | } 48 | 49 | // MustRunCommand wrap MustRun 50 | func MustRunCommand(cmd string, args ...string) { 51 | MustRun(exec.Command(cmd, args...)) 52 | } 53 | 54 | var warnedAboutGit bool 55 | 56 | // RunGit runs a git subcommand and returns its output. 57 | // The command must complete successfully. 58 | func RunGit(args ...string) string { 59 | cmd := exec.Command("git", args...) 60 | var stdout, stderr bytes.Buffer 61 | cmd.Stdout, cmd.Stderr = &stdout, &stderr 62 | if err := cmd.Run(); err != nil { 63 | if e, ok := err.(*exec.Error); ok && e.Err == exec.ErrNotFound { 64 | if !warnedAboutGit { 65 | log.Println("Warning: can't find 'git' in PATH") 66 | warnedAboutGit = true 67 | } 68 | return "" 69 | } 70 | log.Fatal(strings.Join(cmd.Args, " "), ": ", err, "\n", stderr.String()) 71 | } 72 | return strings.TrimSpace(stdout.String()) 73 | } 74 | 75 | // readGitFile returns content of file in .git directory. 76 | func readGitFile(file string) string { 77 | content, err := ioutil.ReadFile(path.Join(".git", file)) 78 | if err != nil { 79 | return "" 80 | } 81 | return strings.TrimSpace(string(content)) 82 | } 83 | 84 | // GoTool returns the command that runs a go tool. This uses go from GOROOT instead of PATH 85 | // so that go commands executed by build use the same version of Go as the 'host' that runs 86 | // build code. e.g. 87 | // 88 | // /usr/lib/go-1.12.1/bin/go run build/ci.go ... 89 | // 90 | // runs using go 1.12.1 and invokes go 1.12.1 tools from the same GOROOT. This is also important 91 | // because runtime.Version checks on the host should match the tools that are run. 92 | func GoTool(tool string, args ...string) *exec.Cmd { 93 | args = append([]string{tool}, args...) 94 | return exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), args...) //nolint:gosec // any better way? 95 | } 96 | -------------------------------------------------------------------------------- /internal/swapapi/converts.go: -------------------------------------------------------------------------------- 1 | package swapapi 2 | 3 | import ( 4 | "github.com/fsn-dev/crossChain-Bridge/mongodb" 5 | "github.com/fsn-dev/crossChain-Bridge/tokens" 6 | ) 7 | 8 | // ConvertMgoSwapToSwapInfo convert 9 | func ConvertMgoSwapToSwapInfo(ms *mongodb.MgoSwap) *SwapInfo { 10 | return &SwapInfo{ 11 | TxID: ms.TxID, 12 | Bind: ms.Bind, 13 | Status: ms.Status, 14 | Timestamp: ms.Timestamp, 15 | Memo: ms.Memo, 16 | } 17 | } 18 | 19 | // ConvertMgoSwapsToSwapInfos convert 20 | func ConvertMgoSwapsToSwapInfos(msSlice []*mongodb.MgoSwap) []*SwapInfo { 21 | result := make([]*SwapInfo, len(msSlice)) 22 | for k, v := range msSlice { 23 | result[k] = ConvertMgoSwapToSwapInfo(v) 24 | } 25 | return result 26 | } 27 | 28 | // ConvertMgoSwapResultToSwapInfo convert 29 | func ConvertMgoSwapResultToSwapInfo(mr *mongodb.MgoSwapResult) *SwapInfo { 30 | var confirmations uint64 31 | if mr.SwapHeight != 0 { 32 | var latest uint64 33 | switch mr.SwapType { 34 | case uint32(tokens.SwapinType): 35 | latest = tokens.DstLatestBlockHeight 36 | case uint32(tokens.SwapoutType), uint32(tokens.SwapRecallType): 37 | latest = tokens.SrcLatestBlockHeight 38 | } 39 | if latest > mr.SwapHeight { 40 | confirmations = latest - mr.SwapHeight 41 | } 42 | } 43 | return &SwapInfo{ 44 | TxID: mr.TxID, 45 | TxHeight: mr.TxHeight, 46 | TxTime: mr.TxTime, 47 | From: mr.From, 48 | To: mr.To, 49 | Bind: mr.Bind, 50 | Value: mr.Value, 51 | SwapTx: mr.SwapTx, 52 | SwapHeight: mr.SwapHeight, 53 | SwapTime: mr.SwapTime, 54 | SwapValue: mr.SwapValue, 55 | SwapType: mr.SwapType, 56 | Status: mr.Status, 57 | Timestamp: mr.Timestamp, 58 | Memo: mr.Memo, 59 | Confirmations: confirmations, 60 | } 61 | } 62 | 63 | // ConvertMgoSwapResultsToSwapInfos convert 64 | func ConvertMgoSwapResultsToSwapInfos(mrSlice []*mongodb.MgoSwapResult) []*SwapInfo { 65 | result := make([]*SwapInfo, len(mrSlice)) 66 | for k, v := range mrSlice { 67 | result[k] = ConvertMgoSwapResultToSwapInfo(v) 68 | } 69 | return result 70 | } 71 | -------------------------------------------------------------------------------- /internal/swapapi/types.go: -------------------------------------------------------------------------------- 1 | package swapapi 2 | 3 | import ( 4 | "github.com/fsn-dev/crossChain-Bridge/mongodb" 5 | "github.com/fsn-dev/crossChain-Bridge/tokens" 6 | ) 7 | 8 | // SwapStatus type alias 9 | type SwapStatus = mongodb.SwapStatus 10 | 11 | // Swap type alias 12 | type Swap = mongodb.MgoSwap 13 | 14 | // SwapResult type alias 15 | type SwapResult = mongodb.MgoSwapResult 16 | 17 | // SwapStatistics type alias 18 | type SwapStatistics = mongodb.SwapStatistics 19 | 20 | // LatestScanInfo type alias 21 | type LatestScanInfo = mongodb.MgoLatestScanInfo 22 | 23 | // ServerInfo server info 24 | type ServerInfo struct { 25 | Identifier string 26 | SrcToken *tokens.TokenConfig 27 | DestToken *tokens.TokenConfig 28 | Version string 29 | } 30 | 31 | // PostResult post result 32 | type PostResult string 33 | 34 | // SuccessPostResult success post result 35 | var SuccessPostResult PostResult = "Success" 36 | 37 | // SwapInfo swap info 38 | type SwapInfo struct { 39 | TxID string `json:"txid"` 40 | TxHeight uint64 `json:"txheight"` 41 | TxTime uint64 `json:"txtime"` 42 | From string `json:"from"` 43 | To string `json:"to"` 44 | Bind string `json:"bind"` 45 | Value string `json:"value"` 46 | SwapTx string `json:"swaptx"` 47 | SwapHeight uint64 `json:"swapheight"` 48 | SwapTime uint64 `json:"swaptime"` 49 | SwapValue string `json:"swapvalue"` 50 | SwapType uint32 `json:"swaptype"` 51 | Status SwapStatus `json:"status"` 52 | Timestamp int64 `json:"timestamp"` 53 | Memo string `json:"memo"` 54 | Confirmations uint64 `json:"confirmations"` 55 | } 56 | -------------------------------------------------------------------------------- /log/logger_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | var ( 12 | now = time.Now().Unix() 13 | err = fmt.Errorf("error message") 14 | ) 15 | 16 | // Fatal Fatalf Fatalln is not test 17 | func TestLogger(t *testing.T) { 18 | SetLogger(6, false, true) 19 | 20 | WithFields("timestamp", now, "err", err).Tracef("test WithFields Tracef at %v", now) 21 | WithFields("timestamp", now, "err", err).Debugf("test WithFields Debugf at %v", now) 22 | WithFields("timestamp", now, "err", err).Infof("test WithFields Infof at %v", now) 23 | WithFields("timestamp", now, "err", err).Printf("test WithFields Printf at %v", now) 24 | WithFields("timestamp", now, "err", err).Warnf("test WithFields Warnf at %v", now) 25 | WithFields("timestamp", now, "err", err).Errorf("test WithFields Errorf at %v", now) 26 | assert.Panics(t, func() { WithFields("timestamp", now, "err", err).Panicf("test WithFields Panicf at %v", now) }, "not panic") 27 | 28 | Trace("test Trace", "timestamp", now, "err", err) 29 | Tracef("test Tracef, timestamp=%v err=%v", now, err) 30 | Traceln("test Traceln", "timestamp", now, "err", err) 31 | 32 | Debug("test Debug", "timestamp", now, "err", err) 33 | Debugf("test Debugf, timestamp=%v err=%v", now, err) 34 | Debugln("test Debugln", "timestamp", now, "err", err) 35 | 36 | Info("test Info", "timestamp", now, "err", err) 37 | Infof("test Infof, timestamp=%v err=%v", now, err) 38 | Infoln("test Infoln", "timestamp", now, "err", err) 39 | 40 | Print("test Print ", "timestamp", now, " err ", err) 41 | Printf("test Printf, timestamp=%v err=%v", now, err) 42 | Println("test Println", "timestamp", now, "err", err) 43 | 44 | Warn("test Warn", "timestamp", now, "err", err) 45 | Warnf("test Warnf, timestamp=%v err=%v", now, err) 46 | Warnln("test Warnln", "timestamp", now, "err", err) 47 | 48 | Error("test Error", "timestamp", now, "err", err) 49 | Errorf("test Errorf, timestamp=%v err=%v", now, err) 50 | Errorln("test Errorln", "timestamp", now, "err", err) 51 | 52 | assert.Panics(t, func() { Panic("test Panic", "timestamp", now, "err", err) }, "not panic") 53 | assert.Panics(t, func() { Panicf("test Panicf, timestamp=%v err=%v", now, err) }, "not panic") 54 | assert.Panics(t, func() { Panicln("test Panicln", "timestamp", now, "err", err) }, "not panic") 55 | } 56 | -------------------------------------------------------------------------------- /mongodb/dbinit.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/fsn-dev/crossChain-Bridge/log" 8 | "gopkg.in/mgo.v2" 9 | ) 10 | 11 | var ( 12 | database *mgo.Database 13 | session *mgo.Session 14 | 15 | mongoURL string 16 | dbName string 17 | ) 18 | 19 | // MongoServerInit int mongodb server session 20 | func MongoServerInit(mongourl, dbname string) { 21 | initMongodb(mongourl, dbname) 22 | mongoConnect() 23 | InitCollections() 24 | go checkMongoSession() 25 | } 26 | 27 | func initMongodb(url, db string) { 28 | mongoURL = url 29 | dbName = db 30 | } 31 | 32 | func mongoReconnect() { 33 | log.Info("[mongodb] reconnect database", "dbName", dbName) 34 | mongoConnect() 35 | go checkMongoSession() 36 | } 37 | 38 | func mongoConnect() { 39 | if session != nil { // when reconnect 40 | session.Close() 41 | } 42 | log.Info("[mongodb] connect database start.", "dbName", dbName) 43 | url := fmt.Sprintf("mongodb://%v/%v", mongoURL, dbName) 44 | var err error 45 | for { 46 | session, err = mgo.Dial(url) 47 | if err == nil { 48 | break 49 | } 50 | log.Printf("[mongodb] dial error, err=%v\n", err) 51 | time.Sleep(1 * time.Second) 52 | } 53 | session.SetMode(mgo.Monotonic, true) 54 | session.SetSafe(&mgo.Safe{FSync: true}) 55 | database = session.DB(dbName) 56 | deinintCollections() 57 | log.Info("[mongodb] connect database finished.", "dbName", dbName) 58 | } 59 | 60 | // fix 'read tcp 127.0.0.1:43502->127.0.0.1:27917: i/o timeout' 61 | func checkMongoSession() { 62 | for { 63 | time.Sleep(60 * time.Second) 64 | ensureMongoConnected() 65 | } 66 | } 67 | 68 | func ensureMongoConnected() { 69 | defer func() { 70 | if r := recover(); r != nil { 71 | mongoReconnect() 72 | } 73 | }() 74 | err := session.Ping() 75 | if err != nil { 76 | log.Info("[mongodb] refresh session.", "dbName", dbName) 77 | session.Refresh() 78 | err = session.Ping() 79 | if err == nil { 80 | database = session.DB(dbName) 81 | deinintCollections() 82 | } else { 83 | mongoReconnect() 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /mongodb/errors.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | import ( 4 | rpcjson "github.com/gorilla/rpc/v2/json2" 5 | "gopkg.in/mgo.v2" 6 | ) 7 | 8 | func newError(ec rpcjson.ErrorCode, message string) error { 9 | return &rpcjson.Error{ 10 | Code: ec, 11 | Message: message, 12 | } 13 | } 14 | 15 | func mgoError(err error) error { 16 | if err != nil { 17 | if err == mgo.ErrNotFound { 18 | return ErrItemNotFound 19 | } 20 | if mgo.IsDup(err) { 21 | return ErrItemIsDup 22 | } 23 | return newError(-32001, "mgoError: "+err.Error()) 24 | } 25 | return nil 26 | } 27 | 28 | // mongodb special errors 29 | var ( 30 | ErrItemNotFound = newError(-32002, "mgoError: Item not found") 31 | ErrItemIsDup = newError(-32003, "mgoError: Item is duplicate") 32 | ErrSwapNotFound = newError(-32011, "mgoError: Swap is not found") 33 | ErrSwapinTxNotStable = newError(-32012, "mgoError: Swap in tx is not stable") 34 | ErrSwapinRecallExist = newError(-32013, "mgoError: Swap in recall is exist") 35 | ErrSwapinRecalledOrForbidden = newError(-32014, "mgoError: Swap in is already recalled or can not recall") 36 | ) 37 | -------------------------------------------------------------------------------- /mongodb/status.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | // ----------------------------------------------- 4 | // swap status change graph 5 | // 6 | // TxNotStable -> |- TxVerifyFailed -> stop 7 | // |- TxCanRecall -> TxToBeRecall -> |- TxRecallFailed -> retry 8 | // |- TxProcessed (->MatchTxNotStable) 9 | // |- TxNotSwapped -> |- TxSwapFailed -> retry 10 | // |- TxProcessed (->MatchTxNotStable) 11 | // ----------------------------------------------- 12 | // swap result status change graph 13 | // 14 | // TxWithWrongMemo -> | 15 | // MatchTxEmpty -> | MatchTxNotStable -> MatchTxStable 16 | // ----------------------------------------------- 17 | 18 | // SwapStatus swap status 19 | type SwapStatus uint16 20 | 21 | // swap status values 22 | const ( 23 | TxNotStable SwapStatus = iota // 0 24 | TxVerifyFailed // 1 25 | TxCanRecall // 2 26 | TxToBeRecall // 3 27 | TxRecallFailed // 4 28 | TxNotSwapped // 5 29 | TxSwapFailed // 6 30 | TxProcessed // 7 31 | MatchTxEmpty // 8 32 | MatchTxNotStable // 9 33 | MatchTxStable // 10 34 | TxWithWrongMemo // 11 35 | ) 36 | 37 | func (status SwapStatus) String() string { 38 | switch status { 39 | case TxNotStable: 40 | return "TxNotStable" 41 | case TxVerifyFailed: 42 | return "TxVerifyFailed" 43 | case TxCanRecall: 44 | return "TxCanRecall" 45 | case TxToBeRecall: 46 | return "TxToBeRecall" 47 | case TxRecallFailed: 48 | return "TxRecallFailed" 49 | case TxNotSwapped: 50 | return "TxNotSwapped" 51 | case TxSwapFailed: 52 | return "TxSwapFailed" 53 | case TxProcessed: 54 | return "TxProcessed" 55 | case MatchTxEmpty: 56 | return "MatchTxEmpty" 57 | case MatchTxNotStable: 58 | return "MatchTxNotStable" 59 | case MatchTxStable: 60 | return "MatchTxStable" 61 | case TxWithWrongMemo: 62 | return "TxWithWrongMemo" 63 | default: 64 | panic("unknown swap status") 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /mongodb/tables.go: -------------------------------------------------------------------------------- 1 | package mongodb 2 | 3 | const ( 4 | tbSwapins string = "Swapins" 5 | tbSwapouts string = "Swapouts" 6 | tbSwapinResults string = "SwapinResults" 7 | tbSwapoutResults string = "SwapoutResults" 8 | tbP2shAddresses string = "P2shAddresses" 9 | tbSwapStatistics string = "SwapStatistics" 10 | tbLatestScanInfo string = "LatestScanInfo" 11 | 12 | keyOfSwapStatistics string = "latest" 13 | keyOfSrcLatestScanInfo string = "srclatest" 14 | keyOfDstLatestScanInfo string = "dstlatest" 15 | ) 16 | 17 | // MgoSwap registered swap 18 | type MgoSwap struct { 19 | Key string `bson:"_id"` 20 | TxID string `bson:"txid"` 21 | TxType uint32 `bson:"txtype"` 22 | Bind string `bson:"bind"` 23 | Status SwapStatus `bson:"status"` 24 | Timestamp int64 `bson:"timestamp"` 25 | Memo string `bson:"memo"` 26 | } 27 | 28 | // MgoSwapResult swap result (verified swap) 29 | type MgoSwapResult struct { 30 | Key string `bson:"_id"` 31 | TxID string `bson:"txid"` 32 | TxHeight uint64 `bson:"txheight"` 33 | TxTime uint64 `bson:"txtime"` 34 | From string `bson:"from"` 35 | To string `bson:"to"` 36 | Bind string `bson:"bind"` 37 | Value string `bson:"value"` 38 | SwapTx string `bson:"swaptx"` 39 | SwapHeight uint64 `bson:"swapheight"` 40 | SwapTime uint64 `bson:"swaptime"` 41 | SwapValue string `bson:"swapvalue"` 42 | SwapType uint32 `bson:"swaptype"` 43 | Status SwapStatus `bson:"status"` 44 | Timestamp int64 `bson:"timestamp"` 45 | Memo string `bson:"memo"` 46 | } 47 | 48 | // SwapResultUpdateItems swap update items 49 | type SwapResultUpdateItems struct { 50 | SwapTx string 51 | SwapHeight uint64 52 | SwapTime uint64 53 | SwapValue string 54 | SwapType uint32 55 | Status SwapStatus 56 | Timestamp int64 57 | Memo string 58 | } 59 | 60 | // MgoP2shAddress key is the bind address 61 | type MgoP2shAddress struct { 62 | Key string `bson:"_id"` 63 | P2shAddress string `bson:"p2shaddress"` 64 | } 65 | 66 | // MgoSwapStatistics swap statistics 67 | type MgoSwapStatistics struct { 68 | Key string `bson:"_id"` 69 | StableSwapinCount int `bson:"swapincount"` 70 | TotalSwapinValue string `bson:"totalswapinvalue"` 71 | TotalSwapinFee string `bson:"totalswapinfee"` 72 | StableSwapoutCount int `bson:"swapoutcount"` 73 | TotalSwapoutValue string `bson:"totalswapoutvalue"` 74 | TotalSwapoutFee string `bson:"totalswapoutfee"` 75 | } 76 | 77 | // MgoLatestScanInfo latest scan info 78 | type MgoLatestScanInfo struct { 79 | Key string `bson:"_id"` 80 | BlockHeight uint64 `bson:"blockheight"` 81 | Timestamp int64 `bson:"timestamp"` 82 | } 83 | -------------------------------------------------------------------------------- /params/config.toml: -------------------------------------------------------------------------------- 1 | # a short string to identify the bridge 2 | Identifier = "BTC2ETH" 3 | 4 | # modgodb database connection config (server only) 5 | [MongoDB] 6 | DBURL = "localhost:27017" 7 | DBName = "databasename" 8 | UserName = "username" 9 | Password = "password" 10 | 11 | # bridge API service (server only) 12 | [APIServer] 13 | Port = 11556 14 | AllowedOrigins = [] 15 | 16 | # oracle config (oracle only) 17 | [Oracle] 18 | # post swap register RPC requests to this server 19 | ServerAPIAddress = "http://127.0.0.1:11556/rpc" 20 | 21 | # customize fees in building btc transaction (server only) 22 | [BtcExtra] 23 | MinRelayFee = 400 24 | RelayFeePerKb = 2000 25 | UtxoAggregateMinCount = 10 26 | UtxoAggregateMinValue = 100000 27 | 28 | # source token config 29 | [SrcToken] 30 | BlockChain = "Bitcoin" 31 | NetID = "TestNet3" 32 | ID = "BTC" 33 | Name = "Bitcoin Coin" 34 | Symbol = "BTC" 35 | Decimals = 8 36 | Description = "Bitcoin Coin" 37 | ContractAddress = "" 38 | DcrmAddress = "mfwPnCuht2b4Lvb5XTds4Rvzy3jZ2ZWrBL" 39 | Confirmations = 0 # suggest >= 6 for Mainnet 40 | MaximumSwap = 1000.0 41 | MinimumSwap = 0.00001 42 | SwapFeeRate = 0.001 43 | InitialHeight = 0 44 | 45 | # source blockchain gateway config 46 | [SrcGateway] 47 | APIAddress = "http://47.107.50.83:3002" 48 | 49 | # dest token config 50 | [DestToken] 51 | BlockChain = "Ethereum" 52 | NetID = "Rinkeby" 53 | ID = "mBTC" 54 | Name = "SMPC Bitcoin" 55 | Symbol = "mBTC" 56 | Decimals = 8 57 | Description = "cross chain bridge BTC with mBTC" 58 | ContractAddress = "0x61b8c4d6d28d5f7edadbea5456db3b4f7f836b64" 59 | DcrmAddress = "0xbF0A46d3700E23a98F38079cE217742c92Bb66bC" 60 | Confirmations = 0 # suggest >= 33 for Mainnet 61 | MaximumSwap = 100.0 62 | MinimumSwap = 0.00001 63 | SwapFeeRate = 0.001 64 | InitialHeight = 0 65 | 66 | # dest blockchain gateway config 67 | [DestGateway] 68 | APIAddress = "http://5.189.139.168:8018" 69 | 70 | # DCRM config 71 | [Dcrm] 72 | # server dcrm user (initiator of dcrm sign) 73 | ServerAccount = "0x00c37841378920E2BA5151a5d1E074Cf367586c4" 74 | 75 | # dcrm group ID 76 | GroupID = "74245ef03937fa75b979bdaa6a5952a93f53e021e0832fca4c2ad8952572c9b70f49e291de7e024b0f7fc54ec5875210db2ac775dba44448b3972b75af074d17" 77 | 78 | # dcrm account public key 79 | Pubkey = "045c8648793e4867af465691685000ae841dccab0b011283139d2eae454b569d5789f01632e13a75a5aad8480140e895dd671cae3639f935750bea7ae4b5a2512e" 80 | 81 | # dcrm sub groups for signing (server only) 82 | SignGroups = [ 83 | "38a93f457c793ac3ee242b2c050a403774738e6558cfaa620fe5577bb15a28f63c39adcc0778497e5009a9ee776a0778ffcad4e95827e69efa21b893b8a78793", 84 | "bb1dfe1ec046cc3a3b88408ae03976aabffe459b40e5def09e76f5d4c7a917133241da9da7fc05e3e172fab54ce3129a9a492d52a5a09494d0b9c1e608f661bf" 85 | ] 86 | 87 | # dcrm threshold (NeededOracles=2,TotalOracles=3 represent '2/3' threshold) 88 | NeededOracles = 2 89 | TotalOracles = 3 90 | 91 | # dcrm mode (0:managed 1:private) 92 | Mode = 0 93 | 94 | # dcrm user keystore and password file (suggest using absolute path) 95 | KeystoreFile = "/home/xxx/accounts/keystore1" 96 | PasswordFile = "/home/xxx/accounts/password1" 97 | 98 | # dcrm backend node (gdcrm node RPC address) 99 | RPCAddress = "http://127.0.0.1:2922" 100 | -------------------------------------------------------------------------------- /params/version.go: -------------------------------------------------------------------------------- 1 | package params 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // version parts 8 | const ( 9 | VersionMajor = 0 // Major version component of the current release 10 | VersionMinor = 2 // Minor version component of the current release 11 | VersionPatch = 11 // Patch version component of the current release 12 | VersionMeta = "alpha" // Version metadata to append to the version string 13 | ) 14 | 15 | const ( 16 | versionStable = "stable" 17 | ) 18 | 19 | // Version holds the textual version string. 20 | var Version = func() string { 21 | return fmt.Sprintf("%d.%d.%d", VersionMajor, VersionMinor, VersionPatch) 22 | }() 23 | 24 | // VersionWithMeta holds the textual version string including the metadata. 25 | var VersionWithMeta = func() string { 26 | v := Version 27 | if VersionMeta != "" { 28 | v += "-" + VersionMeta 29 | } 30 | return v 31 | }() 32 | 33 | // ArchiveVersion holds the textual version string used for Geth archives. 34 | // e.g. "1.8.11-dea1ce05" for stable releases, or 35 | // "1.8.13-unstable-21c059b6" for unstable releases 36 | func ArchiveVersion(gitCommit string) string { 37 | vsn := Version 38 | if VersionMeta != versionStable { 39 | vsn += "-" + VersionMeta 40 | } 41 | if len(gitCommit) >= 8 { 42 | vsn += "-" + gitCommit[:8] 43 | } 44 | return vsn 45 | } 46 | 47 | // VersionWithCommit add git commit and data to version. 48 | func VersionWithCommit(gitCommit, gitDate string) string { 49 | vsn := VersionWithMeta 50 | if len(gitCommit) >= 8 { 51 | vsn += "-" + gitCommit[:8] 52 | } 53 | if (VersionMeta != versionStable) && (gitDate != "") { 54 | vsn += "-" + gitDate 55 | } 56 | return vsn 57 | } 58 | -------------------------------------------------------------------------------- /rpc/client/client.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "io" 7 | "io/ioutil" 8 | "net" 9 | "net/http" 10 | "strings" 11 | "time" 12 | ) 13 | 14 | var ( 15 | httpClient *http.Client 16 | ) 17 | 18 | // InitHTTPClient init http client 19 | func InitHTTPClient() { 20 | httpClient = createHTTPClient() 21 | } 22 | 23 | const ( 24 | maxIdleConns int = 100 25 | maxIdleConnsPerHost int = 10 26 | maxConnsPerHost int = 50 27 | idleConnTimeout int = 90 28 | ) 29 | 30 | // createHTTPClient for connection re-use 31 | func createHTTPClient() *http.Client { 32 | return &http.Client{ 33 | Transport: &http.Transport{ 34 | Proxy: http.ProxyFromEnvironment, 35 | DialContext: (&net.Dialer{ 36 | Timeout: 30 * time.Second, 37 | KeepAlive: 30 * time.Second, 38 | }).DialContext, 39 | MaxConnsPerHost: maxConnsPerHost, 40 | MaxIdleConns: maxIdleConns, 41 | MaxIdleConnsPerHost: maxIdleConnsPerHost, 42 | IdleConnTimeout: time.Duration(idleConnTimeout) * time.Second, 43 | }, 44 | Timeout: defaultTimeout * time.Second, 45 | } 46 | } 47 | 48 | // HTTPGet http get 49 | func HTTPGet(url string, params, headers map[string]string, timeout int) (*http.Response, error) { 50 | req, err := http.NewRequest(http.MethodGet, url, nil) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | addParams(req, params) 56 | addHeaders(req, headers) 57 | 58 | return doRequest(req, timeout) 59 | } 60 | 61 | // HTTPPost http post 62 | func HTTPPost(url string, body interface{}, params, headers map[string]string, timeout int) (*http.Response, error) { 63 | req, err := http.NewRequest(http.MethodPost, url, nil) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | addParams(req, params) 69 | addHeaders(req, headers) 70 | if err := addPostBody(req, body); err != nil { 71 | return nil, err 72 | } 73 | 74 | return doRequest(req, timeout) 75 | } 76 | 77 | // HTTPRawPost http raw post 78 | func HTTPRawPost(url, body string, params, headers map[string]string, timeout int) (*http.Response, error) { 79 | req, err := http.NewRequest(http.MethodPost, url, nil) 80 | if err != nil { 81 | return nil, err 82 | } 83 | 84 | addParams(req, params) 85 | addHeaders(req, headers) 86 | if err := addRawPostBody(req, body); err != nil { 87 | return nil, err 88 | } 89 | 90 | return doRequest(req, timeout) 91 | } 92 | 93 | func addParams(req *http.Request, params map[string]string) { 94 | if params != nil { 95 | q := req.URL.Query() 96 | for key, val := range params { 97 | q.Add(key, val) 98 | } 99 | req.URL.RawQuery = q.Encode() 100 | } 101 | } 102 | 103 | func addHeaders(req *http.Request, headers map[string]string) { 104 | for key, val := range headers { 105 | req.Header.Add(key, val) 106 | } 107 | } 108 | 109 | func addPostBody(req *http.Request, body interface{}) error { 110 | if body != nil { 111 | jsonData, err := json.Marshal(body) 112 | if err != nil { 113 | return err 114 | } 115 | req.Header.Set("Content-type", "application/json") 116 | req.GetBody = func() (io.ReadCloser, error) { 117 | return ioutil.NopCloser(bytes.NewBuffer(jsonData)), nil 118 | } 119 | req.Body, _ = req.GetBody() 120 | } 121 | return nil 122 | } 123 | 124 | func addRawPostBody(req *http.Request, body string) (err error) { 125 | if body == "" { 126 | return nil 127 | } 128 | req.Header.Set("Content-Type", "application/x-www-form-urlencoded") 129 | req.GetBody = func() (io.ReadCloser, error) { 130 | return ioutil.NopCloser(strings.NewReader(body)), nil 131 | } 132 | req.Body, err = req.GetBody() 133 | return err 134 | } 135 | 136 | func doRequest(req *http.Request, timeoutSeconds int) (*http.Response, error) { 137 | timeout := time.Duration(timeoutSeconds) * time.Second 138 | if httpClient == nil { 139 | client := http.Client{ 140 | Timeout: timeout, 141 | } 142 | return client.Do(req) 143 | } 144 | httpClient.Timeout = timeout 145 | return httpClient.Do(req) 146 | } 147 | -------------------------------------------------------------------------------- /rpc/client/get.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | ) 9 | 10 | // RPCGet rpc get 11 | func RPCGet(result interface{}, url string) error { 12 | return RPCGetRequest(result, url, nil, nil, defaultTimeout) 13 | } 14 | 15 | // RPCGetWithTimeout rpc get with timeout 16 | func RPCGetWithTimeout(result interface{}, url string, timeout int) error { 17 | return RPCGetRequest(result, url, nil, nil, timeout) 18 | } 19 | 20 | // RPCGetRequest rpc get request 21 | func RPCGetRequest(result interface{}, url string, params, headers map[string]string, timeout int) error { 22 | resp, err := HTTPGet(url, params, headers, timeout) 23 | if err != nil { 24 | return fmt.Errorf("GET request error: %v (url: %v, params: %v)", err, url, params) 25 | } 26 | 27 | if resp.StatusCode != 200 { 28 | return fmt.Errorf("error response status: %v (url: %v)", resp.StatusCode, url) 29 | } 30 | 31 | defer resp.Body.Close() 32 | const maxReadContentLength int64 = 1024 * 1024 * 10 // 10M 33 | body, err := ioutil.ReadAll(io.LimitReader(resp.Body, maxReadContentLength)) 34 | if err != nil { 35 | return fmt.Errorf("read body error: %v", err) 36 | } 37 | 38 | err = json.Unmarshal(body, &result) 39 | if err != nil { 40 | return fmt.Errorf("unmarshal result error: %v", err) 41 | } 42 | return nil 43 | } 44 | 45 | // RPCRawGet rpc raw get 46 | func RPCRawGet(url string) (string, error) { 47 | return RPCRawGetRequest(url, nil, nil, defaultTimeout) 48 | } 49 | 50 | // RPCRawGetWithTimeout rpc raw get with timeout 51 | func RPCRawGetWithTimeout(url string, timeout int) (string, error) { 52 | return RPCRawGetRequest(url, nil, nil, timeout) 53 | } 54 | 55 | // RPCRawGetRequest rpc raw get request 56 | func RPCRawGetRequest(url string, params, headers map[string]string, timeout int) (string, error) { 57 | resp, err := HTTPGet(url, params, headers, timeout) 58 | if err != nil { 59 | return "", fmt.Errorf("GET request error: %v (url: %v, params: %v)", err, url, params) 60 | } 61 | 62 | defer resp.Body.Close() 63 | const maxReadContentLength int64 = 1024 * 1024 * 10 // 10M 64 | body, err := ioutil.ReadAll(io.LimitReader(resp.Body, maxReadContentLength)) 65 | if err != nil { 66 | return "", fmt.Errorf("read body error: %v", err) 67 | } 68 | 69 | if resp.StatusCode != 200 { 70 | return "", fmt.Errorf("wrong response status %v. message: %v", resp.StatusCode, string(body)) 71 | } 72 | return string(body), nil 73 | } 74 | -------------------------------------------------------------------------------- /rpc/client/post.go: -------------------------------------------------------------------------------- 1 | package client 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "net/http" 9 | ) 10 | 11 | const ( 12 | defaultTimeout = 60 // seconds 13 | defaultRequestID = 1 14 | ) 15 | 16 | // Request json rpc request 17 | type Request struct { 18 | Method string 19 | Params interface{} 20 | Timeout int 21 | ID int 22 | } 23 | 24 | // NewRequest new request 25 | func NewRequest(method string, params ...interface{}) *Request { 26 | return &Request{ 27 | Method: method, 28 | Params: params, 29 | Timeout: defaultTimeout, 30 | ID: defaultRequestID, 31 | } 32 | } 33 | 34 | // NewRequestWithTimeoutAndID new request with timeout and id 35 | func NewRequestWithTimeoutAndID(timeout, id int, method string, params ...interface{}) *Request { 36 | return &Request{ 37 | Method: method, 38 | Params: params, 39 | Timeout: timeout, 40 | ID: id, 41 | } 42 | } 43 | 44 | // RPCPost rpc post 45 | func RPCPost(result interface{}, url, method string, params ...interface{}) error { 46 | req := NewRequest(method, params...) 47 | return RPCPostRequest(url, req, result) 48 | } 49 | 50 | // RPCPostWithTimeoutAndID rpc post with timeout and id 51 | func RPCPostWithTimeoutAndID(result interface{}, timeout, id int, url, method string, params ...interface{}) error { 52 | req := NewRequestWithTimeoutAndID(timeout, id, method, params...) 53 | return RPCPostRequest(url, req, result) 54 | } 55 | 56 | // RequestBody request body 57 | type RequestBody struct { 58 | Version string `json:"jsonrpc"` 59 | Method string `json:"method"` 60 | Params interface{} `json:"params"` 61 | ID int `json:"id"` 62 | } 63 | 64 | type jsonError struct { 65 | Code int `json:"code"` 66 | Message string `json:"message"` 67 | Data interface{} `json:"data,omitempty"` 68 | } 69 | 70 | func (err *jsonError) Error() string { 71 | return fmt.Sprintf("json-rpc error %d, %s", err.Code, err.Message) 72 | } 73 | 74 | type jsonrpcResponse struct { 75 | Version string `json:"jsonrpc,omitempty"` 76 | ID json.RawMessage `json:"id,omitempty"` 77 | Error *jsonError `json:"error,omitempty"` 78 | Result json.RawMessage `json:"result,omitempty"` 79 | } 80 | 81 | // RPCPostRequest rpc post request 82 | func RPCPostRequest(url string, req *Request, result interface{}) error { 83 | reqBody := &RequestBody{ 84 | Version: "2.0", 85 | Method: req.Method, 86 | Params: req.Params, 87 | ID: req.ID, 88 | } 89 | resp, err := HTTPPost(url, reqBody, nil, nil, req.Timeout) 90 | if err != nil { 91 | return err 92 | } 93 | return getResultFromJSONResponse(result, resp) 94 | } 95 | 96 | func getResultFromJSONResponse(result interface{}, resp *http.Response) error { 97 | defer resp.Body.Close() 98 | const maxReadContentLength int64 = 1024 * 1024 * 10 // 10M 99 | body, err := ioutil.ReadAll(io.LimitReader(resp.Body, maxReadContentLength)) 100 | if err != nil { 101 | return fmt.Errorf("read body error: %v", err) 102 | } 103 | if resp.StatusCode != 200 { 104 | return fmt.Errorf("wrong response status %v. message: %v", resp.StatusCode, string(body)) 105 | } 106 | if len(body) == 0 { 107 | return fmt.Errorf("empty response body") 108 | } 109 | 110 | var jsonResp jsonrpcResponse 111 | err = json.Unmarshal(body, &jsonResp) 112 | if err != nil { 113 | return fmt.Errorf("unmarshal body error, body is \"%v\" err=\"%v\"", string(body), err) 114 | } 115 | if jsonResp.Error != nil { 116 | return fmt.Errorf("return error: %v", jsonResp.Error.Error()) 117 | } 118 | err = json.Unmarshal(jsonResp.Result, &result) 119 | if err != nil { 120 | return fmt.Errorf("unmarshal result error: %v", err) 121 | } 122 | return nil 123 | } 124 | 125 | // RPCRawPost rpc raw post 126 | func RPCRawPost(url, body string) (string, error) { 127 | return RPCRawPostWithTimeout(url, body, defaultTimeout) 128 | } 129 | 130 | // RPCRawPostWithTimeout rpc raw post with timeout 131 | func RPCRawPostWithTimeout(url, reqBody string, timeout int) (string, error) { 132 | resp, err := HTTPRawPost(url, reqBody, nil, nil, timeout) 133 | if err != nil { 134 | return "", err 135 | } 136 | defer resp.Body.Close() 137 | const maxReadContentLength int64 = 1024 * 1024 * 10 // 10M 138 | body, err := ioutil.ReadAll(io.LimitReader(resp.Body, maxReadContentLength)) 139 | if err != nil { 140 | return "", fmt.Errorf("read body error: %v", err) 141 | } 142 | if resp.StatusCode != 200 { 143 | return "", fmt.Errorf("wrong response status %v. message: %v", resp.StatusCode, string(body)) 144 | } 145 | return string(body), nil 146 | } 147 | -------------------------------------------------------------------------------- /rpc/rpcapi/buildtx.go: -------------------------------------------------------------------------------- 1 | package rpcapi 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/fsn-dev/crossChain-Bridge/common" 7 | "github.com/fsn-dev/crossChain-Bridge/common/hexutil" 8 | "github.com/fsn-dev/crossChain-Bridge/tokens" 9 | "github.com/fsn-dev/crossChain-Bridge/tokens/eth" 10 | "github.com/fsn-dev/crossChain-Bridge/types" 11 | ) 12 | 13 | // BuildSwapoutTxArgs build swapout tx args 14 | type BuildSwapoutTxArgs struct { 15 | From common.Address `json:"from"` 16 | Value *hexutil.Big `json:"value"` 17 | Bind string `json:"bind"` 18 | Gas *hexutil.Uint64 `json:"gas"` 19 | GasPrice *hexutil.Big `json:"gasPrice"` 20 | Nonce *hexutil.Uint64 `json:"nonce"` 21 | } 22 | 23 | // BuildSwapoutTx build swapout tx 24 | func (s *RPCAPI) BuildSwapoutTx(r *http.Request, args *BuildSwapoutTxArgs, result *types.Transaction) error { 25 | from := args.From.String() 26 | token, gateway := tokens.DstBridge.GetTokenAndGateway() 27 | contract := token.ContractAddress 28 | extraArgs := &tokens.EthExtraArgs{ 29 | Gas: (*uint64)(args.Gas), 30 | GasPrice: args.GasPrice.ToInt(), 31 | Nonce: (*uint64)(args.Nonce), 32 | } 33 | swapoutVal := args.Value.ToInt() 34 | bindAddr := args.Bind 35 | 36 | ethBridge := eth.NewCrossChainBridge(false) 37 | ethBridge.TokenConfig = token 38 | ethBridge.GatewayConfig = gateway 39 | tx, err := ethBridge.BuildSwapoutTx(from, contract, extraArgs, swapoutVal, bindAddr) 40 | if err != nil { 41 | return err 42 | } 43 | *result = *tx 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /rpc/server/server.go: -------------------------------------------------------------------------------- 1 | package server 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/gorilla/handlers" 9 | "github.com/gorilla/mux" 10 | "github.com/gorilla/rpc/v2" 11 | rpcjson "github.com/gorilla/rpc/v2/json2" 12 | 13 | "github.com/fsn-dev/crossChain-Bridge/log" 14 | "github.com/fsn-dev/crossChain-Bridge/params" 15 | "github.com/fsn-dev/crossChain-Bridge/rpc/restapi" 16 | "github.com/fsn-dev/crossChain-Bridge/rpc/rpcapi" 17 | ) 18 | 19 | // StartAPIServer start api server 20 | func StartAPIServer() { 21 | router := initRouter() 22 | 23 | apiPort := params.GetAPIPort() 24 | apiServer := params.GetConfig().APIServer 25 | allowedOrigins := apiServer.AllowedOrigins 26 | 27 | corsOptions := []handlers.CORSOption{ 28 | handlers.AllowedMethods([]string{"GET", "POST"}), 29 | } 30 | if len(allowedOrigins) != 0 { 31 | corsOptions = append(corsOptions, 32 | handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type"}), 33 | handlers.AllowedOrigins(allowedOrigins), 34 | ) 35 | } 36 | 37 | log.Info("JSON RPC service listen and serving", "port", apiPort, "allowedOrigins", allowedOrigins) 38 | svr := http.Server{ 39 | Addr: fmt.Sprintf(":%v", apiPort), 40 | ReadTimeout: 60 * time.Second, 41 | WriteTimeout: 60 * time.Second, 42 | Handler: handlers.CORS(corsOptions...)(router), 43 | } 44 | go func() { 45 | if err := svr.ListenAndServe(); err != nil { 46 | log.Error("ListenAndServe error", "err", err) 47 | } 48 | }() 49 | } 50 | 51 | func initRouter() *mux.Router { 52 | r := mux.NewRouter() 53 | 54 | rpcserver := rpc.NewServer() 55 | rpcserver.RegisterCodec(rpcjson.NewCodec(), "application/json") 56 | _ = rpcserver.RegisterService(new(rpcapi.RPCAPI), "swap") 57 | 58 | r.Handle("/rpc", rpcserver) 59 | r.HandleFunc("/serverinfo", restapi.SeverInfoHandler).Methods("GET") 60 | r.HandleFunc("/statistics", restapi.StatisticsHandler).Methods("GET") 61 | r.HandleFunc("/swapin/post/{txid}", restapi.PostSwapinHandler).Methods("POST") 62 | r.HandleFunc("/swapin/post/{txid}/{bind}", restapi.PostP2shSwapinHandler).Methods("POST") 63 | r.HandleFunc("/swapout/post/{txid}", restapi.PostSwapoutHandler).Methods("POST") 64 | r.HandleFunc("/swapin/recall/{txid}", restapi.RecallSwapinHandler).Methods("POST") 65 | r.HandleFunc("/swapin/{txid}", restapi.GetSwapinHandler).Methods("GET") 66 | r.HandleFunc("/swapout/{txid}", restapi.GetSwapoutHandler).Methods("GET") 67 | r.HandleFunc("/swapin/{txid}/raw", restapi.GetRawSwapinHandler).Methods("GET") 68 | r.HandleFunc("/swapout/{txid}/raw", restapi.GetRawSwapoutHandler).Methods("GET") 69 | r.HandleFunc("/swapin/{txid}/rawresult", restapi.GetRawSwapinResultHandler).Methods("GET") 70 | r.HandleFunc("/swapout/{txid}/rawresult", restapi.GetRawSwapoutResultHandler).Methods("GET") 71 | r.HandleFunc("/swapin/history/{address}", restapi.SwapinHistoryHandler).Methods("GET") 72 | r.HandleFunc("/swapout/history/{address}", restapi.SwapoutHistoryHandler).Methods("GET") 73 | r.HandleFunc("/p2sh/{address}", restapi.GetP2shAddressInfo).Methods("GET", "POST") 74 | r.HandleFunc("/p2sh/bind/{address}", restapi.RegisterP2shAddress).Methods("GET", "POST") 75 | 76 | methodsExcluesGet := []string{"POST", "HEAD", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"} 77 | methodsExcluesPost := []string{"GET", "HEAD", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"} 78 | methodsExcluesGetAndPost := []string{"HEAD", "PUT", "DELETE", "CONNECT", "OPTIONS", "TRACE", "PATCH"} 79 | 80 | r.HandleFunc("/serverinfo", warnHandler).Methods(methodsExcluesGet...) 81 | r.HandleFunc("/statistics", warnHandler).Methods(methodsExcluesGet...) 82 | r.HandleFunc("/swapin/post/{txid}", warnHandler).Methods(methodsExcluesPost...) 83 | r.HandleFunc("/swapin/post/{txid}/{bind}", warnHandler).Methods(methodsExcluesPost...) 84 | r.HandleFunc("/swapout/post/{txid}", warnHandler).Methods(methodsExcluesPost...) 85 | r.HandleFunc("/swapin/recall/{txid}", warnHandler).Methods(methodsExcluesPost...) 86 | r.HandleFunc("/swapin/{txid}", warnHandler).Methods(methodsExcluesGet...) 87 | r.HandleFunc("/swapout/{txid}", warnHandler).Methods(methodsExcluesGet...) 88 | r.HandleFunc("/swapin/{txid}/raw", warnHandler).Methods(methodsExcluesGet...) 89 | r.HandleFunc("/swapout/{txid}/raw", warnHandler).Methods(methodsExcluesGet...) 90 | r.HandleFunc("/swapin/{txid}/rawresult", warnHandler).Methods(methodsExcluesGet...) 91 | r.HandleFunc("/swapout/{txid}/rawresult", warnHandler).Methods(methodsExcluesGet...) 92 | r.HandleFunc("/swapin/history/{address}", warnHandler).Methods(methodsExcluesGet...) 93 | r.HandleFunc("/swapout/history/{address}", warnHandler).Methods(methodsExcluesGet...) 94 | r.HandleFunc("/p2sh/{address}", warnHandler).Methods(methodsExcluesGetAndPost...) 95 | r.HandleFunc("/p2sh/bind/{address}", warnHandler).Methods(methodsExcluesGetAndPost...) 96 | 97 | return r 98 | } 99 | 100 | func warnHandler(w http.ResponseWriter, r *http.Request) { 101 | fmt.Fprintf(w, "Forbid '%v' on '%v'\n", r.Method, r.RequestURI) 102 | } 103 | -------------------------------------------------------------------------------- /tokens/btc/address.go: -------------------------------------------------------------------------------- 1 | package btc 2 | 3 | import ( 4 | "strings" 5 | 6 | "github.com/btcsuite/btcd/chaincfg" 7 | "github.com/btcsuite/btcutil" 8 | ) 9 | 10 | // IsValidAddress check address 11 | func (b *Bridge) IsValidAddress(addr string) bool { 12 | chainConfig := b.GetChainConfig() 13 | address, err := btcutil.DecodeAddress(addr, chainConfig) 14 | if err != nil { 15 | return false 16 | } 17 | return address.IsForNet(chainConfig) 18 | } 19 | 20 | // IsP2pkhAddress check p2pkh addrss 21 | func (b *Bridge) IsP2pkhAddress(addr string) bool { 22 | chainConfig := b.GetChainConfig() 23 | address, err := btcutil.DecodeAddress(addr, chainConfig) 24 | if err != nil { 25 | return false 26 | } 27 | if !address.IsForNet(chainConfig) { 28 | return false 29 | } 30 | _, ok := address.(*btcutil.AddressPubKeyHash) 31 | return ok 32 | } 33 | 34 | // IsP2shAddress check p2sh addrss 35 | func (b *Bridge) IsP2shAddress(addr string) bool { 36 | chainConfig := b.GetChainConfig() 37 | address, err := btcutil.DecodeAddress(addr, chainConfig) 38 | if err != nil { 39 | return false 40 | } 41 | if !address.IsForNet(chainConfig) { 42 | return false 43 | } 44 | _, ok := address.(*btcutil.AddressScriptHash) 45 | return ok 46 | } 47 | 48 | // GetChainConfig get chain config (net params) 49 | func (b *Bridge) GetChainConfig() *chaincfg.Params { 50 | token := b.TokenConfig 51 | networkID := strings.ToLower(token.NetID) 52 | switch networkID { 53 | case netMainnet: 54 | return &chaincfg.MainNetParams 55 | case netTestnet3: 56 | return &chaincfg.TestNet3Params 57 | } 58 | return &chaincfg.TestNet3Params 59 | } 60 | -------------------------------------------------------------------------------- /tokens/btc/aggregate.go: -------------------------------------------------------------------------------- 1 | package btc 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/fsn-dev/crossChain-Bridge/tokens" 7 | "github.com/fsn-dev/crossChain-Bridge/tokens/btc/electrs" 8 | ) 9 | 10 | const ( 11 | // AggregateIdentifier used in accepting 12 | AggregateIdentifier = "aggregate" 13 | ) 14 | 15 | // AggregateUtxos aggregate uxtos 16 | func (b *Bridge) AggregateUtxos(addrs []string, utxos []*electrs.ElectUtxo) (string, error) { 17 | authoredTx, err := b.BuildAggregateTransaction(addrs, utxos) 18 | if err != nil { 19 | return "", err 20 | } 21 | 22 | args := &tokens.BuildTxArgs{ 23 | Extra: &tokens.AllExtras{ 24 | BtcExtra: &tokens.BtcExtraArgs{}, 25 | }, 26 | } 27 | 28 | args.Identifier = AggregateIdentifier 29 | extra := args.Extra.BtcExtra 30 | extra.PreviousOutPoints = make([]*tokens.BtcOutPoint, len(authoredTx.Tx.TxIn)) 31 | for i, txin := range authoredTx.Tx.TxIn { 32 | point := txin.PreviousOutPoint 33 | extra.PreviousOutPoints[i] = &tokens.BtcOutPoint{ 34 | Hash: point.Hash.String(), 35 | Index: point.Index, 36 | } 37 | } 38 | 39 | signedTx, txHash, err := b.DcrmSignTransaction(authoredTx, args) 40 | if err != nil { 41 | return "", err 42 | } 43 | _, err = b.SendTransaction(signedTx) 44 | if err != nil { 45 | return "", err 46 | } 47 | return txHash, nil 48 | } 49 | 50 | // VerifyAggregateMsgHash verify aggregate msgHash 51 | func (b *Bridge) VerifyAggregateMsgHash(msgHash []string, args *tokens.BuildTxArgs) error { 52 | if args == nil || args.Extra == nil || args.Extra.BtcExtra == nil || len(args.Extra.BtcExtra.PreviousOutPoints) == 0 { 53 | return errors.New("empty btc extra") 54 | } 55 | rawTx, err := b.rebuildAggregateTransaction(args.Extra.BtcExtra.PreviousOutPoints) 56 | if err != nil { 57 | return err 58 | } 59 | return b.VerifyMsgHash(rawTx, msgHash, args.Extra) 60 | } 61 | -------------------------------------------------------------------------------- /tokens/btc/bridge.go: -------------------------------------------------------------------------------- 1 | package btc 2 | 3 | import ( 4 | "strings" 5 | "time" 6 | 7 | "github.com/fsn-dev/crossChain-Bridge/log" 8 | "github.com/fsn-dev/crossChain-Bridge/tokens" 9 | ) 10 | 11 | const ( 12 | netMainnet = "mainnet" 13 | netTestnet3 = "testnet3" 14 | netCustom = "custom" 15 | ) 16 | 17 | // BridgeInstance btc bridge instance 18 | var BridgeInstance *Bridge 19 | 20 | // Bridge btc bridge 21 | type Bridge struct { 22 | *tokens.CrossChainBridgeBase 23 | } 24 | 25 | // NewCrossChainBridge new btc bridge 26 | func NewCrossChainBridge(isSrc bool) *Bridge { 27 | if !isSrc { 28 | panic(tokens.ErrBridgeDestinationNotSupported) 29 | } 30 | BridgeInstance = &Bridge{tokens.NewCrossChainBridgeBase(isSrc)} 31 | return BridgeInstance 32 | } 33 | 34 | // SetTokenAndGateway set token and gateway config 35 | func (b *Bridge) SetTokenAndGateway(tokenCfg *tokens.TokenConfig, gatewayCfg *tokens.GatewayConfig) { 36 | b.CrossChainBridgeBase.SetTokenAndGateway(tokenCfg, gatewayCfg) 37 | 38 | networkID := strings.ToLower(tokenCfg.NetID) 39 | switch networkID { 40 | case netMainnet, netTestnet3: 41 | case netCustom: 42 | return 43 | default: 44 | log.Fatal("unsupported bitcoin network", "netID", tokenCfg.NetID) 45 | } 46 | 47 | if !b.IsP2pkhAddress(tokenCfg.DcrmAddress) { 48 | log.Fatal("invalid dcrm address (not p2pkh)", "address", tokenCfg.DcrmAddress) 49 | } 50 | 51 | if strings.EqualFold(tokenCfg.Symbol, "BTC") && *tokenCfg.Decimals != 8 { 52 | log.Fatal("invalid decimals for BTC", "configed", *tokenCfg.Decimals, "want", 8) 53 | } 54 | 55 | var latest uint64 56 | var err error 57 | for { 58 | latest, err = b.GetLatestBlockNumber() 59 | if err == nil { 60 | tokens.SetLatestBlockHeight(latest, b.IsSrc) 61 | log.Info("get latst block number succeed.", "number", latest, "BlockChain", tokenCfg.BlockChain, "NetID", tokenCfg.NetID) 62 | break 63 | } 64 | log.Error("get latst block number failed.", "BlockChain", tokenCfg.BlockChain, "NetID", tokenCfg.NetID, "err", err) 65 | log.Println("retry query gateway", gatewayCfg.APIAddress) 66 | time.Sleep(3 * time.Second) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tokens/btc/buildaggregatetx.go: -------------------------------------------------------------------------------- 1 | package btc 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/btcsuite/btcd/chaincfg/chainhash" 8 | "github.com/btcsuite/btcd/wire" 9 | "github.com/btcsuite/btcutil" 10 | "github.com/btcsuite/btcwallet/wallet/txauthor" 11 | "github.com/fsn-dev/crossChain-Bridge/log" 12 | "github.com/fsn-dev/crossChain-Bridge/tokens" 13 | "github.com/fsn-dev/crossChain-Bridge/tokens/btc/electrs" 14 | "github.com/fsn-dev/crossChain-Bridge/tokens/tools" 15 | ) 16 | 17 | // BuildAggregateTransaction build aggregate tx (spend p2sh utxo) 18 | func (b *Bridge) BuildAggregateTransaction(addrs []string, utxos []*electrs.ElectUtxo) (rawTx *txauthor.AuthoredTx, err error) { 19 | if len(addrs) != len(utxos) { 20 | return nil, fmt.Errorf("call BuildAggregateTransaction: count of addrs (%v) is not equal to count of utxos (%v)", len(addrs), len(utxos)) 21 | } 22 | 23 | inputSource := func(target btcutil.Amount) (total btcutil.Amount, inputs []*wire.TxIn, inputValues []btcutil.Amount, scripts [][]byte, err error) { 24 | return b.getUtxosFromElectUtxos(target, addrs, utxos) 25 | } 26 | 27 | changeSource := func() ([]byte, error) { 28 | return b.getPayToAddrScript(b.TokenConfig.DcrmAddress) 29 | } 30 | 31 | relayFeePerKb := btcutil.Amount(tokens.BtcRelayFeePerKb) 32 | 33 | return NewUnsignedTransaction(nil, relayFeePerKb, inputSource, changeSource) 34 | } 35 | 36 | func (b *Bridge) rebuildAggregateTransaction(prevOutPoints []*tokens.BtcOutPoint) (rawTx *txauthor.AuthoredTx, err error) { 37 | addrs, utxos, err := b.getUtxosFromOutPoints(prevOutPoints) 38 | if err != nil { 39 | return nil, err 40 | } 41 | return b.BuildAggregateTransaction(addrs, utxos) 42 | } 43 | 44 | func (b *Bridge) getUtxosFromElectUtxos(target btcutil.Amount, addrs []string, utxos []*electrs.ElectUtxo) (total btcutil.Amount, inputs []*wire.TxIn, inputValues []btcutil.Amount, scripts [][]byte, err error) { 45 | var ( 46 | txHash *chainhash.Hash 47 | value btcutil.Amount 48 | pkScript []byte 49 | p2shAddr string 50 | errt error 51 | ) 52 | 53 | for i, utxo := range utxos { 54 | value = btcutil.Amount(*utxo.Value) 55 | if value == 0 { 56 | continue 57 | } 58 | 59 | address := addrs[i] 60 | if b.IsP2shAddress(address) { 61 | bindAddr := tools.GetP2shBindAddress(address) 62 | if bindAddr == "" { 63 | continue 64 | } 65 | p2shAddr, _, _ = b.GetP2shAddress(bindAddr) 66 | if p2shAddr != address { 67 | log.Warn("wrong registered p2sh address", "have", address, "bind", bindAddr, "want", p2shAddr) 68 | continue 69 | } 70 | } 71 | 72 | pkScript, errt = b.getPayToAddrScript(address) 73 | if errt != nil { 74 | continue 75 | } 76 | 77 | txHash, _ = chainhash.NewHashFromStr(*utxo.Txid) 78 | prevOutPoint := wire.NewOutPoint(txHash, *utxo.Vout) 79 | txIn := wire.NewTxIn(prevOutPoint, pkScript, nil) 80 | 81 | total += value 82 | inputs = append(inputs, txIn) 83 | inputValues = append(inputValues, value) 84 | scripts = append(scripts, pkScript) 85 | } 86 | 87 | if total < target { 88 | log.Warn("getUtxos total %v < target %v", total, target) 89 | } 90 | 91 | return total, inputs, inputValues, scripts, nil 92 | } 93 | 94 | func (b *Bridge) getUtxosFromOutPoints(prevOutPoints []*tokens.BtcOutPoint) (addrs []string, utxos []*electrs.ElectUtxo, err error) { 95 | var ( 96 | tx *electrs.ElectTx 97 | outspend *electrs.ElectOutspend 98 | ) 99 | 100 | for _, point := range prevOutPoints { 101 | for i := 0; i < retryCount; i++ { 102 | outspend, err = b.GetOutspend(point.Hash, point.Index) 103 | if err == nil { 104 | break 105 | } 106 | time.Sleep(retryInterval) 107 | } 108 | if err != nil { 109 | return nil, nil, err 110 | } 111 | if *outspend.Spent { 112 | if outspend.Status != nil && outspend.Status.BlockHeight != nil { 113 | spentHeight := *outspend.Status.BlockHeight 114 | err = fmt.Errorf("out point (%v, %v) is spent at %v", point.Hash, point.Index, spentHeight) 115 | } else { 116 | err = fmt.Errorf("out point (%v, %v) is spent at txpool", point.Hash, point.Index) 117 | } 118 | return nil, nil, err 119 | } 120 | for i := 0; i < retryCount; i++ { 121 | tx, err = b.GetTransactionByHash(point.Hash) 122 | if err == nil { 123 | break 124 | } 125 | time.Sleep(retryInterval) 126 | } 127 | if err != nil { 128 | return nil, nil, err 129 | } 130 | if point.Index >= uint32(len(tx.Vout)) { 131 | err = fmt.Errorf("out point (%v, %v) index overflow", point.Hash, point.Index) 132 | return nil, nil, err 133 | } 134 | output := tx.Vout[point.Index] 135 | if *output.Value == 0 { 136 | err = fmt.Errorf("out point (%v, %v) with zero value", point.Hash, point.Index) 137 | return nil, nil, err 138 | } 139 | 140 | addrs = append(addrs, *output.ScriptpubkeyAddress) 141 | utxos = append(utxos, &electrs.ElectUtxo{ 142 | Txid: &point.Hash, 143 | Vout: &point.Index, 144 | Value: output.Value, 145 | }) 146 | } 147 | return addrs, utxos, nil 148 | } 149 | -------------------------------------------------------------------------------- /tokens/btc/callapi.go: -------------------------------------------------------------------------------- 1 | package btc 2 | 3 | import ( 4 | "github.com/fsn-dev/crossChain-Bridge/tokens/btc/electrs" 5 | ) 6 | 7 | // GetLatestBlockNumber impl 8 | func (b *Bridge) GetLatestBlockNumber() (uint64, error) { 9 | return electrs.GetLatestBlockNumber(b) 10 | } 11 | 12 | // GetTransactionByHash impl 13 | func (b *Bridge) GetTransactionByHash(txHash string) (*electrs.ElectTx, error) { 14 | return electrs.GetTransactionByHash(b, txHash) 15 | } 16 | 17 | // GetElectTransactionStatus impl 18 | func (b *Bridge) GetElectTransactionStatus(txHash string) (*electrs.ElectTxStatus, error) { 19 | return electrs.GetElectTransactionStatus(b, txHash) 20 | } 21 | 22 | // FindUtxos impl 23 | func (b *Bridge) FindUtxos(addr string) ([]*electrs.ElectUtxo, error) { 24 | return electrs.FindUtxos(b, addr) 25 | } 26 | 27 | // GetPoolTxidList impl 28 | func (b *Bridge) GetPoolTxidList() ([]string, error) { 29 | return electrs.GetPoolTxidList(b) 30 | } 31 | 32 | // GetPoolTransactions impl 33 | func (b *Bridge) GetPoolTransactions(addr string) ([]*electrs.ElectTx, error) { 34 | return electrs.GetPoolTransactions(b, addr) 35 | } 36 | 37 | // GetTransactionHistory impl 38 | func (b *Bridge) GetTransactionHistory(addr, lastSeenTxid string) ([]*electrs.ElectTx, error) { 39 | return electrs.GetTransactionHistory(b, addr, lastSeenTxid) 40 | } 41 | 42 | // GetOutspend impl 43 | func (b *Bridge) GetOutspend(txHash string, vout uint32) (*electrs.ElectOutspend, error) { 44 | return electrs.GetOutspend(b, txHash, vout) 45 | } 46 | 47 | // PostTransaction impl 48 | func (b *Bridge) PostTransaction(txHex string) (txHash string, err error) { 49 | return electrs.PostTransaction(b, txHex) 50 | } 51 | 52 | // GetBlockHash impl 53 | func (b *Bridge) GetBlockHash(height uint64) (string, error) { 54 | return electrs.GetBlockHash(b, height) 55 | } 56 | 57 | // GetBlockTxids impl 58 | func (b *Bridge) GetBlockTxids(blockHash string) ([]string, error) { 59 | return electrs.GetBlockTxids(b, blockHash) 60 | } 61 | -------------------------------------------------------------------------------- /tokens/btc/electrs/callapi.go: -------------------------------------------------------------------------------- 1 | package electrs 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | 7 | "github.com/fsn-dev/crossChain-Bridge/rpc/client" 8 | "github.com/fsn-dev/crossChain-Bridge/tokens" 9 | ) 10 | 11 | // GetLatestBlockNumber call /blocks/tip/height 12 | func GetLatestBlockNumber(b tokens.CrossChainBridge) (uint64, error) { 13 | _, gateway := b.GetTokenAndGateway() 14 | url := gateway.APIAddress + "/blocks/tip/height" 15 | var result uint64 16 | err := client.RPCGet(&result, url) 17 | return result, err 18 | } 19 | 20 | // GetTransactionByHash call /tx/{txHash} 21 | func GetTransactionByHash(b tokens.CrossChainBridge, txHash string) (*ElectTx, error) { 22 | _, gateway := b.GetTokenAndGateway() 23 | url := gateway.APIAddress + "/tx/" + txHash 24 | var result ElectTx 25 | err := client.RPCGet(&result, url) 26 | return &result, err 27 | } 28 | 29 | // GetElectTransactionStatus call /tx/{txHash}/status 30 | func GetElectTransactionStatus(b tokens.CrossChainBridge, txHash string) (*ElectTxStatus, error) { 31 | _, gateway := b.GetTokenAndGateway() 32 | url := gateway.APIAddress + "/tx/" + txHash + "/status" 33 | var result ElectTxStatus 34 | err := client.RPCGet(&result, url) 35 | return &result, err 36 | } 37 | 38 | // FindUtxos call /address/{add}/utxo (confirmed first, then big value first) 39 | func FindUtxos(b tokens.CrossChainBridge, addr string) ([]*ElectUtxo, error) { 40 | _, gateway := b.GetTokenAndGateway() 41 | url := gateway.APIAddress + "/address/" + addr + "/utxo" 42 | var result []*ElectUtxo 43 | err := client.RPCGet(&result, url) 44 | sort.Sort(SortableElectUtxoSlice(result)) 45 | return result, err 46 | } 47 | 48 | // GetPoolTxidList call /mempool/txids 49 | func GetPoolTxidList(b tokens.CrossChainBridge) ([]string, error) { 50 | _, gateway := b.GetTokenAndGateway() 51 | url := gateway.APIAddress + "/mempool/txids" 52 | var result []string 53 | err := client.RPCGet(&result, url) 54 | return result, err 55 | } 56 | 57 | // GetPoolTransactions call /address/{addr}/txs/mempool 58 | func GetPoolTransactions(b tokens.CrossChainBridge, addr string) ([]*ElectTx, error) { 59 | _, gateway := b.GetTokenAndGateway() 60 | url := gateway.APIAddress + "/address/" + addr + "/txs/mempool" 61 | var result []*ElectTx 62 | err := client.RPCGet(&result, url) 63 | return result, err 64 | } 65 | 66 | // GetTransactionHistory call /address/{addr}/txs/chain 67 | func GetTransactionHistory(b tokens.CrossChainBridge, addr, lastSeenTxid string) ([]*ElectTx, error) { 68 | _, gateway := b.GetTokenAndGateway() 69 | url := gateway.APIAddress + "/address/" + addr + "/txs/chain" 70 | if lastSeenTxid != "" { 71 | url = url + "/" + lastSeenTxid 72 | } 73 | var result []*ElectTx 74 | err := client.RPCGet(&result, url) 75 | return result, err 76 | } 77 | 78 | // GetOutspend call /tx/{txHash}/outspend/{vout} 79 | func GetOutspend(b tokens.CrossChainBridge, txHash string, vout uint32) (*ElectOutspend, error) { 80 | _, gateway := b.GetTokenAndGateway() 81 | url := gateway.APIAddress + "/tx/" + txHash + "/outspend/" + fmt.Sprintf("%d", vout) 82 | var result ElectOutspend 83 | err := client.RPCGet(&result, url) 84 | return &result, err 85 | } 86 | 87 | // PostTransaction call post to /tx 88 | func PostTransaction(b tokens.CrossChainBridge, txHex string) (txHash string, err error) { 89 | _, gateway := b.GetTokenAndGateway() 90 | url := gateway.APIAddress + "/tx" 91 | return client.RPCRawPost(url, txHex) 92 | } 93 | 94 | // GetBlockHash call /block-height/{height} 95 | func GetBlockHash(b tokens.CrossChainBridge, height uint64) (string, error) { 96 | _, gateway := b.GetTokenAndGateway() 97 | url := gateway.APIAddress + "/block-height/" + fmt.Sprintf("%d", height) 98 | return client.RPCRawGet(url) 99 | } 100 | 101 | // GetBlockTxids call /block/{blockHash}/txids 102 | func GetBlockTxids(b tokens.CrossChainBridge, blockHash string) ([]string, error) { 103 | _, gateway := b.GetTokenAndGateway() 104 | url := gateway.APIAddress + "/block/" + blockHash + "/txids" 105 | var result []string 106 | err := client.RPCGet(&result, url) 107 | return result, err 108 | } 109 | -------------------------------------------------------------------------------- /tokens/btc/electrs/types.go: -------------------------------------------------------------------------------- 1 | package electrs 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // ElectTx struct 8 | type ElectTx struct { 9 | Txid *string `json:"txid"` 10 | Version *uint32 `json:"version"` 11 | Locktime *uint32 `json:"locktime"` 12 | Size *uint32 `json:"size"` 13 | Weight *uint32 `json:"weight"` 14 | Fee *uint64 `json:"fee"` 15 | Vin []*ElectTxin `json:"vin"` 16 | Vout []*ElectTxOut `json:"vout"` 17 | Status *ElectTxStatus `json:"status,omitempty"` 18 | } 19 | 20 | // ElectTxin struct 21 | type ElectTxin struct { 22 | Txid *string `json:"txid"` 23 | Vout *uint32 `json:"vout"` 24 | Scriptsig *string `json:"scriptsig"` 25 | ScriptsigAsm *string `json:"scriptsig_asm"` 26 | IsCoinbase *bool `json:"is_coinbase"` 27 | Sequence *uint32 `json:"sequence"` 28 | InnerRedeemscriptAsm *string `json:"inner_redeemscript_asm"` 29 | Prevout *ElectTxOut `json:"prevout"` 30 | } 31 | 32 | // ElectTxOut struct 33 | type ElectTxOut struct { 34 | Scriptpubkey *string `json:"scriptpubkey"` 35 | ScriptpubkeyAsm *string `json:"scriptpubkey_asm"` 36 | ScriptpubkeyType *string `json:"scriptpubkey_type"` 37 | ScriptpubkeyAddress *string `json:"scriptpubkey_address"` 38 | Value *uint64 `json:"value"` 39 | } 40 | 41 | // ElectOutspend struct 42 | type ElectOutspend struct { 43 | Spent *bool `json:"spent"` 44 | Txid *string `json:"txid"` 45 | Vin *ElectTxin `json:"vin"` 46 | Status *ElectTxStatus `json:"status,omitempty"` 47 | } 48 | 49 | // ElectTxStatus struct 50 | type ElectTxStatus struct { 51 | Confirmed *bool `json:"confirmed"` 52 | BlockHeight *uint64 `json:"block_height"` 53 | BlockHash *string `json:"block_hash"` 54 | BlockTime *uint64 `json:"block_time"` 55 | } 56 | 57 | // ElectUtxo struct 58 | type ElectUtxo struct { 59 | Txid *string `json:"txid"` 60 | Vout *uint32 `json:"vout"` 61 | Value *uint64 `json:"value"` 62 | Status *ElectTxStatus `json:"status"` 63 | } 64 | 65 | func (utxo *ElectUtxo) String() string { 66 | return fmt.Sprintf("txid %v vout %v value %v confirmed %v", *utxo.Txid, *utxo.Vout, *utxo.Value, *utxo.Status.Confirmed) 67 | } 68 | 69 | // SortableElectUtxoSlice sortable 70 | type SortableElectUtxoSlice []*ElectUtxo 71 | 72 | // Len impl Sortable 73 | func (s SortableElectUtxoSlice) Len() int { 74 | return len(s) 75 | } 76 | 77 | // Swap impl Sortable 78 | func (s SortableElectUtxoSlice) Swap(i, j int) { 79 | s[i], s[j] = s[j], s[i] 80 | } 81 | 82 | // Less impl Sortable 83 | // sort utxos 84 | // 1. confirmed fisrt 85 | // 2. value first 86 | func (s SortableElectUtxoSlice) Less(i, j int) bool { 87 | confirmed1 := *s[i].Status.Confirmed 88 | confirmed2 := *s[j].Status.Confirmed 89 | if confirmed1 != confirmed2 { 90 | return confirmed1 91 | } 92 | return *s[i].Value > *s[j].Value 93 | } 94 | -------------------------------------------------------------------------------- /tokens/btc/p2shaddress.go: -------------------------------------------------------------------------------- 1 | package btc 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/btcsuite/btcd/chaincfg" 7 | "github.com/btcsuite/btcd/txscript" 8 | "github.com/btcsuite/btcutil" 9 | "github.com/fsn-dev/crossChain-Bridge/common" 10 | "github.com/fsn-dev/crossChain-Bridge/tokens" 11 | "github.com/fsn-dev/crossChain-Bridge/tokens/tools" 12 | ) 13 | 14 | // GetP2shAddressWithMemo common 15 | func GetP2shAddressWithMemo(memo, pubKeyHash []byte, net *chaincfg.Params) (p2shAddress string, redeemScript []byte, err error) { 16 | redeemScript, err = txscript.NewScriptBuilder(). 17 | AddData(memo).AddOp(txscript.OP_DROP). 18 | AddOp(txscript.OP_DUP).AddOp(txscript.OP_HASH160).AddData(pubKeyHash). 19 | AddOp(txscript.OP_EQUALVERIFY).AddOp(txscript.OP_CHECKSIG). 20 | Script() 21 | if err != nil { 22 | return 23 | } 24 | var addressScriptHash *btcutil.AddressScriptHash 25 | addressScriptHash, err = btcutil.NewAddressScriptHash(redeemScript, net) 26 | if err != nil { 27 | return 28 | } 29 | p2shAddress = addressScriptHash.EncodeAddress() 30 | return 31 | } 32 | 33 | // GetP2shAddress get p2sh address from bind address 34 | func (b *Bridge) GetP2shAddress(bindAddr string) (p2shAddress string, redeemScript []byte, err error) { 35 | if !tokens.GetCrossChainBridge(!b.IsSrc).IsValidAddress(bindAddr) { 36 | return "", nil, fmt.Errorf("invalid bind address %v", bindAddr) 37 | } 38 | memo := common.FromHex(bindAddr) 39 | net := b.GetChainConfig() 40 | dcrmAddress := b.TokenConfig.DcrmAddress 41 | address, _ := btcutil.DecodeAddress(dcrmAddress, net) 42 | pubKeyHash := address.ScriptAddress() 43 | return GetP2shAddressWithMemo(memo, pubKeyHash, net) 44 | } 45 | 46 | func (b *Bridge) getRedeemScriptByOutputScrpit(preScript []byte) ([]byte, error) { 47 | pkScript, err := txscript.ParsePkScript(preScript) 48 | if err != nil { 49 | return nil, err 50 | } 51 | p2shAddress, err := pkScript.Address(b.GetChainConfig()) 52 | if err != nil { 53 | return nil, err 54 | } 55 | p2shAddr := p2shAddress.String() 56 | bindAddr := tools.GetP2shBindAddress(p2shAddr) 57 | if bindAddr == "" { 58 | return nil, fmt.Errorf("ps2h address %v is registered", p2shAddr) 59 | } 60 | var address string 61 | address, redeemScript, _ := b.GetP2shAddress(bindAddr) 62 | if address != p2shAddr { 63 | return nil, fmt.Errorf("ps2h address mismatch for bind address %v, have %v want %v", bindAddr, p2shAddr, address) 64 | } 65 | return redeemScript, nil 66 | } 67 | 68 | // GetP2shAddressByRedeemScript get p2sh address by redeem script 69 | func (b *Bridge) GetP2shAddressByRedeemScript(redeemScript []byte) (string, error) { 70 | net := b.GetChainConfig() 71 | addressScriptHash, err := btcutil.NewAddressScriptHash(redeemScript, net) 72 | if err != nil { 73 | return "", err 74 | } 75 | return addressScriptHash.EncodeAddress(), nil 76 | } 77 | 78 | // GetP2shSigScript get p2sh signature script 79 | func (b *Bridge) GetP2shSigScript(redeemScript []byte) ([]byte, error) { 80 | p2shAddr, err := b.GetP2shAddressByRedeemScript(redeemScript) 81 | if err != nil { 82 | return nil, err 83 | } 84 | return b.getPayToAddrScript(p2shAddr) 85 | } 86 | -------------------------------------------------------------------------------- /tokens/btc/printtx.go: -------------------------------------------------------------------------------- 1 | package btc 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | 7 | "github.com/btcsuite/btcd/txscript" 8 | "github.com/btcsuite/btcutil" 9 | "github.com/btcsuite/btcwallet/wallet/txauthor" 10 | "github.com/fsn-dev/crossChain-Bridge/common/hexutil" 11 | ) 12 | 13 | // MarshalToJSON marshal to json 14 | func MarshalToJSON(obj interface{}, pretty bool) string { 15 | var jsdata []byte 16 | if pretty { 17 | jsdata, _ = json.MarshalIndent(obj, "", " ") 18 | } else { 19 | jsdata, _ = json.Marshal(obj) 20 | } 21 | return string(jsdata) 22 | } 23 | 24 | // AuthoredTxToString AuthoredTx to string 25 | func AuthoredTxToString(authtx interface{}, pretty bool) string { 26 | authoredTx, ok := authtx.(*txauthor.AuthoredTx) 27 | if !ok { 28 | return MarshalToJSON(authtx, pretty) 29 | } 30 | 31 | var encAuthTx EncAuthoredTx 32 | 33 | encAuthTx.ChangeIndex = authoredTx.ChangeIndex 34 | encAuthTx.TotalInput = authoredTx.TotalInput 35 | 36 | tx := authoredTx.Tx 37 | if tx == nil { 38 | return MarshalToJSON(encAuthTx, pretty) 39 | } 40 | 41 | buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize())) 42 | _ = tx.Serialize(buf) 43 | txid := tx.TxHash().String() 44 | 45 | var encTx EncMsgTx 46 | 47 | encTx.Txid = txid 48 | encTx.Version = tx.Version 49 | encTx.LockTime = tx.LockTime 50 | 51 | encTx.TxOut = make([]*EncTxOut, len(tx.TxOut)) 52 | for i, txOut := range tx.TxOut { 53 | encTx.TxOut[i] = &EncTxOut{ 54 | Value: txOut.Value, 55 | } 56 | encTx.TxOut[i].PkScript, _ = txscript.DisasmString(txOut.PkScript) 57 | } 58 | 59 | encTx.TxIn = make([]*EncTxIn, len(tx.TxIn)) 60 | for i, txIn := range tx.TxIn { 61 | encTx.TxIn[i] = &EncTxIn{ 62 | PreviousOutPoint: EncOutPoint{ 63 | Hash: txIn.PreviousOutPoint.Hash.String(), 64 | Index: txIn.PreviousOutPoint.Index, 65 | }, 66 | Sequence: txIn.Sequence, 67 | Value: authoredTx.PrevInputValues[i], 68 | } 69 | encTx.TxIn[i].SignatureScript, _ = txscript.DisasmString(txIn.SignatureScript) 70 | encTx.TxIn[i].Witness = make([]hexutil.Bytes, len(txIn.Witness)) 71 | for j, witness := range txIn.Witness { 72 | encTx.TxIn[i].Witness[j] = hexutil.Bytes(witness) 73 | } 74 | } 75 | 76 | encAuthTx.Tx = &encTx 77 | return MarshalToJSON(encAuthTx, pretty) 78 | } 79 | 80 | // EncAuthoredTx stuct 81 | type EncAuthoredTx struct { 82 | Tx *EncMsgTx 83 | TotalInput btcutil.Amount 84 | ChangeIndex int 85 | } 86 | 87 | // EncMsgTx struct 88 | type EncMsgTx struct { 89 | Txid string 90 | Version int32 91 | TxIn []*EncTxIn 92 | TxOut []*EncTxOut 93 | LockTime uint32 94 | } 95 | 96 | // EncTxOut struct 97 | type EncTxOut struct { 98 | PkScript string 99 | Value int64 100 | } 101 | 102 | // EncOutPoint struct 103 | type EncOutPoint struct { 104 | Hash string 105 | Index uint32 106 | } 107 | 108 | // EncTxIn struct 109 | type EncTxIn struct { 110 | PreviousOutPoint EncOutPoint 111 | SignatureScript string 112 | Witness []hexutil.Bytes 113 | Sequence uint32 114 | Value btcutil.Amount 115 | } 116 | -------------------------------------------------------------------------------- /tokens/btc/processtx.go: -------------------------------------------------------------------------------- 1 | package btc 2 | 3 | import ( 4 | "github.com/fsn-dev/crossChain-Bridge/log" 5 | "github.com/fsn-dev/crossChain-Bridge/tokens" 6 | "github.com/fsn-dev/crossChain-Bridge/tokens/tools" 7 | ) 8 | 9 | func (b *Bridge) processTransaction(txid string) { 10 | _ = b.processSwapin(txid) 11 | _ = b.processP2shSwapin(txid) 12 | } 13 | 14 | func (b *Bridge) processSwapin(txid string) error { 15 | if tools.IsSwapinExist(txid) { 16 | return nil 17 | } 18 | swapInfo, err := b.VerifyTransaction(txid, true) 19 | if !tokens.ShouldRegisterSwapForError(err) { 20 | return err 21 | } 22 | err = tools.RegisterSwapin(txid, swapInfo.Bind) 23 | if err != nil { 24 | log.Trace("[scan] processSwapin", "txid", txid, "err", err) 25 | } 26 | return err 27 | } 28 | 29 | func (b *Bridge) processP2shSwapin(txid string) error { 30 | if tools.IsSwapinExist(txid) { 31 | return nil 32 | } 33 | swapInfo, err := b.checkP2shTransaction(txid, true) 34 | if !tokens.ShouldRegisterSwapForError(err) { 35 | return err 36 | } 37 | err = tools.RegisterP2shSwapin(txid, swapInfo.Bind) 38 | if err != nil { 39 | log.Trace("[scan] processP2shSwapin", "txid", txid, "err", err) 40 | } 41 | return err 42 | } 43 | 44 | func (b *Bridge) checkP2shTransaction(txHash string, allowUnstable bool) (*tokens.TxSwapInfo, error) { 45 | tx, err := b.GetTransactionByHash(txHash) 46 | if err != nil { 47 | log.Debug(b.TokenConfig.BlockChain+" Bridge::GetTransaction fail", "tx", txHash, "err", err) 48 | return nil, tokens.ErrTxNotFound 49 | } 50 | var bindAddress, p2shAddress string 51 | for _, output := range tx.Vout { 52 | if *output.ScriptpubkeyType == p2shType { 53 | p2shAddress = *output.ScriptpubkeyAddress 54 | bindAddress = tools.GetP2shBindAddress(p2shAddress) 55 | if bindAddress != "" { 56 | break 57 | } 58 | } 59 | } 60 | if bindAddress == "" { 61 | return nil, tokens.ErrTxWithWrongReceiver 62 | } 63 | return b.VerifyP2shTransaction(txHash, bindAddress, allowUnstable) 64 | } 65 | -------------------------------------------------------------------------------- /tokens/btc/scanchaintx.go: -------------------------------------------------------------------------------- 1 | package btc 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/fsn-dev/crossChain-Bridge/log" 7 | "github.com/fsn-dev/crossChain-Bridge/tokens/tools" 8 | ) 9 | 10 | var ( 11 | scannedBlocks = tools.NewCachedScannedBlocks(13) 12 | ) 13 | 14 | // StartChainTransactionScanJob scan job 15 | func (b *Bridge) StartChainTransactionScanJob() { 16 | log.Info("[scanchain] start scan chain tx job", "isSrc", b.IsSrc) 17 | 18 | startHeight := tools.GetLatestScanHeight(b.IsSrc) 19 | confirmations := *b.TokenConfig.Confirmations 20 | initialHeight := b.TokenConfig.InitialHeight 21 | 22 | var height uint64 23 | switch { 24 | case startHeight != 0: 25 | height = startHeight 26 | case initialHeight != 0: 27 | height = initialHeight 28 | default: 29 | latest := tools.LoopGetLatestBlockNumber(b) 30 | if latest > confirmations { 31 | height = latest - confirmations 32 | } 33 | } 34 | if height < initialHeight { 35 | height = initialHeight 36 | } 37 | _ = tools.UpdateLatestScanInfo(b.IsSrc, height) 38 | log.Info("[scanchain] start scan tx history loop", "isSrc", b.IsSrc, "start", height) 39 | 40 | for { 41 | latest := tools.LoopGetLatestBlockNumber(b) 42 | for h := height + 1; h <= latest; { 43 | blockHash, err := b.GetBlockHash(h) 44 | if err != nil { 45 | log.Error("[scanchain] get block hash failed", "isSrc", b.IsSrc, "height", h, "err", err) 46 | time.Sleep(retryIntervalInScanJob) 47 | continue 48 | } 49 | if scannedBlocks.IsBlockScanned(blockHash) { 50 | h++ 51 | continue 52 | } 53 | txids, err := b.GetBlockTxids(blockHash) 54 | if err != nil { 55 | log.Error("[scanchain] get block txids failed", "isSrc", b.IsSrc, "height", h, "blockHash", blockHash, "err", err) 56 | time.Sleep(retryIntervalInScanJob) 57 | continue 58 | } 59 | for _, txid := range txids { 60 | b.processTransaction(txid) 61 | } 62 | scannedBlocks.CacheScannedBlock(blockHash, h) 63 | log.Info("[scanchain] scanned tx history", "isSrc", b.IsSrc, "blockHash", blockHash, "height", h, "txs", len(txids)) 64 | h++ 65 | } 66 | if latest > confirmations { 67 | latestStable := latest - confirmations 68 | if height < latestStable { 69 | height = latestStable 70 | _ = tools.UpdateLatestScanInfo(b.IsSrc, height) 71 | } 72 | } 73 | time.Sleep(restIntervalInScanJob) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /tokens/btc/scanpooltx.go: -------------------------------------------------------------------------------- 1 | package btc 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/fsn-dev/crossChain-Bridge/log" 7 | "github.com/fsn-dev/crossChain-Bridge/tokens/tools" 8 | ) 9 | 10 | var ( 11 | scannedTxs = tools.NewCachedScannedTxs(100) 12 | ) 13 | 14 | // StartPoolTransactionScanJob scan job 15 | func (b *Bridge) StartPoolTransactionScanJob() { 16 | log.Info("[scanpool] start scan pool tx job", "isSrc", b.IsSrc) 17 | for { 18 | txids, err := b.GetPoolTxidList() 19 | if err != nil { 20 | log.Error("[scanpool] get pool tx list error", "isSrc", b.IsSrc, "err", err) 21 | time.Sleep(retryIntervalInScanJob) 22 | continue 23 | } 24 | log.Info("[scanpool] scan pool tx", "isSrc", b.IsSrc, "txs", len(txids)) 25 | for _, txid := range txids { 26 | if scannedTxs.IsTxScanned(txid) { 27 | continue 28 | } 29 | b.processTransaction(txid) 30 | scannedTxs.CacheScannedTx(txid) 31 | } 32 | time.Sleep(restIntervalInScanJob) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tokens/btc/scanswaphistory.go: -------------------------------------------------------------------------------- 1 | package btc 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/fsn-dev/crossChain-Bridge/log" 7 | "github.com/fsn-dev/crossChain-Bridge/tokens/tools" 8 | ) 9 | 10 | var ( 11 | maxScanLifetime = int64(3 * 24 * 3600) 12 | retryIntervalInScanJob = 3 * time.Second 13 | restIntervalInScanJob = 3 * time.Second 14 | ) 15 | 16 | // StartSwapHistoryScanJob scan job 17 | func (b *Bridge) StartSwapHistoryScanJob() { 18 | log.Info("[scanhistory] start scan swap history job", "isSrc", b.IsSrc) 19 | 20 | isProcessed := func(txid string) bool { 21 | if b.IsSrc { 22 | return tools.IsSwapinExist(txid) 23 | } 24 | return tools.IsSwapoutExist(txid) 25 | } 26 | 27 | go b.scanFirstLoop(isProcessed) 28 | 29 | b.scanTransactionHistory(isProcessed) 30 | } 31 | 32 | func (b *Bridge) scanFirstLoop(isProcessed func(string) bool) { 33 | // first loop process all tx history no matter whether processed before 34 | log.Info("[scanhistory] start first scan loop", "isSrc", b.IsSrc) 35 | var ( 36 | nowTime = time.Now().Unix() 37 | lastSeenTxid = "" 38 | initialHeight = b.TokenConfig.InitialHeight 39 | ) 40 | 41 | isTooOld := func(time *uint64) bool { 42 | return time != nil && int64(*time)+maxScanLifetime < nowTime 43 | } 44 | 45 | FIRST_LOOP: 46 | for { 47 | txHistory, err := b.GetTransactionHistory(b.TokenConfig.DcrmAddress, lastSeenTxid) 48 | if err != nil { 49 | time.Sleep(retryIntervalInScanJob) 50 | continue 51 | } 52 | if len(txHistory) == 0 { 53 | break 54 | } 55 | for _, tx := range txHistory { 56 | if tx.Status.BlockHeight != nil && *tx.Status.BlockHeight < initialHeight { 57 | break FIRST_LOOP 58 | } 59 | if isTooOld(tx.Status.BlockTime) { 60 | break FIRST_LOOP 61 | } 62 | txid := *tx.Txid 63 | if !isProcessed(txid) { 64 | _ = b.processSwapin(txid) 65 | } 66 | } 67 | lastSeenTxid = *txHistory[len(txHistory)-1].Txid 68 | } 69 | 70 | log.Info("[scanhistory] finish first scan loop", "isSrc", b.IsSrc) 71 | } 72 | 73 | func (b *Bridge) scanTransactionHistory(isProcessed func(string) bool) { 74 | log.Info("[scanhistory] start scan swap history loop", "isSrc", b.IsSrc) 75 | var ( 76 | lastSeenTxid = "" 77 | rescan = true 78 | initialHeight = b.TokenConfig.InitialHeight 79 | ) 80 | 81 | for { 82 | txHistory, err := b.GetTransactionHistory(b.TokenConfig.DcrmAddress, lastSeenTxid) 83 | if err != nil { 84 | log.Error("[scanhistory] get tx history error", "isSrc", b.IsSrc, "err", err) 85 | time.Sleep(retryIntervalInScanJob) 86 | continue 87 | } 88 | if len(txHistory) == 0 { 89 | rescan = true 90 | } else if rescan { 91 | rescan = false 92 | } 93 | log.Info("[scanhistory] scan swap history", "isSrc", b.IsSrc, "count", len(txHistory)) 94 | for _, tx := range txHistory { 95 | if tx.Status.BlockHeight != nil && *tx.Status.BlockHeight < initialHeight { 96 | rescan = true 97 | break 98 | } 99 | txid := *tx.Txid 100 | if isProcessed(txid) { 101 | rescan = true 102 | break // rescan if already processed 103 | } 104 | _ = b.processSwapin(txid) 105 | } 106 | if rescan { 107 | lastSeenTxid = "" 108 | time.Sleep(restIntervalInScanJob) 109 | } else { 110 | lastSeenTxid = *txHistory[len(txHistory)-1].Txid 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /tokens/btc/sendtx.go: -------------------------------------------------------------------------------- 1 | package btc 2 | 3 | import ( 4 | "bytes" 5 | "encoding/hex" 6 | 7 | "github.com/btcsuite/btcwallet/wallet/txauthor" 8 | "github.com/fsn-dev/crossChain-Bridge/log" 9 | "github.com/fsn-dev/crossChain-Bridge/tokens" 10 | ) 11 | 12 | // SendTransaction send signed tx 13 | func (b *Bridge) SendTransaction(signedTx interface{}) (txHash string, err error) { 14 | authoredTx, ok := signedTx.(*txauthor.AuthoredTx) 15 | if !ok { 16 | return "", tokens.ErrWrongRawTx 17 | } 18 | 19 | tx := authoredTx.Tx 20 | if tx == nil { 21 | return "", tokens.ErrWrongRawTx 22 | } 23 | 24 | buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize())) 25 | err = tx.Serialize(buf) 26 | if err != nil { 27 | return "", err 28 | } 29 | txHex := hex.EncodeToString(buf.Bytes()) 30 | log.Info("Bridge send tx", "hash", tx.TxHash()) 31 | 32 | return b.PostTransaction(txHex) 33 | } 34 | -------------------------------------------------------------------------------- /tokens/btc/verifyp2shtx.go: -------------------------------------------------------------------------------- 1 | package btc 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/fsn-dev/crossChain-Bridge/common" 7 | "github.com/fsn-dev/crossChain-Bridge/log" 8 | "github.com/fsn-dev/crossChain-Bridge/tokens" 9 | ) 10 | 11 | // VerifyP2shTransaction verify p2sh tx 12 | func (b *Bridge) VerifyP2shTransaction(txHash, bindAddress string, allowUnstable bool) (*tokens.TxSwapInfo, error) { 13 | swapInfo := &tokens.TxSwapInfo{} 14 | swapInfo.Hash = txHash // Hash 15 | if !b.IsSrc { 16 | return swapInfo, tokens.ErrBridgeDestinationNotSupported 17 | } 18 | p2shAddress, _, err := b.GetP2shAddress(bindAddress) 19 | if err != nil { 20 | return swapInfo, fmt.Errorf("verify p2sh tx, wrong bind address %v", bindAddress) 21 | } 22 | if !allowUnstable && !b.checkStable(txHash) { 23 | return swapInfo, tokens.ErrTxNotStable 24 | } 25 | tx, err := b.GetTransactionByHash(txHash) 26 | if err != nil { 27 | log.Debug(b.TokenConfig.BlockChain+" Bridge::GetTransaction fail", "tx", txHash, "err", err) 28 | return swapInfo, tokens.ErrTxNotFound 29 | } 30 | txStatus := tx.Status 31 | if txStatus.BlockHeight != nil { 32 | swapInfo.Height = *txStatus.BlockHeight // Height 33 | } 34 | if txStatus.BlockTime != nil { 35 | swapInfo.Timestamp = *txStatus.BlockTime // Timestamp 36 | } 37 | value, _, rightReceiver := b.getReceivedValue(tx.Vout, p2shAddress) 38 | if !rightReceiver { 39 | return swapInfo, tokens.ErrTxWithWrongReceiver 40 | } 41 | swapInfo.To = p2shAddress // To 42 | swapInfo.Bind = bindAddress // Bind 43 | swapInfo.Value = common.BigFromUint64(value) // Value 44 | 45 | swapInfo.From = getTxFrom(tx.Vin) // From 46 | 47 | // check sender 48 | if swapInfo.From == swapInfo.To { 49 | return swapInfo, tokens.ErrTxWithWrongSender 50 | } 51 | 52 | if !tokens.CheckSwapValue(swapInfo.Value, b.IsSrc) { 53 | return swapInfo, tokens.ErrTxWithWrongValue 54 | } 55 | 56 | if !allowUnstable { 57 | log.Debug("verify p2sh swapin pass", "from", swapInfo.From, "to", swapInfo.To, "bind", swapInfo.Bind, "value", swapInfo.Value, "txid", swapInfo.Hash, "height", swapInfo.Height, "timestamp", swapInfo.Timestamp) 58 | } 59 | return swapInfo, nil 60 | } 61 | -------------------------------------------------------------------------------- /tokens/eth/abipack.go: -------------------------------------------------------------------------------- 1 | package eth 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | 7 | "github.com/fsn-dev/crossChain-Bridge/common" 8 | ) 9 | 10 | // PackDataWithFuncHash pack data with func hash 11 | func PackDataWithFuncHash(funcHash []byte, args ...interface{}) []byte { 12 | packData := PackData(args...) 13 | 14 | bs := make([]byte, 4+len(packData)) 15 | 16 | copy(bs[:4], funcHash) 17 | copy(bs[4:], packData) 18 | 19 | return bs 20 | } 21 | 22 | // PackData pack data 23 | func PackData(args ...interface{}) []byte { 24 | lenArgs := len(args) 25 | bs := make([]byte, lenArgs*32) 26 | for i, arg := range args { 27 | switch v := arg.(type) { 28 | case common.Hash: 29 | copy(bs[i*32:], packHash(v)) 30 | case common.Address: 31 | copy(bs[i*32:], packAddress(v)) 32 | case *big.Int: 33 | copy(bs[i*32:(i+1)*32], packBigInt(v)) 34 | case string: 35 | offset := big.NewInt(int64(len(bs))) 36 | copy(bs[i*32:], packBigInt(offset)) 37 | bs = append(bs, packString(v)...) 38 | case uint64: 39 | copy(bs[i*32:], packBigInt(new(big.Int).SetUint64(v))) 40 | case int64: 41 | copy(bs[i*32:], packBigInt(big.NewInt(v))) 42 | case int: 43 | copy(bs[i*32:], packBigInt(big.NewInt(int64(v)))) 44 | default: 45 | panic(fmt.Sprintf("unsupported to pack %v (%T)", v, v)) 46 | } 47 | } 48 | return bs 49 | } 50 | 51 | func packHash(hash common.Hash) []byte { 52 | return hash.Bytes() 53 | } 54 | 55 | func packAddress(address common.Address) []byte { 56 | return address.Hash().Bytes() 57 | } 58 | 59 | func packBigInt(bi *big.Int) []byte { 60 | return common.LeftPadBytes(bi.Bytes(), 32) 61 | } 62 | 63 | func packString(str string) []byte { 64 | strLen := len(str) 65 | paddedStrLen := (strLen + 31) / 32 * 32 66 | 67 | bs := make([]byte, 32+paddedStrLen) 68 | 69 | copy(bs[:32], packBigInt(big.NewInt(int64(strLen)))) 70 | copy(bs[32:], str) 71 | 72 | return bs 73 | } 74 | -------------------------------------------------------------------------------- /tokens/eth/address.go: -------------------------------------------------------------------------------- 1 | package eth 2 | 3 | import ( 4 | "github.com/fsn-dev/crossChain-Bridge/common" 5 | ) 6 | 7 | // IsValidAddress check address 8 | func (b *Bridge) IsValidAddress(address string) bool { 9 | if !common.IsHexAddress(address) { 10 | return false 11 | } 12 | unprefixedHex, ok, hasUpperChar := common.GetUnprefixedHex(address) 13 | if hasUpperChar { 14 | // valid checksum 15 | if unprefixedHex != common.HexToAddress(address).String()[2:] { 16 | return false 17 | } 18 | } 19 | return ok 20 | } 21 | -------------------------------------------------------------------------------- /tokens/eth/bridge.go: -------------------------------------------------------------------------------- 1 | package eth 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | "strings" 7 | "time" 8 | 9 | "github.com/fsn-dev/crossChain-Bridge/log" 10 | "github.com/fsn-dev/crossChain-Bridge/tokens" 11 | "github.com/fsn-dev/crossChain-Bridge/types" 12 | ) 13 | 14 | const ( 15 | netMainnet = "mainnet" 16 | netRinkeby = "rinkeby" 17 | netCustom = "custom" 18 | ) 19 | 20 | // Bridge eth bridge 21 | type Bridge struct { 22 | *tokens.CrossChainBridgeBase 23 | Signer types.Signer 24 | } 25 | 26 | // NewCrossChainBridge new bridge 27 | func NewCrossChainBridge(isSrc bool) *Bridge { 28 | return &Bridge{CrossChainBridgeBase: tokens.NewCrossChainBridgeBase(isSrc)} 29 | } 30 | 31 | // SetTokenAndGateway set token and gateway config 32 | func (b *Bridge) SetTokenAndGateway(tokenCfg *tokens.TokenConfig, gatewayCfg *tokens.GatewayConfig) { 33 | b.CrossChainBridgeBase.SetTokenAndGateway(tokenCfg, gatewayCfg) 34 | b.VerifyChainID() 35 | b.VerifyTokenCofig() 36 | b.InitLatestBlockNumber() 37 | } 38 | 39 | // VerifyChainID verify chain id 40 | func (b *Bridge) VerifyChainID() { 41 | tokenCfg := b.TokenConfig 42 | gatewayCfg := b.GatewayConfig 43 | 44 | networkID := strings.ToLower(tokenCfg.NetID) 45 | 46 | switch networkID { 47 | case netMainnet, netRinkeby: 48 | case netCustom: 49 | return 50 | default: 51 | panic(fmt.Sprintf("unsupported ethereum network: %v", tokenCfg.NetID)) 52 | } 53 | 54 | var ( 55 | chainID *big.Int 56 | err error 57 | ) 58 | 59 | for { 60 | chainID, err = b.ChainID() 61 | if err == nil { 62 | break 63 | } 64 | log.Errorf("can not get gateway chainID. %v", err) 65 | log.Println("retry query gateway", gatewayCfg.APIAddress) 66 | time.Sleep(3 * time.Second) 67 | } 68 | 69 | panicMismatchChainID := func() { 70 | panic(fmt.Sprintf("gateway chainID %v is not %v", chainID, tokenCfg.NetID)) 71 | } 72 | 73 | switch networkID { 74 | case netMainnet: 75 | if chainID.Uint64() != 1 { 76 | panicMismatchChainID() 77 | } 78 | case netRinkeby: 79 | if chainID.Uint64() != 4 { 80 | panicMismatchChainID() 81 | } 82 | default: 83 | panic("unsupported ethereum network") 84 | } 85 | 86 | b.Signer = types.MakeSigner("EIP155", chainID) 87 | 88 | log.Info("VerifyChainID succeed", "networkID", networkID, "chainID", chainID) 89 | } 90 | 91 | // VerifyTokenCofig verify token config 92 | func (b *Bridge) VerifyTokenCofig() { 93 | tokenCfg := b.TokenConfig 94 | if !b.IsValidAddress(tokenCfg.DcrmAddress) { 95 | log.Fatal("invalid dcrm address", "address", tokenCfg.DcrmAddress) 96 | } 97 | 98 | configedDecimals := *tokenCfg.Decimals 99 | switch strings.ToUpper(tokenCfg.Symbol) { 100 | case "ETH", "FSN": 101 | if configedDecimals != 18 { 102 | log.Fatal("invalid decimals for ETH", "configed", configedDecimals, "want", 18) 103 | } 104 | log.Info(tokenCfg.Symbol+" verify decimals success", "decimals", configedDecimals) 105 | } 106 | 107 | if tokenCfg.ContractAddress != "" { 108 | if !b.IsValidAddress(tokenCfg.ContractAddress) { 109 | log.Fatal("invalid contract address", "address", tokenCfg.ContractAddress) 110 | } 111 | switch { 112 | case !b.IsSrc: 113 | if err := b.VerifyMbtcContractAddress(tokenCfg.ContractAddress); err != nil { 114 | log.Fatal("wrong contract address", "address", tokenCfg.ContractAddress, "err", err) 115 | } 116 | case tokenCfg.IsErc20(): 117 | if err := b.VerifyErc20ContractAddress(tokenCfg.ContractAddress); err != nil { 118 | log.Fatal("wrong contract address", "address", tokenCfg.ContractAddress, "err", err) 119 | } 120 | default: 121 | log.Fatal("unsupported type of contract address in source chain, please assign SrcToken.ID (eg. ERC20) in config file", "address", tokenCfg.ContractAddress) 122 | } 123 | log.Info("verify contract address pass", "address", tokenCfg.ContractAddress) 124 | } 125 | 126 | if tokenCfg.IsErc20() { 127 | for { 128 | decimals, err := b.GetErc20Decimals(tokenCfg.ContractAddress) 129 | if err == nil { 130 | if decimals != configedDecimals { 131 | log.Fatal("invalid decimals for "+tokenCfg.Symbol, "configed", configedDecimals, "want", decimals) 132 | } 133 | log.Info(tokenCfg.Symbol+" verify decimals success", "decimals", configedDecimals) 134 | break 135 | } 136 | log.Error("get erc20 decimals failed", "err", err) 137 | time.Sleep(3 * time.Second) 138 | } 139 | } 140 | } 141 | 142 | // InitLatestBlockNumber init latest block number 143 | func (b *Bridge) InitLatestBlockNumber() { 144 | var ( 145 | tokenCfg = b.TokenConfig 146 | gatewayCfg = b.GatewayConfig 147 | latest uint64 148 | err error 149 | ) 150 | 151 | for { 152 | latest, err = b.GetLatestBlockNumber() 153 | if err == nil { 154 | tokens.SetLatestBlockHeight(latest, b.IsSrc) 155 | log.Info("get latst block number succeed.", "number", latest, "BlockChain", tokenCfg.BlockChain, "NetID", tokenCfg.NetID) 156 | break 157 | } 158 | log.Error("get latst block number failed.", "BlockChain", tokenCfg.BlockChain, "NetID", tokenCfg.NetID, "err", err) 159 | log.Println("retry query gateway", gatewayCfg.APIAddress) 160 | time.Sleep(3 * time.Second) 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /tokens/eth/buildswapouttx.go: -------------------------------------------------------------------------------- 1 | package eth 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | 7 | "github.com/fsn-dev/crossChain-Bridge/log" 8 | "github.com/fsn-dev/crossChain-Bridge/tokens" 9 | "github.com/fsn-dev/crossChain-Bridge/types" 10 | ) 11 | 12 | // BuildSwapoutTx build swapout tx 13 | func (b *Bridge) BuildSwapoutTx(from, contract string, extraArgs *tokens.EthExtraArgs, swapoutVal *big.Int, bindAddr string) (*types.Transaction, error) { 14 | if swapoutVal == nil || swapoutVal.Sign() == 0 { 15 | return nil, fmt.Errorf("swapout value must be greater than zero") 16 | } 17 | balance, err := b.GetErc20Balance(contract, from) 18 | if err != nil { 19 | return nil, err 20 | } 21 | if balance.Cmp(swapoutVal) < 0 { 22 | return nil, fmt.Errorf("not enough balance, %v < %v", balance, swapoutVal) 23 | } 24 | token := b.TokenConfig 25 | if token != nil && !tokens.CheckSwapValue(swapoutVal, b.IsSrc) { 26 | decimals := *token.Decimals 27 | minValue := tokens.ToBits(*token.MinimumSwap, decimals) 28 | maxValue := tokens.ToBits(*token.MaximumSwap, decimals) 29 | return nil, fmt.Errorf("wrong swapout value, not in range [%v, %v]", minValue, maxValue) 30 | } 31 | if tokens.SrcBridge != nil && !tokens.SrcBridge.IsValidAddress(bindAddr) { 32 | return nil, fmt.Errorf("wrong swapout bind address %v", bindAddr) 33 | } 34 | input, err := BuildSwapoutTxInput(swapoutVal, bindAddr) 35 | if err != nil { 36 | return nil, err 37 | } 38 | args := &tokens.BuildTxArgs{ 39 | From: from, 40 | To: contract, 41 | Value: big.NewInt(0), 42 | Input: &input, 43 | } 44 | if extraArgs != nil { 45 | args.Extra = &tokens.AllExtras{ 46 | EthExtra: extraArgs, 47 | } 48 | } 49 | rawtx, err := b.BuildRawTransaction(args) 50 | if err != nil { 51 | return nil, err 52 | } 53 | tx, ok := rawtx.(*types.Transaction) 54 | if !ok { 55 | return nil, tokens.ErrWrongRawTx 56 | } 57 | return tx, nil 58 | } 59 | 60 | // BuildSwapoutTxInput build swapout tx input 61 | func BuildSwapoutTxInput(swapoutVal *big.Int, bindAddr string) ([]byte, error) { 62 | input := PackDataWithFuncHash(getSwapoutFuncHash(), swapoutVal, bindAddr) 63 | 64 | // verify input 65 | bindAddress, swapoutvalue, err := parseSwapoutTxInput(&input) 66 | if err != nil { 67 | log.Error("parseSwapoutTxInput error", "err", err) 68 | return nil, err 69 | } 70 | log.Info("parseSwapoutTxInput", "bindAddress", bindAddress, "swapoutvalue", swapoutvalue) 71 | 72 | return input, nil 73 | } 74 | -------------------------------------------------------------------------------- /tokens/eth/callcontract.go: -------------------------------------------------------------------------------- 1 | package eth 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/fsn-dev/crossChain-Bridge/common" 7 | "github.com/fsn-dev/crossChain-Bridge/common/hexutil" 8 | ) 9 | 10 | // GetErc20Balance get erc20 balacne of address 11 | func (b *Bridge) GetErc20Balance(contract, address string) (*big.Int, error) { 12 | data := make(hexutil.Bytes, 36) 13 | copy(data[:4], erc20CodeParts["balanceOf"]) 14 | copy(data[4:], common.HexToAddress(address).Hash().Bytes()) 15 | result, err := b.CallContract(contract, data, "pending") 16 | if err != nil { 17 | return nil, err 18 | } 19 | return common.GetBigIntFromStr(result) 20 | } 21 | 22 | // GetErc20Decimals get erc20 decimals 23 | func (b *Bridge) GetErc20Decimals(contract string) (uint8, error) { 24 | data := make(hexutil.Bytes, 4) 25 | copy(data[:4], erc20CodeParts["decimals"]) 26 | result, err := b.CallContract(contract, data, "latest") 27 | if err != nil { 28 | return 0, err 29 | } 30 | decimals, err := common.GetUint64FromStr(result) 31 | return uint8(decimals), err 32 | } 33 | -------------------------------------------------------------------------------- /tokens/eth/processtx.go: -------------------------------------------------------------------------------- 1 | package eth 2 | 3 | import ( 4 | "github.com/fsn-dev/crossChain-Bridge/tokens" 5 | "github.com/fsn-dev/crossChain-Bridge/tokens/tools" 6 | ) 7 | 8 | func (b *Bridge) processTransaction(txid string) { 9 | if b.IsSrc { 10 | _ = b.processSwapin(txid) 11 | } else { 12 | _ = b.processSwapout(txid) 13 | } 14 | } 15 | 16 | func (b *Bridge) processSwapin(txid string) error { 17 | if tools.IsSwapinExist(txid) { 18 | return nil 19 | } 20 | swapInfo, err := b.VerifyTransaction(txid, true) 21 | if !tokens.ShouldRegisterSwapForError(err) { 22 | return err 23 | } 24 | return tools.RegisterSwapin(txid, swapInfo.Bind) 25 | } 26 | 27 | func (b *Bridge) processSwapout(txid string) error { 28 | if tools.IsSwapoutExist(txid) { 29 | return nil 30 | } 31 | swapInfo, err := b.VerifyTransaction(txid, true) 32 | if !tokens.ShouldRegisterSwapForError(err) { 33 | return err 34 | } 35 | return tools.RegisterSwapout(txid, swapInfo.Bind) 36 | } 37 | -------------------------------------------------------------------------------- /tokens/eth/scanchaintx.go: -------------------------------------------------------------------------------- 1 | package eth 2 | 3 | import ( 4 | "math/big" 5 | "time" 6 | 7 | "github.com/fsn-dev/crossChain-Bridge/log" 8 | "github.com/fsn-dev/crossChain-Bridge/tokens/tools" 9 | ) 10 | 11 | var ( 12 | scannedBlocks = tools.NewCachedScannedBlocks(67) 13 | ) 14 | 15 | // StartChainTransactionScanJob scan job 16 | func (b *Bridge) StartChainTransactionScanJob() { 17 | log.Info("[scanchain] start scan chain job", "isSrc", b.IsSrc) 18 | 19 | startHeight := tools.GetLatestScanHeight(b.IsSrc) 20 | confirmations := *b.TokenConfig.Confirmations 21 | initialHeight := b.TokenConfig.InitialHeight 22 | 23 | var height uint64 24 | switch { 25 | case startHeight != 0: 26 | height = startHeight 27 | case initialHeight != 0: 28 | height = initialHeight 29 | default: 30 | latest := tools.LoopGetLatestBlockNumber(b) 31 | if latest > confirmations { 32 | height = latest - confirmations 33 | } 34 | } 35 | if height < initialHeight { 36 | height = initialHeight 37 | } 38 | _ = tools.UpdateLatestScanInfo(b.IsSrc, height) 39 | log.Info("[scanchain] start scan chain loop", "isSrc", b.IsSrc, "start", height) 40 | 41 | for { 42 | latest := tools.LoopGetLatestBlockNumber(b) 43 | for h := height + 1; h <= latest; { 44 | block, err := b.GetBlockByNumber(new(big.Int).SetUint64(h)) 45 | if err != nil { 46 | log.Error("[scanchain] get block failed", "isSrc", b.IsSrc, "height", h, "err", err) 47 | time.Sleep(retryIntervalInScanJob) 48 | continue 49 | } 50 | blockHash := block.Hash.String() 51 | if scannedBlocks.IsBlockScanned(blockHash) { 52 | h++ 53 | continue 54 | } 55 | for _, tx := range block.Transactions { 56 | b.processTransaction(tx.String()) 57 | } 58 | scannedBlocks.CacheScannedBlock(blockHash, h) 59 | log.Info("[scanchain] scanned chain", "isSrc", b.IsSrc, "blockHash", blockHash, "height", h, "txs", len(block.Transactions)) 60 | h++ 61 | } 62 | if latest > confirmations { 63 | latestStable := latest - confirmations 64 | if height < latestStable { 65 | height = latestStable 66 | _ = tools.UpdateLatestScanInfo(b.IsSrc, height) 67 | } 68 | } 69 | time.Sleep(restIntervalInScanJob) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /tokens/eth/scanpooltx.go: -------------------------------------------------------------------------------- 1 | package eth 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/fsn-dev/crossChain-Bridge/log" 7 | "github.com/fsn-dev/crossChain-Bridge/tokens/tools" 8 | ) 9 | 10 | var ( 11 | scannedTxs = tools.NewCachedScannedTxs(300) 12 | ) 13 | 14 | // StartPoolTransactionScanJob scan job 15 | func (b *Bridge) StartPoolTransactionScanJob() { 16 | log.Info("[scanpool] start scan tx pool loop", "isSrc", b.IsSrc) 17 | for { 18 | txs, err := b.GetPendingTransactions() 19 | if err != nil { 20 | log.Error("[scanpool] get pool txs error", "isSrc", b.IsSrc, "err", err) 21 | time.Sleep(retryIntervalInScanJob) 22 | continue 23 | } 24 | log.Info("[scanpool] scan pool tx", "isSrc", b.IsSrc, "txs", len(txs)) 25 | for _, tx := range txs { 26 | txid := tx.Hash.String() 27 | if scannedTxs.IsTxScanned(txid) { 28 | continue 29 | } 30 | b.processTransaction(txid) 31 | scannedTxs.CacheScannedTx(txid) 32 | } 33 | time.Sleep(restIntervalInScanJob) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tokens/eth/scanswaphistory.go: -------------------------------------------------------------------------------- 1 | package eth 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/fsn-dev/crossChain-Bridge/common" 7 | "github.com/fsn-dev/crossChain-Bridge/log" 8 | "github.com/fsn-dev/crossChain-Bridge/tokens/tools" 9 | "github.com/fsn-dev/crossChain-Bridge/types" 10 | ) 11 | 12 | var ( 13 | maxScanHeight = uint64(15000) 14 | retryIntervalInScanJob = 3 * time.Second 15 | restIntervalInScanJob = 3 * time.Second 16 | ) 17 | 18 | // StartSwapHistoryScanJob scan job 19 | func (b *Bridge) StartSwapHistoryScanJob() { 20 | if b.TokenConfig.ContractAddress == "" { 21 | return 22 | } 23 | log.Info("[swaphistory] start scan swap history job", "isSrc", b.IsSrc) 24 | 25 | isProcessed := func(txid string) bool { 26 | if b.IsSrc { 27 | return tools.IsSwapinExist(txid) 28 | } 29 | return tools.IsSwapoutExist(txid) 30 | } 31 | 32 | go b.scanFirstLoop(isProcessed) 33 | 34 | b.scanTransactionHistory(isProcessed) 35 | } 36 | 37 | func (b *Bridge) getSwapLogs(blockHeight uint64) ([]*types.RPCLog, error) { 38 | token := b.TokenConfig 39 | contractAddress := token.ContractAddress 40 | var logTopic string 41 | if b.IsSrc { 42 | logTopic = common.ToHex(getLogSwapinTopic()) 43 | } else { 44 | logTopic = common.ToHex(getLogSwapoutTopic()) 45 | } 46 | return b.GetContractLogs(contractAddress, logTopic, blockHeight) 47 | } 48 | 49 | func (b *Bridge) scanFirstLoop(isProcessed func(string) bool) { 50 | // first loop process all tx history no matter whether processed before 51 | log.Info("[scanhistory] start first scan loop", "isSrc", b.IsSrc) 52 | initialHeight := b.TokenConfig.InitialHeight 53 | latest := tools.LoopGetLatestBlockNumber(b) 54 | for height := latest; height+maxScanHeight > latest && height >= initialHeight; { 55 | logs, err := b.getSwapLogs(height) 56 | if err != nil { 57 | log.Error("[scanhistory] get swap logs error", "isSrc", b.IsSrc, "height", height, "err", err) 58 | time.Sleep(retryIntervalInScanJob) 59 | continue 60 | } 61 | for _, log := range logs { 62 | txid := log.TxHash.String() 63 | if !isProcessed(txid) { 64 | b.processTransaction(txid) 65 | } 66 | } 67 | if height > 0 { 68 | height-- 69 | } else { 70 | break 71 | } 72 | } 73 | 74 | log.Info("[scanhistory] finish first scan loop", "isSrc", b.IsSrc) 75 | } 76 | 77 | func (b *Bridge) scanTransactionHistory(isProcessed func(string) bool) { 78 | log.Info("[scanhistory] start scan swap history loop") 79 | var ( 80 | height uint64 81 | rescan = true 82 | initialHeight = b.TokenConfig.InitialHeight 83 | ) 84 | for { 85 | if rescan || height < initialHeight || height == 0 { 86 | height = tools.LoopGetLatestBlockNumber(b) 87 | } 88 | logs, err := b.getSwapLogs(height) 89 | if err != nil { 90 | log.Error("[swaphistory] get swap logs error", "isSrc", b.IsSrc, "height", height, "err", err) 91 | time.Sleep(retryIntervalInScanJob) 92 | continue 93 | } 94 | log.Info("[scanhistory] scan swap history", "isSrc", b.IsSrc, "height", height, "count", len(logs)) 95 | for _, log := range logs { 96 | txid := log.TxHash.String() 97 | if isProcessed(txid) { 98 | rescan = true 99 | break // rescan if already processed 100 | } 101 | b.processTransaction(txid) 102 | } 103 | if rescan { 104 | time.Sleep(restIntervalInScanJob) 105 | } else if height > 0 { 106 | height-- 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /tokens/eth/sendtx.go: -------------------------------------------------------------------------------- 1 | package eth 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "github.com/fsn-dev/crossChain-Bridge/log" 8 | "github.com/fsn-dev/crossChain-Bridge/types" 9 | ) 10 | 11 | // SendTransaction send signed tx 12 | func (b *Bridge) SendTransaction(signedTx interface{}) (txHash string, err error) { 13 | tx, ok := signedTx.(*types.Transaction) 14 | if !ok { 15 | fmt.Printf("signed tx is %+v\n", signedTx) 16 | return "", errors.New("wrong signed transaction type") 17 | } 18 | err = b.SendSignedTransaction(tx) 19 | if err != nil { 20 | log.Info("SendTransaction failed", "hash", tx.Hash().String(), "err", err) 21 | return "", err 22 | } 23 | log.Info("SendTransaction success", "hash", tx.Hash().String()) 24 | return tx.Hash().String(), nil 25 | } 26 | -------------------------------------------------------------------------------- /tokens/eth/signtx.go: -------------------------------------------------------------------------------- 1 | package eth 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "time" 8 | 9 | "github.com/fsn-dev/crossChain-Bridge/common" 10 | "github.com/fsn-dev/crossChain-Bridge/dcrm" 11 | "github.com/fsn-dev/crossChain-Bridge/log" 12 | "github.com/fsn-dev/crossChain-Bridge/tokens" 13 | "github.com/fsn-dev/crossChain-Bridge/tools/crypto" 14 | "github.com/fsn-dev/crossChain-Bridge/types" 15 | ) 16 | 17 | var ( 18 | retryCount = 15 19 | retryInterval = 10 * time.Second 20 | waitInterval = 10 * time.Second 21 | ) 22 | 23 | // DcrmSignTransaction dcrm sign raw tx 24 | func (b *Bridge) DcrmSignTransaction(rawTx interface{}, args *tokens.BuildTxArgs) (signTx interface{}, txHash string, err error) { 25 | swapinNonce-- 26 | tx, ok := rawTx.(*types.Transaction) 27 | if !ok { 28 | return nil, "", errors.New("wrong raw tx param") 29 | } 30 | signer := b.Signer 31 | msgHash := signer.Hash(tx) 32 | jsondata, _ := json.Marshal(args) 33 | msgContext := string(jsondata) 34 | keyID, err := dcrm.DoSignOne(msgHash.String(), msgContext) 35 | if err != nil { 36 | return nil, "", err 37 | } 38 | log.Info(b.TokenConfig.BlockChain+" DcrmSignTransaction start", "keyID", keyID, "msghash", msgHash.String(), "txid", args.SwapID) 39 | time.Sleep(waitInterval) 40 | 41 | var rsv string 42 | i := 0 43 | for ; i < retryCount; i++ { 44 | signStatus, err2 := dcrm.GetSignStatus(keyID) 45 | if err2 == nil { 46 | if len(signStatus.Rsv) != 1 { 47 | return nil, "", fmt.Errorf("get sign status require one rsv but have %v (keyID = %v)", len(signStatus.Rsv), keyID) 48 | } 49 | 50 | rsv = signStatus.Rsv[0] 51 | break 52 | } 53 | switch err2 { 54 | case dcrm.ErrGetSignStatusFailed, dcrm.ErrGetSignStatusTimeout: 55 | return nil, "", err2 56 | } 57 | log.Warn("retry get sign status as error", "err", err2, "txid", args.SwapID) 58 | time.Sleep(retryInterval) 59 | } 60 | if i == retryCount || rsv == "" { 61 | return nil, "", errors.New("get sign status failed") 62 | } 63 | 64 | log.Trace(b.TokenConfig.BlockChain+" DcrmSignTransaction get rsv success", "keyID", keyID, "rsv", rsv) 65 | 66 | signature := common.FromHex(rsv) 67 | 68 | if len(signature) != crypto.SignatureLength { 69 | log.Error("DcrmSignTransaction wrong length of signature") 70 | return nil, "", errors.New("wrong signature of keyID " + keyID) 71 | } 72 | 73 | signedTx, err := tx.WithSignature(signer, signature) 74 | if err != nil { 75 | return nil, "", err 76 | } 77 | 78 | sender, err := types.Sender(signer, signedTx) 79 | if err != nil { 80 | return nil, "", err 81 | } 82 | 83 | token := b.TokenConfig 84 | if sender.String() != token.DcrmAddress { 85 | log.Error("DcrmSignTransaction verify sender failed", "have", sender.String(), "want", token.DcrmAddress) 86 | return nil, "", errors.New("wrong sender address") 87 | } 88 | txHash = signedTx.Hash().String() 89 | swapinNonce++ 90 | log.Info(b.TokenConfig.BlockChain+" DcrmSignTransaction success", "keyID", keyID, "txhash", txHash, "nonce", swapinNonce) 91 | return signedTx, txHash, err 92 | } 93 | -------------------------------------------------------------------------------- /tokens/eth/verifycontractaddress.go: -------------------------------------------------------------------------------- 1 | package eth 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/fsn-dev/crossChain-Bridge/common" 9 | "github.com/fsn-dev/crossChain-Bridge/log" 10 | "github.com/fsn-dev/crossChain-Bridge/tokens/btc" 11 | ) 12 | 13 | var ( 14 | // ExtCodeParts extended func hashes and log topics 15 | ExtCodeParts map[string][]byte 16 | 17 | // first 4 bytes of `Keccak256Hash([]byte("Swapin(bytes32,address,uint256)"))` 18 | swapinFuncHash = common.FromHex("0xec126c77") 19 | logSwapinTopic = common.FromHex("0x05d0634fe981be85c22e2942a880821b70095d84e152c3ea3c17a4e4250d9d61") 20 | 21 | // first 4 bytes of `Keccak256Hash([]byte("Swapout(uint256,string)"))` 22 | mBTCSwapoutFuncHash = common.FromHex("0xad54056d") 23 | mBTCLogSwapoutTopic = common.FromHex("0x9c92ad817e5474d30a4378deface765150479363a897b0590fbb12ae9d89396b") 24 | 25 | // first 4 bytes of `Keccak256Hash([]byte("Swapout(uint256)"))` 26 | mETHSwapoutFuncHash = common.FromHex("0xf1337b76") 27 | mETHLogSwapoutTopic = common.FromHex("0x9711511ecf3840282a7a2f2bd0f1dcc30c8cf0913c9575c8089a8d018a2099ff") 28 | ) 29 | 30 | var mBTCExtCodeParts = map[string][]byte{ 31 | // Extended interfaces 32 | "SwapinFuncHash": swapinFuncHash, 33 | "LogSwapinTopic": logSwapinTopic, 34 | "SwapoutFuncHash": mBTCSwapoutFuncHash, 35 | "LogSwapoutTopic": mBTCLogSwapoutTopic, 36 | } 37 | 38 | var mETHExtCodeParts = map[string][]byte{ 39 | // Extended interfaces 40 | "SwapinFuncHash": swapinFuncHash, 41 | "LogSwapinTopic": logSwapinTopic, 42 | "SwapoutFuncHash": mETHSwapoutFuncHash, 43 | "LogSwapoutTopic": mETHLogSwapoutTopic, 44 | } 45 | 46 | var erc20CodeParts = map[string][]byte{ 47 | // Erc20 interfaces 48 | "name": common.FromHex("0x06fdde03"), 49 | "symbol": common.FromHex("0x95d89b41"), 50 | "decimals": common.FromHex("0x313ce567"), 51 | "totalSupply": common.FromHex("0x18160ddd"), 52 | "balanceOf": common.FromHex("0x70a08231"), 53 | "transfer": common.FromHex("0xa9059cbb"), 54 | "transferFrom": common.FromHex("0x23b872dd"), 55 | "approve": common.FromHex("0x095ea7b3"), 56 | "allowance": common.FromHex("0xdd62ed3e"), 57 | "LogTransfer": common.FromHex("0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"), 58 | "LogApproval": common.FromHex("0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"), 59 | } 60 | 61 | // VerifyContractCode verify contract code 62 | func (b *Bridge) VerifyContractCode(contract string, codePartsSlice ...map[string][]byte) (err error) { 63 | var code []byte 64 | retryCount := 3 65 | for i := 0; i < retryCount; i++ { 66 | code, err = b.GetCode(contract) 67 | if err == nil { 68 | break 69 | } 70 | log.Warn("get contract code failed", "contract", contract, "err", err) 71 | time.Sleep(1 * time.Second) 72 | } 73 | for _, codeParts := range codePartsSlice { 74 | for key, part := range codeParts { 75 | if !bytes.Contains(code, part) { 76 | return fmt.Errorf("contract byte code miss '%v' bytes '%x'", key, part) 77 | } 78 | } 79 | } 80 | return nil 81 | } 82 | 83 | // VerifyErc20ContractAddress verify erc20 contract 84 | func (b *Bridge) VerifyErc20ContractAddress(contract string) (err error) { 85 | return b.VerifyContractCode(contract, erc20CodeParts) 86 | } 87 | 88 | // VerifyMbtcContractAddress verify mbtc contract 89 | func (b *Bridge) VerifyMbtcContractAddress(contract string) (err error) { 90 | return b.VerifyContractCode(contract, ExtCodeParts, erc20CodeParts) 91 | } 92 | 93 | // InitExtCodeParts int extended code parts 94 | func InitExtCodeParts() { 95 | switch { 96 | case isMbtcSwapout(): 97 | ExtCodeParts = mBTCExtCodeParts 98 | default: 99 | ExtCodeParts = mETHExtCodeParts 100 | } 101 | log.Info("init extented code parts", "isMBTC", isMbtcSwapout()) 102 | } 103 | 104 | func isMbtcSwapout() bool { 105 | return btc.BridgeInstance != nil 106 | } 107 | 108 | func getSwapinFuncHash() []byte { 109 | return ExtCodeParts["SwapinFuncHash"] 110 | } 111 | 112 | func getLogSwapinTopic() []byte { 113 | return ExtCodeParts["LogSwapinTopic"] 114 | } 115 | 116 | func getSwapoutFuncHash() []byte { 117 | return ExtCodeParts["SwapoutFuncHash"] 118 | } 119 | 120 | func getLogSwapoutTopic() []byte { 121 | return ExtCodeParts["LogSwapoutTopic"] 122 | } 123 | -------------------------------------------------------------------------------- /tokens/eth/verifytx.go: -------------------------------------------------------------------------------- 1 | package eth 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/fsn-dev/crossChain-Bridge/common" 8 | "github.com/fsn-dev/crossChain-Bridge/log" 9 | "github.com/fsn-dev/crossChain-Bridge/tokens" 10 | "github.com/fsn-dev/crossChain-Bridge/types" 11 | ) 12 | 13 | // GetTransaction impl 14 | func (b *Bridge) GetTransaction(txHash string) (interface{}, error) { 15 | return b.GetTransactionByHash(txHash) 16 | } 17 | 18 | // GetTransactionStatus impl 19 | func (b *Bridge) GetTransactionStatus(txHash string) *tokens.TxStatus { 20 | var txStatus tokens.TxStatus 21 | txr, err := b.GetTransactionReceipt(txHash) 22 | if err != nil { 23 | log.Debug("GetTransactionReceipt fail", "hash", txHash, "err", err) 24 | return &txStatus 25 | } 26 | if *txr.Status != 1 { 27 | log.Debug("transaction with wrong receipt status", "hash", txHash, "status", txr.Status) 28 | } 29 | txStatus.BlockHeight = txr.BlockNumber.ToInt().Uint64() 30 | txStatus.BlockHash = txr.BlockHash.String() 31 | block, err := b.GetBlockByHash(txStatus.BlockHash) 32 | if err == nil { 33 | txStatus.BlockTime = block.Time.ToInt().Uint64() 34 | } else { 35 | log.Debug("GetBlockByHash fail", "hash", txStatus.BlockHash, "err", err) 36 | } 37 | if *txr.Status == 1 { 38 | latest, err := b.GetLatestBlockNumber() 39 | if err == nil { 40 | txStatus.Confirmations = latest - txStatus.BlockHeight 41 | } else { 42 | log.Debug("GetLatestBlockNumber fail", "err", err) 43 | } 44 | } 45 | txStatus.Receipt = txr 46 | return &txStatus 47 | } 48 | 49 | // VerifyMsgHash verify msg hash 50 | func (b *Bridge) VerifyMsgHash(rawTx interface{}, msgHashes []string, extra interface{}) error { 51 | tx, ok := rawTx.(*types.Transaction) 52 | if !ok { 53 | return tokens.ErrWrongRawTx 54 | } 55 | if len(msgHashes) != 1 { 56 | return fmt.Errorf("require one msgHash but have %v", len(msgHashes)) 57 | } 58 | msgHash := msgHashes[0] 59 | signer := b.Signer 60 | sigHash := signer.Hash(tx) 61 | if sigHash.String() != msgHash { 62 | return tokens.ErrMsgHashMismatch 63 | } 64 | return nil 65 | } 66 | 67 | // VerifyTransaction impl 68 | func (b *Bridge) VerifyTransaction(txHash string, allowUnstable bool) (*tokens.TxSwapInfo, error) { 69 | if !b.IsSrc { 70 | return b.verifySwapoutTx(txHash, allowUnstable) 71 | } 72 | return b.verifySwapinTx(txHash, allowUnstable) 73 | } 74 | 75 | func (b *Bridge) verifySwapinTx(txHash string, allowUnstable bool) (*tokens.TxSwapInfo, error) { 76 | if b.TokenConfig.IsErc20() { 77 | return b.verifyErc20SwapinTx(txHash, allowUnstable) 78 | } 79 | 80 | swapInfo := &tokens.TxSwapInfo{} 81 | swapInfo.Hash = txHash // Hash 82 | token := b.TokenConfig 83 | dcrmAddress := token.DcrmAddress 84 | 85 | tx, err := b.GetTransactionByHash(txHash) 86 | if err != nil { 87 | log.Debug(b.TokenConfig.BlockChain+" Bridge::GetTransaction fail", "tx", txHash, "err", err) 88 | return swapInfo, tokens.ErrTxNotFound 89 | } 90 | if tx.BlockNumber != nil { 91 | swapInfo.Height = tx.BlockNumber.ToInt().Uint64() // Height 92 | } 93 | if tx.Recipient != nil { 94 | swapInfo.To = strings.ToLower(tx.Recipient.String()) // To 95 | } 96 | swapInfo.From = strings.ToLower(tx.From.String()) // From 97 | swapInfo.Bind = swapInfo.From // Bind 98 | swapInfo.Value = tx.Amount.ToInt() // Value 99 | 100 | if !allowUnstable { 101 | txStatus := b.GetTransactionStatus(txHash) 102 | swapInfo.Height = txStatus.BlockHeight // Height 103 | swapInfo.Timestamp = txStatus.BlockTime // Timestamp 104 | receipt, ok := txStatus.Receipt.(*types.RPCTxReceipt) 105 | if !ok || receipt == nil { 106 | return swapInfo, tokens.ErrTxNotStable 107 | } 108 | if *receipt.Status != 1 { 109 | return swapInfo, tokens.ErrTxWithWrongReceipt 110 | } 111 | if txStatus.BlockHeight == 0 || 112 | txStatus.Confirmations < *token.Confirmations { 113 | return swapInfo, tokens.ErrTxNotStable 114 | } 115 | } 116 | 117 | if !common.IsEqualIgnoreCase(swapInfo.To, dcrmAddress) { 118 | return swapInfo, tokens.ErrTxWithWrongReceiver 119 | } 120 | 121 | // check sender 122 | if swapInfo.From == swapInfo.To { 123 | return swapInfo, tokens.ErrTxWithWrongSender 124 | } 125 | 126 | if !tokens.CheckSwapValue(swapInfo.Value, b.IsSrc) { 127 | return swapInfo, tokens.ErrTxWithWrongValue 128 | } 129 | 130 | log.Debug("verify swapin stable pass", "from", swapInfo.From, "to", swapInfo.To, "bind", swapInfo.Bind, "value", swapInfo.Value, "txid", txHash, "height", swapInfo.Height, "timestamp", swapInfo.Timestamp) 131 | return swapInfo, nil 132 | } 133 | -------------------------------------------------------------------------------- /tokens/fsn/bridge.go: -------------------------------------------------------------------------------- 1 | package fsn 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | "strings" 7 | "time" 8 | 9 | "github.com/fsn-dev/crossChain-Bridge/log" 10 | "github.com/fsn-dev/crossChain-Bridge/tokens" 11 | "github.com/fsn-dev/crossChain-Bridge/tokens/eth" 12 | "github.com/fsn-dev/crossChain-Bridge/types" 13 | ) 14 | 15 | const ( 16 | netMainnet = "mainnet" 17 | netTestnet = "testnet" 18 | netDevnet = "devnet" 19 | netCustom = "custom" 20 | ) 21 | 22 | // Bridge fsn bridge inherit from eth bridge 23 | type Bridge struct { 24 | *eth.Bridge 25 | } 26 | 27 | // NewCrossChainBridge new fsn bridge 28 | func NewCrossChainBridge(isSrc bool) *Bridge { 29 | return &Bridge{Bridge: eth.NewCrossChainBridge(isSrc)} 30 | } 31 | 32 | // SetTokenAndGateway set token and gateway config 33 | func (b *Bridge) SetTokenAndGateway(tokenCfg *tokens.TokenConfig, gatewayCfg *tokens.GatewayConfig) { 34 | b.CrossChainBridgeBase.SetTokenAndGateway(tokenCfg, gatewayCfg) 35 | b.VerifyChainID() 36 | b.VerifyTokenCofig() 37 | b.InitLatestBlockNumber() 38 | } 39 | 40 | // VerifyChainID verify chain id 41 | func (b *Bridge) VerifyChainID() { 42 | tokenCfg := b.TokenConfig 43 | gatewayCfg := b.GatewayConfig 44 | 45 | networkID := strings.ToLower(tokenCfg.NetID) 46 | 47 | switch networkID { 48 | case netMainnet, netTestnet, netDevnet: 49 | case netCustom: 50 | return 51 | default: 52 | panic(fmt.Sprintf("unsupported fusion network: %v", tokenCfg.NetID)) 53 | } 54 | 55 | var ( 56 | chainID *big.Int 57 | err error 58 | ) 59 | 60 | for { 61 | chainID, err = b.ChainID() 62 | if err == nil { 63 | break 64 | } 65 | log.Errorf("can not get gateway chainID. %v", err) 66 | log.Println("retry query gateway", gatewayCfg.APIAddress) 67 | time.Sleep(3 * time.Second) 68 | } 69 | 70 | panicMismatchChainID := func() { 71 | panic(fmt.Sprintf("gateway chainID %v is not %v", chainID, tokenCfg.NetID)) 72 | } 73 | 74 | switch networkID { 75 | case netMainnet: 76 | if chainID.Uint64() != 32659 { 77 | panicMismatchChainID() 78 | } 79 | case netTestnet: 80 | if chainID.Uint64() != 46688 { 81 | panicMismatchChainID() 82 | } 83 | case netDevnet: 84 | if chainID.Uint64() != 55555 { 85 | panicMismatchChainID() 86 | } 87 | default: 88 | panic("unsupported fusion network") 89 | } 90 | 91 | b.Signer = types.MakeSigner("EIP155", chainID) 92 | 93 | log.Info("VerifyChainID succeed", "networkID", networkID, "chainID", chainID) 94 | } 95 | -------------------------------------------------------------------------------- /tokens/fsn/callapi.go: -------------------------------------------------------------------------------- 1 | package fsn 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math/big" 7 | 8 | "github.com/fsn-dev/crossChain-Bridge/rpc/client" 9 | "github.com/fsn-dev/crossChain-Bridge/types" 10 | ) 11 | 12 | // GetTransactionAndReceipt get tx and receipt (fsn special) 13 | func (b *Bridge) GetTransactionAndReceipt(txHash string) (*types.RPCTxAndReceipt, error) { 14 | gateway := b.GatewayConfig 15 | url := gateway.APIAddress 16 | var result *types.RPCTxAndReceipt 17 | err := client.RPCPost(&result, url, "fsn_getTransactionAndReceipt", txHash) 18 | if err != nil { 19 | return nil, err 20 | } 21 | if result == nil { 22 | return nil, errors.New("tx and receipt not found") 23 | } 24 | return result, nil 25 | } 26 | 27 | // ChainID get chain id use net_version (eth_chainId does not work) 28 | func (b *Bridge) ChainID() (*big.Int, error) { 29 | gateway := b.GatewayConfig 30 | url := gateway.APIAddress 31 | var result string 32 | err := client.RPCPost(&result, url, "net_version") 33 | if err != nil { 34 | return nil, err 35 | } 36 | version := new(big.Int) 37 | if _, ok := version.SetString(result, 10); !ok { 38 | return nil, fmt.Errorf("invalid net_version result %q", result) 39 | } 40 | return version, nil 41 | } 42 | -------------------------------------------------------------------------------- /tokens/tools/cachedscanblocks.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | // NewCachedScannedBlocks new cached scanned blocks 4 | func NewCachedScannedBlocks(capacity int) *CachedScannedBlocks { 5 | return &CachedScannedBlocks{ 6 | nextIndex: 0, 7 | capacity: capacity, 8 | blocks: make([]cachedScannedBlockRecord, capacity), 9 | } 10 | } 11 | 12 | // CachedScannedBlocks cached scanned blocks 13 | type CachedScannedBlocks struct { 14 | nextIndex int 15 | capacity int 16 | blocks []cachedScannedBlockRecord 17 | } 18 | 19 | type cachedScannedBlockRecord struct { 20 | hash string 21 | height uint64 22 | } 23 | 24 | // CacheScannedBlock add cache block 25 | func (c *CachedScannedBlocks) CacheScannedBlock(hash string, height uint64) { 26 | c.blocks[c.nextIndex] = cachedScannedBlockRecord{ 27 | hash: hash, 28 | height: height, 29 | } 30 | c.nextIndex = (c.nextIndex + 1) % c.capacity 31 | } 32 | 33 | // IsBlockScanned return if cache block exist 34 | func (c *CachedScannedBlocks) IsBlockScanned(blockHash string) bool { 35 | for _, block := range c.blocks { 36 | if block.hash == blockHash { 37 | return true 38 | } 39 | } 40 | return false 41 | } 42 | -------------------------------------------------------------------------------- /tokens/tools/cachedscantxs.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | // NewCachedScannedTxs new cached scanned txs 4 | func NewCachedScannedTxs(capacity int) *CachedScannedTxs { 5 | return &CachedScannedTxs{ 6 | nextIndex: 0, 7 | capacity: capacity, 8 | txs: make([]cachedScannedTxRecord, capacity), 9 | } 10 | } 11 | 12 | // CachedScannedTxs cached scanned txs 13 | type CachedScannedTxs struct { 14 | nextIndex int 15 | capacity int 16 | txs []cachedScannedTxRecord 17 | } 18 | 19 | type cachedScannedTxRecord struct { 20 | hash string 21 | } 22 | 23 | // CacheScannedTx add cache tx 24 | func (c *CachedScannedTxs) CacheScannedTx(hash string) { 25 | c.txs[c.nextIndex] = cachedScannedTxRecord{ 26 | hash: hash, 27 | } 28 | c.nextIndex = (c.nextIndex + 1) % c.capacity 29 | } 30 | 31 | // IsTxScanned return if cache tx exist 32 | func (c *CachedScannedTxs) IsTxScanned(txHash string) bool { 33 | for _, tx := range c.txs { 34 | if tx.hash == txHash { 35 | return true 36 | } 37 | } 38 | return false 39 | } 40 | -------------------------------------------------------------------------------- /tools/crypto/signature.go: -------------------------------------------------------------------------------- 1 | // Copyright 2017 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | 17 | package crypto 18 | 19 | import ( 20 | "crypto/ecdsa" 21 | "crypto/elliptic" 22 | "errors" 23 | "fmt" 24 | "math/big" 25 | 26 | "github.com/btcsuite/btcd/btcec" 27 | ) 28 | 29 | // Ecrecover returns the uncompressed public key that created the given signature. 30 | func Ecrecover(hash, sig []byte) ([]byte, error) { 31 | pub, err := SigToPub(hash, sig) 32 | if err != nil { 33 | return nil, err 34 | } 35 | bytes := (*btcec.PublicKey)(pub).SerializeUncompressed() 36 | return bytes, err 37 | } 38 | 39 | // SigToPub returns the public key that created the given signature. 40 | func SigToPub(hash, sig []byte) (*ecdsa.PublicKey, error) { 41 | // Convert to btcec input format with 'recovery id' v at the beginning. 42 | btcsig := make([]byte, SignatureLength) 43 | btcsig[0] = sig[64] + 27 44 | copy(btcsig[1:], sig) 45 | 46 | pub, _, err := btcec.RecoverCompact(btcec.S256(), btcsig, hash) 47 | return (*ecdsa.PublicKey)(pub), err 48 | } 49 | 50 | // Sign calculates an ECDSA signature. 51 | // 52 | // This function is susceptible to chosen plaintext attacks that can leak 53 | // information about the private key that is used for signing. Callers must 54 | // be aware that the given hash cannot be chosen by an adversery. Common 55 | // solution is to hash any input before calculating the signature. 56 | // 57 | // The produced signature is in the [R || S || V] format where V is 0 or 1. 58 | func Sign(hash []byte, prv *ecdsa.PrivateKey) ([]byte, error) { 59 | if len(hash) != 32 { 60 | return nil, fmt.Errorf("hash is required to be exactly 32 bytes (%d)", len(hash)) 61 | } 62 | if prv.Curve != btcec.S256() { 63 | return nil, fmt.Errorf("private key curve is not secp256k1") 64 | } 65 | sig, err := btcec.SignCompact(btcec.S256(), (*btcec.PrivateKey)(prv), hash, false) 66 | if err != nil { 67 | return nil, err 68 | } 69 | // Convert to Ethereum signature format with 'recovery id' v at the end. 70 | v := sig[0] - 27 71 | copy(sig, sig[1:]) 72 | sig[64] = v 73 | return sig, nil 74 | } 75 | 76 | // VerifySignature checks that the given public key created signature over hash. 77 | // The public key should be in compressed (33 bytes) or uncompressed (65 bytes) format. 78 | // The signature should have the 64 byte [R || S] format. 79 | func VerifySignature(pubkey, hash, signature []byte) bool { 80 | if len(signature) != 64 { 81 | return false 82 | } 83 | sig := &btcec.Signature{R: new(big.Int).SetBytes(signature[:32]), S: new(big.Int).SetBytes(signature[32:])} 84 | key, err := btcec.ParsePubKey(pubkey, btcec.S256()) 85 | if err != nil { 86 | return false 87 | } 88 | // Reject malleable signatures. libsecp256k1 does this check but btcec doesn't. 89 | if sig.S.Cmp(secp256k1halfN) > 0 { 90 | return false 91 | } 92 | return sig.Verify(hash, key) 93 | } 94 | 95 | // DecompressPubkey parses a public key in the 33-byte compressed format. 96 | func DecompressPubkey(pubkey []byte) (*ecdsa.PublicKey, error) { 97 | if len(pubkey) != 33 { 98 | return nil, errors.New("invalid compressed public key length") 99 | } 100 | key, err := btcec.ParsePubKey(pubkey, btcec.S256()) 101 | if err != nil { 102 | return nil, err 103 | } 104 | return key.ToECDSA(), nil 105 | } 106 | 107 | // CompressPubkey encodes a public key to the 33-byte compressed format. 108 | func CompressPubkey(pubkey *ecdsa.PublicKey) []byte { 109 | return (*btcec.PublicKey)(pubkey).SerializeCompressed() 110 | } 111 | 112 | // S256 returns an instance of the secp256k1 curve. 113 | func S256() elliptic.Curve { 114 | return btcec.S256() 115 | } 116 | -------------------------------------------------------------------------------- /tools/keystore/aes.go: -------------------------------------------------------------------------------- 1 | // Copyright 2016 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | 17 | package keystore 18 | 19 | import ( 20 | "crypto/aes" 21 | "crypto/cipher" 22 | "errors" 23 | ) 24 | 25 | var ( 26 | // ErrDecrypt decrypt error 27 | ErrDecrypt = errors.New("could not decrypt key with given password") 28 | ) 29 | 30 | func aesCTRXOR(key, inText, iv []byte) ([]byte, error) { 31 | // AES-128 is selected due to size of encryptKey. 32 | aesBlock, err := aes.NewCipher(key) 33 | if err != nil { 34 | return nil, err 35 | } 36 | stream := cipher.NewCTR(aesBlock, iv) 37 | outText := make([]byte, len(inText)) 38 | stream.XORKeyStream(outText, inText) 39 | return outText, err 40 | } 41 | 42 | func aesCBCDecrypt(key, cipherText, iv []byte) ([]byte, error) { 43 | aesBlock, err := aes.NewCipher(key) 44 | if err != nil { 45 | return nil, err 46 | } 47 | decrypter := cipher.NewCBCDecrypter(aesBlock, iv) 48 | paddedPlaintext := make([]byte, len(cipherText)) 49 | decrypter.CryptBlocks(paddedPlaintext, cipherText) 50 | plaintext := pkcs7Unpad(paddedPlaintext) 51 | if plaintext == nil { 52 | return nil, ErrDecrypt 53 | } 54 | return plaintext, err 55 | } 56 | 57 | // From https://leanpub.com/gocrypto/read#leanpub-auto-block-cipher-modes 58 | func pkcs7Unpad(in []byte) []byte { 59 | if len(in) == 0 { 60 | return nil 61 | } 62 | 63 | padding := in[len(in)-1] 64 | if int(padding) > len(in) || padding > aes.BlockSize { 65 | return nil 66 | } else if padding == 0 { 67 | return nil 68 | } 69 | 70 | for i := len(in) - 1; i > len(in)-int(padding)-1; i-- { 71 | if in[i] != padding { 72 | return nil 73 | } 74 | } 75 | return in[:len(in)-int(padding)] 76 | } 77 | -------------------------------------------------------------------------------- /tools/keystore/key.go: -------------------------------------------------------------------------------- 1 | package keystore 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "encoding/hex" 6 | "encoding/json" 7 | 8 | "github.com/fsn-dev/crossChain-Bridge/common" 9 | "github.com/fsn-dev/crossChain-Bridge/tools/crypto" 10 | "github.com/pborman/uuid" 11 | ) 12 | 13 | const ( 14 | version = 3 15 | ) 16 | 17 | // Key struct 18 | type Key struct { 19 | ID uuid.UUID // Version 4 "random" for unique id not derived from key data 20 | // to simplify lookups we also store the address 21 | Address common.Address 22 | // we only store privkey as pubkey/address can be derived from it 23 | // privkey in this struct is always in plaintext 24 | PrivateKey *ecdsa.PrivateKey 25 | } 26 | 27 | type plainKeyJSON struct { 28 | Address string `json:"address"` 29 | PrivateKey string `json:"privatekey"` 30 | ID string `json:"id"` 31 | Version int `json:"version"` 32 | } 33 | 34 | type encryptedKeyJSONV3 struct { 35 | Address string `json:"address"` 36 | Crypto CryptoJSON `json:"crypto"` 37 | ID string `json:"id"` 38 | Version int `json:"version"` 39 | } 40 | 41 | type encryptedKeyJSONV1 struct { 42 | Address string `json:"address"` 43 | Crypto CryptoJSON `json:"crypto"` 44 | ID string `json:"id"` 45 | Version string `json:"version"` 46 | } 47 | 48 | // CryptoJSON struct 49 | type CryptoJSON struct { 50 | Cipher string `json:"cipher"` 51 | CipherText string `json:"ciphertext"` 52 | CipherParams cipherparamsJSON `json:"cipherparams"` 53 | KDF string `json:"kdf"` 54 | KDFParams map[string]interface{} `json:"kdfparams"` 55 | MAC string `json:"mac"` 56 | } 57 | 58 | type cipherparamsJSON struct { 59 | IV string `json:"iv"` 60 | } 61 | 62 | // MarshalJSON marshal json 63 | func (k *Key) MarshalJSON() (j []byte, err error) { 64 | jStruct := plainKeyJSON{ 65 | hex.EncodeToString(k.Address[:]), 66 | hex.EncodeToString(crypto.FromECDSA(k.PrivateKey)), 67 | k.ID.String(), 68 | version, 69 | } 70 | j, err = json.Marshal(jStruct) 71 | return j, err 72 | } 73 | 74 | // UnmarshalJSON unmarshal json 75 | func (k *Key) UnmarshalJSON(j []byte) (err error) { 76 | keyJSON := new(plainKeyJSON) 77 | err = json.Unmarshal(j, &keyJSON) 78 | if err != nil { 79 | return err 80 | } 81 | 82 | u := new(uuid.UUID) 83 | *u = uuid.Parse(keyJSON.ID) 84 | k.ID = *u 85 | addr, err := hex.DecodeString(keyJSON.Address) 86 | if err != nil { 87 | return err 88 | } 89 | privkey, err := crypto.HexToECDSA(keyJSON.PrivateKey) 90 | if err != nil { 91 | return err 92 | } 93 | 94 | k.Address = common.BytesToAddress(addr) 95 | k.PrivateKey = privkey 96 | 97 | return nil 98 | } 99 | -------------------------------------------------------------------------------- /tools/loadkeystore.go: -------------------------------------------------------------------------------- 1 | package tools 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "strings" 7 | 8 | "github.com/fsn-dev/crossChain-Bridge/tools/keystore" 9 | ) 10 | 11 | // LoadKeyStore load keystore from keyfile and passfile 12 | func LoadKeyStore(keyfile, passfile string) (*keystore.Key, error) { 13 | keyjson, err := ioutil.ReadFile(keyfile) 14 | if err != nil { 15 | return nil, fmt.Errorf("read keystore fail %v", err) 16 | } 17 | passdata, err := ioutil.ReadFile(passfile) 18 | if err != nil { 19 | return nil, fmt.Errorf("read password fail %v", err) 20 | } 21 | passwd := strings.TrimSpace(string(passdata)) 22 | key, err := keystore.DecryptKey(keyjson, passwd) 23 | if err != nil { 24 | return nil, fmt.Errorf("decrypt key fail %v", err) 25 | } 26 | return key, nil 27 | } 28 | -------------------------------------------------------------------------------- /tools/rlp/decode_tail_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | 17 | package rlp 18 | 19 | import ( 20 | "bytes" 21 | "fmt" 22 | ) 23 | 24 | type structWithTail struct { 25 | A, B uint 26 | C []uint `rlp:"tail"` 27 | } 28 | 29 | func ExampleDecode_structTagTail() { 30 | // In this example, the "tail" struct tag is used to decode lists of 31 | // differing length into a struct. 32 | var val structWithTail 33 | 34 | err := Decode(bytes.NewReader([]byte{0xC4, 0x01, 0x02, 0x03, 0x04}), &val) 35 | fmt.Printf("with 4 elements: err=%v val=%v\n", err, val) 36 | 37 | err = Decode(bytes.NewReader([]byte{0xC6, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06}), &val) 38 | fmt.Printf("with 6 elements: err=%v val=%v\n", err, val) 39 | 40 | // Note that at least two list elements must be present to 41 | // fill fields A and B: 42 | err = Decode(bytes.NewReader([]byte{0xC1, 0x01}), &val) 43 | fmt.Printf("with 1 element: err=%q\n", err) 44 | 45 | // Output: 46 | // with 4 elements: err= val={1 2 [3 4]} 47 | // with 6 elements: err= val={1 2 [3 4 5 6]} 48 | // with 1 element: err="rlp: too few elements for rlp.structWithTail" 49 | } 50 | -------------------------------------------------------------------------------- /tools/rlp/encoder_example_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | 17 | package rlp 18 | 19 | import ( 20 | "fmt" 21 | "io" 22 | ) 23 | 24 | type MyCoolType struct { 25 | Name string 26 | a, b uint 27 | } 28 | 29 | // EncodeRLP writes x as RLP list [a, b] that omits the Name field. 30 | func (x *MyCoolType) EncodeRLP(w io.Writer) (err error) { 31 | return Encode(w, []uint{x.a, x.b}) 32 | } 33 | 34 | func ExampleEncoder() { 35 | var t *MyCoolType // t is nil pointer to MyCoolType 36 | bytes, _ := EncodeToBytes(t) 37 | fmt.Printf("%v → %X\n", t, bytes) 38 | 39 | t = &MyCoolType{Name: "foobar", a: 5, b: 6} 40 | bytes, _ = EncodeToBytes(t) 41 | fmt.Printf("%v → %X\n", t, bytes) 42 | 43 | // Output: 44 | // → C0 45 | // &{foobar 5 6} → C20506 46 | } 47 | -------------------------------------------------------------------------------- /tools/rlp/raw.go: -------------------------------------------------------------------------------- 1 | // Copyright 2015 The go-ethereum Authors 2 | // This file is part of the go-ethereum library. 3 | // 4 | // The go-ethereum library is free software: you can redistribute it and/or modify 5 | // it under the terms of the GNU Lesser General Public License as published by 6 | // the Free Software Foundation, either version 3 of the License, or 7 | // (at your option) any later version. 8 | // 9 | // The go-ethereum library is distributed in the hope that it will be useful, 10 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | // GNU Lesser General Public License for more details. 13 | // 14 | // You should have received a copy of the GNU Lesser General Public License 15 | // along with the go-ethereum library. If not, see . 16 | 17 | package rlp 18 | 19 | import ( 20 | "io" 21 | "reflect" 22 | ) 23 | 24 | // RawValue represents an encoded RLP value and can be used to delay 25 | // RLP decoding or to precompute an encoding. Note that the decoder does 26 | // not verify whether the content of RawValues is valid RLP. 27 | type RawValue []byte 28 | 29 | var rawValueType = reflect.TypeOf(RawValue{}) 30 | 31 | // ListSize returns the encoded size of an RLP list with the given 32 | // content size. 33 | func ListSize(contentSize uint64) uint64 { 34 | return uint64(headsize(contentSize)) + contentSize 35 | } 36 | 37 | // Split returns the content of first RLP value and any 38 | // bytes after the value as subslices of b. 39 | func Split(b []byte) (k Kind, content, rest []byte, err error) { 40 | k, ts, cs, err := readKind(b) 41 | if err != nil { 42 | return 0, nil, b, err 43 | } 44 | return k, b[ts : ts+cs], b[ts+cs:], nil 45 | } 46 | 47 | // SplitString splits b into the content of an RLP string 48 | // and any remaining bytes after the string. 49 | func SplitString(b []byte) (content, rest []byte, err error) { 50 | k, content, rest, err := Split(b) 51 | if err != nil { 52 | return nil, b, err 53 | } 54 | if k == List { 55 | return nil, b, ErrExpectedString 56 | } 57 | return content, rest, nil 58 | } 59 | 60 | // SplitList splits b into the content of a list and any remaining 61 | // bytes after the list. 62 | func SplitList(b []byte) (content, rest []byte, err error) { 63 | k, content, rest, err := Split(b) 64 | if err != nil { 65 | return nil, b, err 66 | } 67 | if k != List { 68 | return nil, b, ErrExpectedList 69 | } 70 | return content, rest, nil 71 | } 72 | 73 | // CountValues counts the number of encoded values in b. 74 | func CountValues(b []byte) (int, error) { 75 | i := 0 76 | for ; len(b) > 0; i++ { 77 | _, tagsize, size, err := readKind(b) 78 | if err != nil { 79 | return 0, err 80 | } 81 | b = b[tagsize+size:] 82 | } 83 | return i, nil 84 | } 85 | 86 | func readKind(buf []byte) (k Kind, tagsize, contentsize uint64, err error) { 87 | if len(buf) == 0 { 88 | return 0, 0, 0, io.ErrUnexpectedEOF 89 | } 90 | b := buf[0] 91 | switch { 92 | case b < 0x80: 93 | k = Byte 94 | tagsize = 0 95 | contentsize = 1 96 | case b < 0xB8: 97 | k = String 98 | tagsize = 1 99 | contentsize = uint64(b - 0x80) 100 | // Reject strings that should've been single bytes. 101 | if contentsize == 1 && len(buf) > 1 && buf[1] < 128 { 102 | return 0, 0, 0, ErrCanonSize 103 | } 104 | case b < 0xC0: 105 | k = String 106 | tagsize = uint64(b-0xB7) + 1 107 | contentsize, err = readSize(buf[1:], b-0xB7) 108 | case b < 0xF8: 109 | k = List 110 | tagsize = 1 111 | contentsize = uint64(b - 0xC0) 112 | default: 113 | k = List 114 | tagsize = uint64(b-0xF7) + 1 115 | contentsize, err = readSize(buf[1:], b-0xF7) 116 | } 117 | if err != nil { 118 | return 0, 0, 0, err 119 | } 120 | // Reject values larger than the input slice. 121 | if contentsize > uint64(len(buf))-tagsize { 122 | return 0, 0, 0, ErrValueTooLarge 123 | } 124 | return k, tagsize, contentsize, err 125 | } 126 | 127 | func readSize(b []byte, slen byte) (uint64, error) { 128 | if int(slen) > len(b) { 129 | return 0, io.ErrUnexpectedEOF 130 | } 131 | var s uint64 132 | switch slen { 133 | case 1: 134 | s = uint64(b[0]) 135 | case 2: 136 | s = uint64(b[0])<<8 | uint64(b[1]) 137 | case 3: 138 | s = uint64(b[0])<<16 | uint64(b[1])<<8 | uint64(b[2]) 139 | case 4: 140 | s = uint64(b[0])<<24 | uint64(b[1])<<16 | uint64(b[2])<<8 | uint64(b[3]) 141 | case 5: 142 | s = uint64(b[0])<<32 | uint64(b[1])<<24 | uint64(b[2])<<16 | uint64(b[3])<<8 | uint64(b[4]) 143 | case 6: 144 | s = uint64(b[0])<<40 | uint64(b[1])<<32 | uint64(b[2])<<24 | uint64(b[3])<<16 | uint64(b[4])<<8 | uint64(b[5]) 145 | case 7: 146 | s = uint64(b[0])<<48 | uint64(b[1])<<40 | uint64(b[2])<<32 | uint64(b[3])<<24 | uint64(b[4])<<16 | uint64(b[5])<<8 | uint64(b[6]) 147 | case 8: 148 | s = uint64(b[0])<<56 | uint64(b[1])<<48 | uint64(b[2])<<40 | uint64(b[3])<<32 | uint64(b[4])<<24 | uint64(b[5])<<16 | uint64(b[6])<<8 | uint64(b[7]) 149 | } 150 | // Reject sizes < 56 (shouldn't have separate size) and sizes with 151 | // leading zero bytes. 152 | if s < 56 || b[0] == 0 { 153 | return 0, ErrCanonSize 154 | } 155 | return s, nil 156 | } 157 | -------------------------------------------------------------------------------- /types/gen_tx_json.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "math/big" 8 | 9 | "github.com/fsn-dev/crossChain-Bridge/common" 10 | "github.com/fsn-dev/crossChain-Bridge/common/hexutil" 11 | "github.com/fsn-dev/crossChain-Bridge/tools/rlp" 12 | ) 13 | 14 | // MarshalJSON marshals as JSON. 15 | func (t *txdata) MarshalJSON() ([]byte, error) { 16 | type txdata struct { 17 | AccountNonce hexutil.Uint64 `json:"nonce" gencodec:"required"` 18 | Price *hexutil.Big `json:"gasPrice" gencodec:"required"` 19 | GasLimit hexutil.Uint64 `json:"gas" gencodec:"required"` 20 | Recipient *common.Address `json:"to" rlp:"nil"` 21 | Amount *hexutil.Big `json:"value" gencodec:"required"` 22 | Payload hexutil.Bytes `json:"input" gencodec:"required"` 23 | V *hexutil.Big `json:"v" gencodec:"required"` 24 | R *hexutil.Big `json:"r" gencodec:"required"` 25 | S *hexutil.Big `json:"s" gencodec:"required"` 26 | Hash *common.Hash `json:"hash" rlp:"-"` 27 | } 28 | var enc txdata 29 | enc.AccountNonce = hexutil.Uint64(t.AccountNonce) 30 | enc.Price = (*hexutil.Big)(t.Price) 31 | enc.GasLimit = hexutil.Uint64(t.GasLimit) 32 | enc.Recipient = t.Recipient 33 | enc.Amount = (*hexutil.Big)(t.Amount) 34 | enc.Payload = t.Payload 35 | enc.V = (*hexutil.Big)(t.V) 36 | enc.R = (*hexutil.Big)(t.R) 37 | enc.S = (*hexutil.Big)(t.S) 38 | enc.Hash = t.Hash 39 | return json.Marshal(&enc) 40 | } 41 | 42 | // UnmarshalJSON unmarshals from JSON. 43 | func (t *txdata) UnmarshalJSON(input []byte) error { 44 | type txdata struct { 45 | AccountNonce *hexutil.Uint64 `json:"nonce" gencodec:"required"` 46 | Price *hexutil.Big `json:"gasPrice" gencodec:"required"` 47 | GasLimit *hexutil.Uint64 `json:"gas" gencodec:"required"` 48 | Recipient *common.Address `json:"to" rlp:"nil"` 49 | Amount *hexutil.Big `json:"value" gencodec:"required"` 50 | Payload *hexutil.Bytes `json:"input" gencodec:"required"` 51 | V *hexutil.Big `json:"v" gencodec:"required"` 52 | R *hexutil.Big `json:"r" gencodec:"required"` 53 | S *hexutil.Big `json:"s" gencodec:"required"` 54 | Hash *common.Hash `json:"hash" rlp:"-"` 55 | } 56 | var dec txdata 57 | if err := json.Unmarshal(input, &dec); err != nil { 58 | return err 59 | } 60 | if dec.AccountNonce == nil { 61 | return errors.New("missing required field 'nonce' for txdata") 62 | } 63 | t.AccountNonce = uint64(*dec.AccountNonce) 64 | if dec.Price == nil { 65 | return errors.New("missing required field 'gasPrice' for txdata") 66 | } 67 | t.Price = (*big.Int)(dec.Price) 68 | if dec.GasLimit == nil { 69 | return errors.New("missing required field 'gas' for txdata") 70 | } 71 | t.GasLimit = uint64(*dec.GasLimit) 72 | if dec.Recipient != nil { 73 | t.Recipient = dec.Recipient 74 | } 75 | if dec.Amount == nil { 76 | return errors.New("missing required field 'value' for txdata") 77 | } 78 | t.Amount = (*big.Int)(dec.Amount) 79 | if dec.Payload == nil { 80 | return errors.New("missing required field 'input' for txdata") 81 | } 82 | t.Payload = *dec.Payload 83 | if dec.V == nil { 84 | return errors.New("missing required field 'v' for txdata") 85 | } 86 | t.V = (*big.Int)(dec.V) 87 | if dec.R == nil { 88 | return errors.New("missing required field 'r' for txdata") 89 | } 90 | t.R = (*big.Int)(dec.R) 91 | if dec.S == nil { 92 | return errors.New("missing required field 's' for txdata") 93 | } 94 | t.S = (*big.Int)(dec.S) 95 | if dec.Hash != nil { 96 | t.Hash = dec.Hash 97 | } 98 | return nil 99 | } 100 | 101 | // PrintPretty print pretty (json) 102 | func (tx *Transaction) PrintPretty() { 103 | bs, _ := json.MarshalIndent(tx, "", " ") 104 | fmt.Println(string(bs)) 105 | } 106 | 107 | // PrintRaw print raw encoded (hex string) 108 | func (tx *Transaction) PrintRaw() { 109 | bs, _ := rlp.EncodeToBytes(tx) 110 | fmt.Println(hexutil.Bytes(bs)) 111 | } 112 | -------------------------------------------------------------------------------- /types/rpctypes.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "fmt" 5 | "math/big" 6 | 7 | "github.com/fsn-dev/crossChain-Bridge/common" 8 | "github.com/fsn-dev/crossChain-Bridge/common/hexutil" 9 | ) 10 | 11 | // RPCBlock struct 12 | type RPCBlock struct { 13 | Hash *common.Hash `json:"hash"` 14 | ParentHash *common.Hash `json:"parentHash"` 15 | UncleHash *common.Hash `json:"sha3Uncles"` 16 | Coinbase *common.Address `json:"miner"` 17 | Root *common.Hash `json:"stateRoot"` 18 | TxHash *common.Hash `json:"transactionsRoot"` 19 | ReceiptHash *common.Hash `json:"receiptsRoot"` 20 | Bloom *hexutil.Bytes `json:"logsBloom"` 21 | Difficulty *hexutil.Big `json:"difficulty"` 22 | Number *hexutil.Big `json:"number"` 23 | GasLimit *hexutil.Uint64 `json:"gasLimit"` 24 | GasUsed *hexutil.Uint64 `json:"gasUsed"` 25 | Time *hexutil.Big `json:"timestamp"` 26 | Extra *hexutil.Bytes `json:"extraData"` 27 | MixDigest *common.Hash `json:"mixHash"` 28 | Nonce *hexutil.Bytes `json:"nonce"` 29 | Size *string `json:"size"` 30 | TotalDifficulty *hexutil.Big `json:"totalDifficulty"` 31 | Transactions []*common.Hash `json:"transactions"` 32 | Uncles []*common.Hash `json:"uncles"` 33 | } 34 | 35 | // RPCTransaction struct 36 | type RPCTransaction struct { 37 | Hash *common.Hash `json:"hash"` 38 | TransactionIndex *hexutil.Uint `json:"transactionIndex"` 39 | BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` 40 | BlockHash *common.Hash `json:"blockHash,omitempty"` 41 | From *common.Address `json:"from,omitempty"` 42 | AccountNonce *hexutil.Uint64 `json:"nonce"` 43 | Price *hexutil.Big `json:"gasPrice"` 44 | GasLimit *hexutil.Uint64 `json:"gas"` 45 | Recipient *common.Address `json:"to"` 46 | Amount *hexutil.Big `json:"value"` 47 | Payload *hexutil.Bytes `json:"input"` 48 | V *hexutil.Big `json:"v"` 49 | R *hexutil.Big `json:"r"` 50 | S *hexutil.Big `json:"s"` 51 | } 52 | 53 | // RPCLog struct 54 | type RPCLog struct { 55 | Address *common.Address `json:"address"` 56 | Topics []common.Hash `json:"topics"` 57 | Data *hexutil.Bytes `json:"data"` 58 | BlockNumber *hexutil.Uint64 `json:"blockNumber"` 59 | TxHash *common.Hash `json:"transactionHash"` 60 | TxIndex *hexutil.Uint `json:"transactionIndex"` 61 | BlockHash *common.Hash `json:"blockHash"` 62 | Index *hexutil.Uint `json:"logIndex"` 63 | Removed *bool `json:"removed"` 64 | } 65 | 66 | // RPCTxReceipt struct 67 | type RPCTxReceipt struct { 68 | TxHash *common.Hash `json:"transactionHash"` 69 | TxIndex *hexutil.Uint `json:"transactionIndex"` 70 | BlockNumber *hexutil.Big `json:"blockNumber"` 71 | BlockHash *common.Hash `json:"blockHash"` 72 | PostState *hexutil.Bytes `json:"root"` 73 | Status *hexutil.Uint64 `json:"status"` 74 | From *common.Address `json:"from"` 75 | Recipient *common.Address `json:"to"` 76 | GasUsed *hexutil.Uint64 `json:"gasUsed"` 77 | CumulativeGasUsed *hexutil.Uint64 `json:"cumulativeGasUsed"` 78 | ContractAddress *common.Address `json:"contractAddress,omitempty"` 79 | Bloom *hexutil.Bytes `json:"logsBloom"` 80 | FsnLogTopic *string `json:"fsnLogTopic,omitempty"` 81 | FsnLogData interface{} `json:"fsnLogData,omitempty"` 82 | Logs []*RPCLog `json:"logs"` 83 | } 84 | 85 | // RPCTxAndReceipt struct 86 | type RPCTxAndReceipt struct { 87 | FsnTxInput interface{} `json:"fsnTxInput,omitempty"` 88 | Tx *RPCTransaction `json:"tx"` 89 | Receipt *RPCTxReceipt `json:"receipt"` 90 | ReceiptFound *bool `json:"receiptFound"` 91 | } 92 | 93 | // FilterQuery struct 94 | type FilterQuery struct { 95 | BlockHash *common.Hash 96 | FromBlock *big.Int 97 | ToBlock *big.Int 98 | Addresses []common.Address 99 | Topics [][]common.Hash 100 | } 101 | 102 | // ToFilterArg query to filter arg 103 | func ToFilterArg(q *FilterQuery) (interface{}, error) { 104 | arg := map[string]interface{}{ 105 | "address": q.Addresses, 106 | "topics": q.Topics, 107 | } 108 | if q.BlockHash != nil { 109 | arg["blockHash"] = *q.BlockHash 110 | if q.FromBlock != nil || q.ToBlock != nil { 111 | return nil, fmt.Errorf("cannot specify both BlockHash and FromBlock/ToBlock") 112 | } 113 | } else { 114 | if q.FromBlock == nil { 115 | arg["fromBlock"] = "0x0" 116 | } else { 117 | arg["fromBlock"] = ToBlockNumArg(q.FromBlock) 118 | } 119 | arg["toBlock"] = ToBlockNumArg(q.ToBlock) 120 | } 121 | return arg, nil 122 | } 123 | 124 | // ToBlockNumArg to block number arg 125 | func ToBlockNumArg(number *big.Int) string { 126 | if number == nil { 127 | return "latest" 128 | } 129 | return hexutil.EncodeBig(number) 130 | } 131 | -------------------------------------------------------------------------------- /worker/aggregate.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/fsn-dev/crossChain-Bridge/mongodb" 7 | "github.com/fsn-dev/crossChain-Bridge/tokens" 8 | "github.com/fsn-dev/crossChain-Bridge/tokens/btc" 9 | "github.com/fsn-dev/crossChain-Bridge/tokens/btc/electrs" 10 | ) 11 | 12 | var ( 13 | utxoPageLimit = 100 14 | 15 | aggSumVal uint64 16 | aggAddrs []string 17 | aggUtxos []*electrs.ElectUtxo 18 | aggOffset int 19 | aggInterval = 300 * time.Second 20 | ) 21 | 22 | // StartAggregateJob aggregate job 23 | func StartAggregateJob() { 24 | if btc.BridgeInstance == nil { 25 | return 26 | } 27 | 28 | for loop := 1; ; loop++ { 29 | logWorker("aggregate", "start aggregate job", "loop", loop) 30 | doAggregateJob() 31 | logWorker("aggregate", "finish aggregate job", "loop", loop) 32 | time.Sleep(aggInterval) 33 | } 34 | } 35 | 36 | func doAggregateJob() { 37 | aggOffset = 0 38 | for { 39 | p2shAddrs, err := mongodb.FindP2shAddresses(aggOffset, utxoPageLimit) 40 | if err != nil { 41 | logWorkerError("aggregate", "FindP2shAddresses failed", err, "offset", aggOffset, "limit", utxoPageLimit) 42 | time.Sleep(3 * time.Second) 43 | continue 44 | } 45 | for _, p2shAddr := range p2shAddrs { 46 | findUtxosAndAggregate(p2shAddr.P2shAddress) 47 | } 48 | if len(p2shAddrs) < utxoPageLimit { 49 | break 50 | } 51 | aggOffset += utxoPageLimit 52 | } 53 | } 54 | 55 | func findUtxosAndAggregate(addr string) { 56 | findUtxos, _ := btc.BridgeInstance.FindUtxos(addr) 57 | for _, utxo := range findUtxos { 58 | if utxo.Value == nil || *utxo.Value == 0 { 59 | continue 60 | } 61 | logWorker("aggregate", "find utxo", "address", addr, "utxo", utxo.String()) 62 | 63 | aggSumVal += *utxo.Value 64 | aggAddrs = append(aggAddrs, addr) 65 | aggUtxos = append(aggUtxos, utxo) 66 | 67 | if shouldAggregate() { 68 | aggregate() 69 | } 70 | } 71 | } 72 | 73 | func shouldAggregate() bool { 74 | if len(aggUtxos) >= tokens.BtcUtxoAggregateMinCount { 75 | return true 76 | } 77 | if aggSumVal >= tokens.BtcUtxoAggregateMinValue { 78 | return true 79 | } 80 | return false 81 | } 82 | 83 | func aggregate() { 84 | txHash, err := btc.BridgeInstance.AggregateUtxos(aggAddrs, aggUtxos) 85 | if err != nil { 86 | logWorkerError("aggregate", "AggregateUtxos failed", err) 87 | } else { 88 | logWorker("aggregate", "AggregateUtxos succeed", "txHash", txHash, "utxos", len(aggUtxos), "sumVal", aggSumVal) 89 | } 90 | aggSumVal = 0 91 | aggAddrs = nil 92 | aggUtxos = nil 93 | } 94 | -------------------------------------------------------------------------------- /worker/common.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "github.com/fsn-dev/crossChain-Bridge/mongodb" 5 | "github.com/fsn-dev/crossChain-Bridge/tokens" 6 | ) 7 | 8 | // MatchTx struct 9 | type MatchTx struct { 10 | SwapTx string 11 | SwapHeight uint64 12 | SwapTime uint64 13 | SwapValue string 14 | SwapType tokens.SwapType 15 | } 16 | 17 | func addInitialSwapinResult(tx *tokens.TxSwapInfo, status mongodb.SwapStatus) error { 18 | return addInitialSwapResult(tx, status, true) 19 | } 20 | 21 | func addInitialSwapoutResult(tx *tokens.TxSwapInfo, status mongodb.SwapStatus) error { 22 | return addInitialSwapResult(tx, status, false) 23 | } 24 | 25 | func addInitialSwapResult(tx *tokens.TxSwapInfo, status mongodb.SwapStatus, isSwapin bool) (err error) { 26 | txid := tx.Hash 27 | var swapType tokens.SwapType 28 | if isSwapin { 29 | swapType = tokens.SwapinType 30 | } else { 31 | swapType = tokens.SwapoutType 32 | } 33 | swapResult := &mongodb.MgoSwapResult{ 34 | Key: txid, 35 | TxID: txid, 36 | TxHeight: tx.Height, 37 | TxTime: tx.Timestamp, 38 | From: tx.From, 39 | To: tx.To, 40 | Bind: tx.Bind, 41 | Value: tx.Value.String(), 42 | SwapTx: "", 43 | SwapHeight: 0, 44 | SwapTime: 0, 45 | SwapValue: "0", 46 | SwapType: uint32(swapType), 47 | Status: status, 48 | Timestamp: now(), 49 | Memo: "", 50 | } 51 | if isSwapin { 52 | err = mongodb.AddSwapinResult(swapResult) 53 | } else { 54 | err = mongodb.AddSwapoutResult(swapResult) 55 | } 56 | if err != nil { 57 | logWorkerError("add", "addInitialSwapResult", err, "txid", txid) 58 | } else { 59 | logWorker("add", "addInitialSwapResult", "txid", txid) 60 | } 61 | return err 62 | } 63 | 64 | func updateSwapinResult(key string, mtx *MatchTx) error { 65 | return updateSwapResult(key, mtx) 66 | } 67 | 68 | func updateSwapoutResult(key string, mtx *MatchTx) error { 69 | return updateSwapResult(key, mtx) 70 | } 71 | 72 | func updateSwapResult(key string, mtx *MatchTx) (err error) { 73 | updates := &mongodb.SwapResultUpdateItems{ 74 | Status: mongodb.MatchTxNotStable, 75 | Timestamp: now(), 76 | } 77 | if mtx.SwapTx != "" { 78 | updates.SwapTx = mtx.SwapTx 79 | updates.SwapValue = mtx.SwapValue 80 | updates.SwapHeight = 0 81 | updates.SwapTime = 0 82 | } else { 83 | updates.SwapHeight = mtx.SwapHeight 84 | updates.SwapTime = mtx.SwapTime 85 | } 86 | switch mtx.SwapType { 87 | case tokens.SwapRecallType: 88 | updates.SwapType = uint32(mtx.SwapType) 89 | fallthrough 90 | case tokens.SwapinType: 91 | err = mongodb.UpdateSwapinResult(key, updates) 92 | case tokens.SwapoutType: 93 | err = mongodb.UpdateSwapoutResult(key, updates) 94 | default: 95 | err = tokens.ErrUnknownSwapType 96 | } 97 | if err != nil { 98 | logWorkerError("update", "updateSwapResult", err, "txid", key, "swaptx", mtx.SwapTx, "swapheight", mtx.SwapHeight, "swaptime", mtx.SwapTime, "swapvalue", mtx.SwapValue, "swaptype", mtx.SwapType) 99 | } else { 100 | logWorker("update", "updateSwapResult", "txid", key, "swaptx", mtx.SwapTx, "swapheight", mtx.SwapHeight, "swaptime", mtx.SwapTime, "swapvalue", mtx.SwapValue, "swaptype", mtx.SwapType) 101 | } 102 | return err 103 | } 104 | 105 | func markSwapinResultStable(key string) error { 106 | return markSwapResultStable(key, true) 107 | } 108 | 109 | func markSwapoutResultStable(key string) error { 110 | return markSwapResultStable(key, false) 111 | } 112 | 113 | func markSwapResultStable(key string, isSwapin bool) (err error) { 114 | status := mongodb.MatchTxStable 115 | timestamp := now() 116 | memo := "" // unchange 117 | if isSwapin { 118 | err = mongodb.UpdateSwapinResultStatus(key, status, timestamp, memo) 119 | } else { 120 | err = mongodb.UpdateSwapoutResultStatus(key, status, timestamp, memo) 121 | } 122 | if err != nil { 123 | logWorkerError("stable", "markSwapResultStable", err, "txid", key, "isSwapin", isSwapin) 124 | } else { 125 | logWorker("stable", "markSwapResultStable", "txid", key, "isSwapin", isSwapin) 126 | } 127 | return err 128 | } 129 | -------------------------------------------------------------------------------- /worker/recall.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | "time" 7 | 8 | "github.com/fsn-dev/crossChain-Bridge/common" 9 | "github.com/fsn-dev/crossChain-Bridge/mongodb" 10 | "github.com/fsn-dev/crossChain-Bridge/tokens" 11 | ) 12 | 13 | var ( 14 | swapinRecallStarter sync.Once 15 | ) 16 | 17 | // StartRecallJob recall job 18 | func StartRecallJob() { 19 | go startSwapinRecallJob() 20 | } 21 | 22 | func startSwapinRecallJob() { 23 | swapinRecallStarter.Do(func() { 24 | logWorker("recall", "start swapin recall job") 25 | for { 26 | res, err := findSwapinsToRecall() 27 | if err != nil { 28 | logWorkerError("recall", "find recalls error", err) 29 | } 30 | if len(res) > 0 { 31 | logWorker("recall", "find recalls to recall", "count", len(res)) 32 | } 33 | for _, swap := range res { 34 | err = processRecallSwapin(swap) 35 | if err != nil { 36 | logWorkerError("recall", "process recall error", err, "txid", swap.TxID) 37 | } 38 | } 39 | restInJob(restIntervalInRecallJob) 40 | } 41 | }) 42 | } 43 | 44 | func findSwapinsToRecall() ([]*mongodb.MgoSwap, error) { 45 | status := mongodb.TxToBeRecall 46 | septime := getSepTimeInFind(maxRecallLifetime) 47 | return mongodb.FindSwapinsWithStatus(status, septime) 48 | } 49 | 50 | func processRecallSwapin(swap *mongodb.MgoSwap) (err error) { 51 | txid := swap.TxID 52 | res, err := mongodb.FindSwapinResult(txid) 53 | if err != nil { 54 | return err 55 | } 56 | if res.SwapTx != "" { 57 | if res.Status == mongodb.TxToBeRecall { 58 | _ = mongodb.UpdateSwapinStatus(txid, mongodb.TxProcessed, now(), "") 59 | } 60 | return fmt.Errorf("%v already swapped to %v", txid, res.SwapTx) 61 | } 62 | 63 | value, err := common.GetBigIntFromStr(res.Value) 64 | if err != nil { 65 | return fmt.Errorf("wrong value %v", res.Value) 66 | } 67 | 68 | args := &tokens.BuildTxArgs{ 69 | SwapInfo: tokens.SwapInfo{ 70 | SwapID: res.TxID, 71 | SwapType: tokens.SwapRecallType, 72 | TxType: tokens.SwapTxType(swap.TxType), 73 | Bind: swap.Bind, 74 | }, 75 | To: res.Bind, 76 | Value: value, 77 | Memo: fmt.Sprintf("%s%s", tokens.RecallMemoPrefix, res.TxID), 78 | } 79 | bridge := tokens.SrcBridge 80 | rawTx, err := bridge.BuildRawTransaction(args) 81 | if err != nil { 82 | logWorkerError("recall", "BuildRawTransaction failed", err, "txid", txid) 83 | return err 84 | } 85 | 86 | signedTx, txHash, err := bridge.DcrmSignTransaction(rawTx, args.GetExtraArgs()) 87 | if err != nil { 88 | logWorkerError("recall", "DcrmSignTransaction failed", err, "txid", txid) 89 | return err 90 | } 91 | 92 | // update database before sending transaction 93 | matchTx := &MatchTx{ 94 | SwapTx: txHash, 95 | SwapValue: tokens.CalcSwappedValue(value, false).String(), 96 | SwapType: tokens.SwapRecallType, 97 | } 98 | err = updateSwapinResult(txid, matchTx) 99 | if err != nil { 100 | return err 101 | } 102 | err = mongodb.UpdateSwapinStatus(txid, mongodb.TxProcessed, now(), "") 103 | if err != nil { 104 | return err 105 | } 106 | 107 | for i := 0; i < retrySendTxCount; i++ { 108 | if _, err = bridge.SendTransaction(signedTx); err == nil { 109 | if tx, _ := bridge.GetTransaction(txHash); tx != nil { 110 | break 111 | } 112 | } 113 | time.Sleep(retrySendTxInterval) 114 | } 115 | if err != nil { 116 | _ = mongodb.UpdateSwapinStatus(txid, mongodb.TxRecallFailed, now(), err.Error()) 117 | _ = mongodb.UpdateSwapinResultStatus(txid, mongodb.TxRecallFailed, now(), err.Error()) 118 | return err 119 | } 120 | return nil 121 | } 122 | -------------------------------------------------------------------------------- /worker/scan.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "github.com/fsn-dev/crossChain-Bridge/tokens" 5 | ) 6 | 7 | // StartScanJob scan job 8 | func StartScanJob(isServer bool) { 9 | go tokens.SrcBridge.StartPoolTransactionScanJob() 10 | go tokens.SrcBridge.StartChainTransactionScanJob() 11 | go tokens.SrcBridge.StartSwapHistoryScanJob() 12 | 13 | go tokens.DstBridge.StartPoolTransactionScanJob() 14 | go tokens.DstBridge.StartChainTransactionScanJob() 15 | go tokens.DstBridge.StartSwapHistoryScanJob() 16 | } 17 | -------------------------------------------------------------------------------- /worker/stable.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/fsn-dev/crossChain-Bridge/mongodb" 8 | "github.com/fsn-dev/crossChain-Bridge/tokens" 9 | ) 10 | 11 | var ( 12 | swapinStableStarter sync.Once 13 | swapoutStableStarter sync.Once 14 | ) 15 | 16 | // StartStableJob stable job 17 | func StartStableJob() { 18 | go startSwapinStableJob() 19 | go startSwapoutStableJob() 20 | } 21 | 22 | func startSwapinStableJob() { 23 | swapinStableStarter.Do(func() { 24 | logWorker("stable", "start update swapin stable job") 25 | for { 26 | res, err := findSwapinResultsToStable() 27 | if err != nil { 28 | logWorkerError("stable", "find swapin results error", err) 29 | } 30 | if len(res) > 0 { 31 | logWorker("stable", "find swapin results to stable", "count", len(res)) 32 | } 33 | for _, swap := range res { 34 | err = processSwapinStable(swap) 35 | if err != nil { 36 | logWorkerError("stable", "process swapin stable error", err) 37 | } 38 | } 39 | restInJob(restIntervalInStableJob) 40 | } 41 | }) 42 | } 43 | 44 | func startSwapoutStableJob() { 45 | swapoutStableStarter.Do(func() { 46 | logWorker("stable", "start update swapout stable job") 47 | for { 48 | res, err := findSwapoutResultsToStable() 49 | if err != nil { 50 | logWorkerError("stable", "find swapout results error", err) 51 | } 52 | if len(res) > 0 { 53 | logWorker("stable", "find swapout results to stable", "count", len(res)) 54 | } 55 | for _, swap := range res { 56 | err = processSwapoutStable(swap) 57 | if err != nil { 58 | logWorkerError("recall", "process swapout stable error", err) 59 | } 60 | } 61 | restInJob(restIntervalInStableJob) 62 | } 63 | }) 64 | } 65 | 66 | func findSwapinResultsToStable() ([]*mongodb.MgoSwapResult, error) { 67 | status := mongodb.MatchTxNotStable 68 | septime := getSepTimeInFind(maxStableLifetime) 69 | return mongodb.FindSwapinResultsWithStatus(status, septime) 70 | } 71 | 72 | func findSwapoutResultsToStable() ([]*mongodb.MgoSwapResult, error) { 73 | status := mongodb.MatchTxNotStable 74 | septime := getSepTimeInFind(maxStableLifetime) 75 | return mongodb.FindSwapoutResultsWithStatus(status, septime) 76 | } 77 | 78 | func processSwapinStable(swap *mongodb.MgoSwapResult) error { 79 | swapTxID := swap.SwapTx 80 | logWorker("stable", "start processSwapinStable", "swaptxid", swapTxID, "status", swap.Status) 81 | var ( 82 | txStatus *tokens.TxStatus 83 | confirmations uint64 84 | ) 85 | if swap.SwapType == uint32(tokens.SwapRecallType) { 86 | txStatus = tokens.SrcBridge.GetTransactionStatus(swapTxID) 87 | token, _ := tokens.SrcBridge.GetTokenAndGateway() 88 | confirmations = *token.Confirmations 89 | } else { 90 | txStatus = tokens.DstBridge.GetTransactionStatus(swapTxID) 91 | token, _ := tokens.DstBridge.GetTokenAndGateway() 92 | confirmations = *token.Confirmations 93 | } 94 | 95 | if txStatus == nil { 96 | return fmt.Errorf("[processSwapinStable] tx status is empty, swapTxID=%v", swapTxID) 97 | } 98 | 99 | if txStatus.BlockHeight == 0 { 100 | return nil 101 | } 102 | 103 | if swap.SwapHeight != 0 { 104 | if txStatus.Confirmations >= confirmations { 105 | return markSwapinResultStable(swap.Key) 106 | } 107 | return nil 108 | } 109 | 110 | matchTx := &MatchTx{ 111 | SwapHeight: txStatus.BlockHeight, 112 | SwapTime: txStatus.BlockTime, 113 | SwapType: tokens.SwapinType, 114 | } 115 | return updateSwapinResult(swap.Key, matchTx) 116 | } 117 | 118 | func processSwapoutStable(swap *mongodb.MgoSwapResult) (err error) { 119 | swapTxID := swap.SwapTx 120 | logWorker("stable", "start processSwapoutStable", "swaptxid", swapTxID, "status", swap.Status) 121 | 122 | var txStatus *tokens.TxStatus 123 | var confirmations uint64 124 | 125 | txStatus = tokens.SrcBridge.GetTransactionStatus(swapTxID) 126 | token, _ := tokens.SrcBridge.GetTokenAndGateway() 127 | confirmations = *token.Confirmations 128 | 129 | if txStatus == nil { 130 | return fmt.Errorf("[processSwapoutStable] tx status is empty, swapTxID=%v", swapTxID) 131 | } 132 | 133 | if txStatus.BlockHeight == 0 { 134 | return nil 135 | } 136 | 137 | if swap.SwapHeight != 0 { 138 | if txStatus.Confirmations >= confirmations { 139 | return markSwapoutResultStable(swap.Key) 140 | } 141 | return nil 142 | } 143 | 144 | matchTx := &MatchTx{ 145 | SwapHeight: txStatus.BlockHeight, 146 | SwapTime: txStatus.BlockTime, 147 | SwapType: tokens.SwapoutType, 148 | } 149 | return updateSwapoutResult(swap.Key, matchTx) 150 | } 151 | -------------------------------------------------------------------------------- /worker/updatelatest.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | 7 | "github.com/fsn-dev/crossChain-Bridge/tokens" 8 | ) 9 | 10 | var ( 11 | updateLatestBlockHeightStarter sync.Once 12 | updateLatestBlockHeightInterval = 5 * time.Second 13 | ) 14 | 15 | // StartUpdateLatestBlockHeightJob update latest block height job 16 | func StartUpdateLatestBlockHeightJob() { 17 | updateLatestBlockHeightStarter.Do(func() { 18 | logWorker("updatelatest", "start update latest block height job") 19 | for { 20 | updateSrcLatestBlockHeight() 21 | updateDstLatestBlockHeight() 22 | time.Sleep(updateLatestBlockHeightInterval) 23 | } 24 | }) 25 | } 26 | 27 | func updateSrcLatestBlockHeight() { 28 | srcLatest, err := tokens.SrcBridge.GetLatestBlockNumber() 29 | if err != nil { 30 | logWorkerError("updatelatest", "get src latest block number error", err) 31 | return 32 | } 33 | if tokens.SrcLatestBlockHeight != srcLatest { 34 | tokens.SrcLatestBlockHeight = srcLatest 35 | logWorker("updatelatest", "update src latest block number", "latest", srcLatest) 36 | } 37 | } 38 | 39 | func updateDstLatestBlockHeight() { 40 | dstLatest, err := tokens.DstBridge.GetLatestBlockNumber() 41 | if err != nil { 42 | logWorkerError("updatelatest", "get dest latest block number error", err) 43 | return 44 | } 45 | if tokens.DstLatestBlockHeight != dstLatest { 46 | tokens.DstLatestBlockHeight = dstLatest 47 | logWorker("updatelatest", "update dest latest block number", "latest", dstLatest) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /worker/utils.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/fsn-dev/crossChain-Bridge/log" 7 | ) 8 | 9 | var ( 10 | maxRecallLifetime = int64(10 * 24 * 3600) 11 | restIntervalInRecallJob = 3 * time.Second 12 | 13 | maxVerifyLifetime = int64(7 * 24 * 3600) 14 | restIntervalInVerifyJob = 3 * time.Second 15 | 16 | maxDoSwapLifetime = int64(7 * 24 * 3600) 17 | restIntervalInDoSwapJob = 3 * time.Second 18 | 19 | maxStableLifetime = int64(7 * 24 * 3600) 20 | restIntervalInStableJob = 3 * time.Second 21 | 22 | retrySendTxCount = 3 23 | retrySendTxInterval = 1 * time.Second 24 | ) 25 | 26 | func now() int64 { 27 | return time.Now().Unix() 28 | } 29 | 30 | func logWorker(job, subject string, context ...interface{}) { 31 | log.Info("["+job+"] "+subject, context...) 32 | } 33 | 34 | func logWorkerError(job, subject string, err error, context ...interface{}) { 35 | fields := []interface{}{"err", err} 36 | fields = append(fields, context...) 37 | log.Error("["+job+"] "+subject, fields...) 38 | } 39 | 40 | func logWorkerTrace(job, subject string, context ...interface{}) { 41 | log.Trace("["+job+"] "+subject, context...) 42 | } 43 | 44 | func getSepTimeInFind(dist int64) int64 { 45 | return now() - dist 46 | } 47 | 48 | func restInJob(duration time.Duration) { 49 | time.Sleep(duration) 50 | } 51 | -------------------------------------------------------------------------------- /worker/verify.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/fsn-dev/crossChain-Bridge/mongodb" 7 | "github.com/fsn-dev/crossChain-Bridge/tokens" 8 | "github.com/fsn-dev/crossChain-Bridge/tokens/btc" 9 | ) 10 | 11 | var ( 12 | swapinVerifyStarter sync.Once 13 | swapoutVerifyStarter sync.Once 14 | ) 15 | 16 | // StartVerifyJob verify job 17 | func StartVerifyJob() { 18 | go startSwapinVerifyJob() 19 | go startSwapoutVerifyJob() 20 | } 21 | 22 | func startSwapinVerifyJob() { 23 | swapinVerifyStarter.Do(func() { 24 | logWorker("verify", "start swapin verify job") 25 | for { 26 | res, err := findSwapinsToVerify() 27 | if err != nil { 28 | logWorkerError("verify", "find swapins error", err) 29 | } 30 | if len(res) > 0 { 31 | logWorker("verify", "find swapins to verify", "count", len(res)) 32 | } 33 | for _, swap := range res { 34 | err = processSwapinVerify(swap) 35 | switch err { 36 | case nil, tokens.ErrTxNotStable, tokens.ErrTxNotFound: 37 | default: 38 | logWorkerError("verify", "process swapin verify error", err, "txid", swap.TxID) 39 | } 40 | } 41 | restInJob(restIntervalInVerifyJob) 42 | } 43 | }) 44 | } 45 | 46 | func startSwapoutVerifyJob() { 47 | swapoutVerifyStarter.Do(func() { 48 | logWorker("verify", "start swapout verify job") 49 | for { 50 | res, err := findSwapoutsToVerify() 51 | if err != nil { 52 | logWorkerError("verify", "find swapouts error", err) 53 | } 54 | if len(res) > 0 { 55 | logWorker("verify", "find swapouts to verify", "count", len(res)) 56 | } 57 | for _, swap := range res { 58 | err = processSwapoutVerify(swap) 59 | switch err { 60 | case nil, tokens.ErrTxNotStable, tokens.ErrTxNotFound: 61 | default: 62 | logWorkerError("verify", "process swapout verify error", err, "txid", swap.TxID) 63 | } 64 | } 65 | restInJob(restIntervalInVerifyJob) 66 | } 67 | }) 68 | } 69 | 70 | func findSwapinsToVerify() ([]*mongodb.MgoSwap, error) { 71 | status := mongodb.TxNotStable 72 | septime := getSepTimeInFind(maxVerifyLifetime) 73 | return mongodb.FindSwapinsWithStatus(status, septime) 74 | } 75 | 76 | func findSwapoutsToVerify() ([]*mongodb.MgoSwap, error) { 77 | status := mongodb.TxNotStable 78 | septime := getSepTimeInFind(maxVerifyLifetime) 79 | return mongodb.FindSwapoutsWithStatus(status, septime) 80 | } 81 | 82 | func processSwapinVerify(swap *mongodb.MgoSwap) (err error) { 83 | txid := swap.TxID 84 | var swapInfo *tokens.TxSwapInfo 85 | switch tokens.SwapTxType(swap.TxType) { 86 | case tokens.SwapinTx: 87 | swapInfo, err = tokens.SrcBridge.VerifyTransaction(txid, false) 88 | case tokens.P2shSwapinTx: 89 | if btc.BridgeInstance == nil { 90 | return tokens.ErrWrongP2shSwapin 91 | } 92 | swapInfo, err = btc.BridgeInstance.VerifyP2shTransaction(txid, swap.Bind, false) 93 | default: 94 | return tokens.ErrWrongSwapinTxType 95 | } 96 | if swapInfo.Height != 0 && 97 | swapInfo.Height < tokens.GetTokenConfig(true).InitialHeight { 98 | err = tokens.ErrTxBeforeInitialHeight 99 | } 100 | 101 | resultStatus := mongodb.MatchTxEmpty 102 | 103 | switch err { 104 | case tokens.ErrTxNotStable, tokens.ErrTxNotFound: 105 | return err 106 | case tokens.ErrTxWithWrongMemo: 107 | resultStatus = mongodb.TxWithWrongMemo 108 | err = mongodb.UpdateSwapinStatus(txid, mongodb.TxCanRecall, now(), err.Error()) 109 | case nil: 110 | err = mongodb.UpdateSwapinStatus(txid, mongodb.TxNotSwapped, now(), "") 111 | default: 112 | return mongodb.UpdateSwapinStatus(txid, mongodb.TxVerifyFailed, now(), err.Error()) 113 | } 114 | 115 | if err != nil { 116 | logWorkerError("verify", "processSwapinVerify", err, "txid", txid) 117 | return err 118 | } 119 | return addInitialSwapinResult(swapInfo, resultStatus) 120 | } 121 | 122 | func processSwapoutVerify(swap *mongodb.MgoSwap) error { 123 | txid := swap.TxID 124 | swapInfo, err := tokens.DstBridge.VerifyTransaction(txid, false) 125 | if swapInfo.Height != 0 && 126 | swapInfo.Height < tokens.GetTokenConfig(false).InitialHeight { 127 | err = tokens.ErrTxBeforeInitialHeight 128 | } 129 | 130 | resultStatus := mongodb.MatchTxEmpty 131 | 132 | switch err { 133 | case tokens.ErrTxNotStable, tokens.ErrTxNotFound: 134 | return err 135 | case tokens.ErrTxWithWrongMemo: 136 | resultStatus = mongodb.TxWithWrongMemo 137 | err = mongodb.UpdateSwapoutStatus(txid, mongodb.TxCanRecall, now(), err.Error()) 138 | case nil: 139 | err = mongodb.UpdateSwapoutStatus(txid, mongodb.TxNotSwapped, now(), "") 140 | default: 141 | return mongodb.UpdateSwapoutStatus(txid, mongodb.TxVerifyFailed, now(), err.Error()) 142 | } 143 | 144 | if err != nil { 145 | logWorkerError("verify", "processSwapoutVerify", err, "txid", txid) 146 | return err 147 | } 148 | return addInitialSwapoutResult(swapInfo, resultStatus) 149 | } 150 | -------------------------------------------------------------------------------- /worker/worker.go: -------------------------------------------------------------------------------- 1 | package worker 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/fsn-dev/crossChain-Bridge/rpc/client" 7 | "github.com/fsn-dev/crossChain-Bridge/tokens/bridge" 8 | ) 9 | 10 | const interval = 10 * time.Millisecond 11 | 12 | // StartWork start swap server work 13 | func StartWork(isServer bool) { 14 | logWorker("worker", "start server worker") 15 | 16 | client.InitHTTPClient() 17 | bridge.InitCrossChainBridge(isServer) 18 | 19 | go StartScanJob(isServer) 20 | time.Sleep(interval) 21 | 22 | if !isServer { 23 | go StartAcceptSignJob() 24 | return 25 | } 26 | 27 | go StartUpdateLatestBlockHeightJob() 28 | time.Sleep(interval) 29 | 30 | go StartVerifyJob() 31 | time.Sleep(interval) 32 | 33 | go StartSwapJob() 34 | time.Sleep(interval) 35 | 36 | go StartStableJob() 37 | time.Sleep(interval) 38 | 39 | go StartAggregateJob() 40 | } 41 | --------------------------------------------------------------------------------