diff --git a/.travis.yml b/.travis.yml index 51af0570b3..c93b57907f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -53,15 +53,36 @@ matrix: - CC=aarch64-linux-gnu-gcc go run build/ci.go install -arch arm64 - go run build/ci.go archive -arch arm64 -type tar -signer LINUX_SIGNING_KEY -upload gethstore/builds - # This builder does the OSX Azure uploads + # This builder does the OSX Azure, Android Maven and Azure and iOS CocoaPods and Azure uploads - os: osx go: 1.7 env: - azure-osx + - mobile + cache: + directories: + - $HOME/.android.platforms + - $HOME/.cocoapods script: - go run build/ci.go install - go run build/ci.go archive -type tar -signer OSX_SIGNING_KEY -upload gethstore/builds + # Build the iOS framework and upload it to CocoaPods and Azure + - gem install cocoapods --pre + - go run build/ci.go xcode -signer IOS_SIGNING_KEY -deploy trunk -upload gethstore/builds + + # Build the Android archive and upload it to Maven Central and Azure + - brew update + - brew install android-sdk maven + - export ANDROID_HOME=/usr/local/opt/android-sdk + + - mkdir -p $ANDROID_HOME/platforms + - mv -f $HOME/.android.platforms $ANDROID_HOME/platforms + - echo "y" | android update sdk --no-ui --filter platform + + - go run build/ci.go aar -signer ANDROID_SIGNING_KEY -deploy https://oss.sonatype.org -upload gethstore/builds + - mv -f $ANDROID_HOME/platforms $HOME/.android.platforms + install: - go get golang.org/x/tools/cmd/cover script: diff --git a/accounts/abi/bind/bind.go b/accounts/abi/bind/bind.go index 24fe9f7708..84cf22e3c3 100644 --- a/accounts/abi/bind/bind.go +++ b/accounts/abi/bind/bind.go @@ -32,11 +32,20 @@ import ( "golang.org/x/tools/imports" ) +// Lang is a target programming language selector to generate bindings for. +type Lang int + +const ( + LangGo Lang = iota + LangJava + LangObjC +) + // Bind generates a Go wrapper around a contract ABI. This wrapper isn't meant // to be used as is in client code, but rather as an intermediate struct which // enforces compile time type safety and naming convention opposed to having to // manually maintain hard coded strings that break on runtime. -func Bind(types []string, abis []string, bytecodes []string, pkg string) (string, error) { +func Bind(types []string, abis []string, bytecodes []string, pkg string, lang Lang) (string, error) { // Process each individual contract requested binding contracts := make(map[string]*tmplContract) @@ -62,7 +71,7 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string) (string for _, original := range evmABI.Methods { // Normalize the method for capital cases and non-anonymous inputs/outputs normalized := original - normalized.Name = capitalise(original.Name) + normalized.Name = methodNormalizer[lang](original.Name) normalized.Inputs = make([]abi.Argument, len(original.Inputs)) copy(normalized.Inputs, original.Inputs) @@ -78,7 +87,7 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string) (string normalized.Outputs[j].Name = capitalise(output.Name) } } - // Append the methos to the call or transact lists + // Append the methods to the call or transact lists if original.Const { calls[original.Name] = &tmplMethod{Original: original, Normalized: normalized, Structured: structured(original)} } else { @@ -87,7 +96,7 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string) (string } contracts[types[i]] = &tmplContract{ Type: capitalise(types[i]), - InputABI: strippedABI, + InputABI: strings.Replace(strippedABI, "\"", "\\\"", -1), InputBin: strings.TrimSpace(bytecodes[i]), Constructor: evmABI.Constructor, Calls: calls, @@ -102,24 +111,38 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string) (string buffer := new(bytes.Buffer) funcs := map[string]interface{}{ - "bindtype": bindType, + "bindtype": bindType[lang], + "namedtype": namedType[lang], + "capitalise": capitalise, + "decapitalise": decapitalise, } - tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource)) + tmpl := template.Must(template.New("").Funcs(funcs).Parse(tmplSource[lang])) if err := tmpl.Execute(buffer, data); err != nil { return "", err } - // Pass the code through goimports to clean it up and double check - code, err := imports.Process("", buffer.Bytes(), nil) - if err != nil { - return "", fmt.Errorf("%v\n%s", err, buffer) + // For Go bindings pass the code through goimports to clean it up and double check + if lang == LangGo { + code, err := imports.Process("", buffer.Bytes(), nil) + if err != nil { + return "", fmt.Errorf("%v\n%s", err, buffer) + } + return string(code), nil } - return string(code), nil + // For all others just return as is for now + return string(buffer.Bytes()), nil } -// bindType converts a Solidity type to a Go one. Since there is no clear mapping +// bindType is a set of type binders that convert Solidity types to some supported +// programming language. +var bindType = map[Lang]func(kind abi.Type) string{ + LangGo: bindTypeGo, + LangJava: bindTypeJava, +} + +// bindTypeGo converts a Solidity type to a Go one. Since there is no clear mapping // from all Solidity types to Go ones (e.g. uint17), those that cannot be exactly // mapped will use an upscaled type (e.g. *big.Int). -func bindType(kind abi.Type) string { +func bindTypeGo(kind abi.Type) string { stringKind := kind.String() switch { @@ -160,11 +183,137 @@ func bindType(kind abi.Type) string { } } +// bindTypeJava converts a Solidity type to a Java one. Since there is no clear mapping +// from all Solidity types to Java ones (e.g. uint17), those that cannot be exactly +// mapped will use an upscaled type (e.g. BigDecimal). +func bindTypeJava(kind abi.Type) string { + stringKind := kind.String() + + switch { + case strings.HasPrefix(stringKind, "address"): + parts := regexp.MustCompile("address(\\[[0-9]*\\])?").FindStringSubmatch(stringKind) + if len(parts) != 2 { + return stringKind + } + if parts[1] == "" { + return fmt.Sprintf("Address") + } + return fmt.Sprintf("Addresses") + + case strings.HasPrefix(stringKind, "bytes"): + parts := regexp.MustCompile("bytes([0-9]*)(\\[[0-9]*\\])?").FindStringSubmatch(stringKind) + if len(parts) != 3 { + return stringKind + } + if parts[2] != "" { + return "byte[][]" + } + return "byte[]" + + case strings.HasPrefix(stringKind, "int") || strings.HasPrefix(stringKind, "uint"): + parts := regexp.MustCompile("(u)?int([0-9]*)(\\[[0-9]*\\])?").FindStringSubmatch(stringKind) + if len(parts) != 4 { + return stringKind + } + switch parts[2] { + case "8", "16", "32", "64": + if parts[1] == "" { + if parts[3] == "" { + return fmt.Sprintf("int%s", parts[2]) + } + return fmt.Sprintf("int%s[]", parts[2]) + } + } + if parts[3] == "" { + return fmt.Sprintf("BigInt") + } + return fmt.Sprintf("BigInts") + + case strings.HasPrefix(stringKind, "bool"): + parts := regexp.MustCompile("bool(\\[[0-9]*\\])?").FindStringSubmatch(stringKind) + if len(parts) != 2 { + return stringKind + } + if parts[1] == "" { + return fmt.Sprintf("bool") + } + return fmt.Sprintf("bool[]") + + case strings.HasPrefix(stringKind, "string"): + parts := regexp.MustCompile("string(\\[[0-9]*\\])?").FindStringSubmatch(stringKind) + if len(parts) != 2 { + return stringKind + } + if parts[1] == "" { + return fmt.Sprintf("String") + } + return fmt.Sprintf("String[]") + + default: + return stringKind + } +} + +// namedType is a set of functions that transform language specific types to +// named versions that my be used inside method names. +var namedType = map[Lang]func(string, abi.Type) string{ + LangGo: func(string, abi.Type) string { panic("this shouldn't be needed") }, + LangJava: namedTypeJava, +} + +// namedTypeJava converts some primitive data types to named variants that can +// be used as parts of method names. +func namedTypeJava(javaKind string, solKind abi.Type) string { + switch javaKind { + case "byte[]": + return "Binary" + case "byte[][]": + return "Binaries" + case "string": + return "String" + case "string[]": + return "Strings" + case "bool": + return "Bool" + case "bool[]": + return "Bools" + case "BigInt": + parts := regexp.MustCompile("(u)?int([0-9]*)(\\[[0-9]*\\])?").FindStringSubmatch(solKind.String()) + if len(parts) != 4 { + return javaKind + } + switch parts[2] { + case "8", "16", "32", "64": + if parts[3] == "" { + return capitalise(fmt.Sprintf("%sint%s", parts[1], parts[2])) + } + return capitalise(fmt.Sprintf("%sint%ss", parts[1], parts[2])) + + default: + return javaKind + } + default: + return javaKind + } +} + +// methodNormalizer is a name transformer that modifies Solidity method names to +// conform to target language naming concentions. +var methodNormalizer = map[Lang]func(string) string{ + LangGo: capitalise, + LangJava: decapitalise, +} + // capitalise makes the first character of a string upper case. func capitalise(input string) string { return strings.ToUpper(input[:1]) + input[1:] } +// decapitalise makes the first character of a string lower case. +func decapitalise(input string) string { + return strings.ToLower(input[:1]) + input[1:] +} + // structured checks whether a method has enough information to return a proper // Go struct ot if flat returns are needed. func structured(method abi.Method) bool { diff --git a/accounts/abi/bind/bind_test.go b/accounts/abi/bind/bind_test.go index 67c09c3ada..6ebc8ea0a3 100644 --- a/accounts/abi/bind/bind_test.go +++ b/accounts/abi/bind/bind_test.go @@ -398,7 +398,7 @@ func TestBindings(t *testing.T) { // Generate the test suite for all the contracts for i, tt := range bindTests { // Generate the binding and create a Go source file in the workspace - bind, err := Bind([]string{tt.name}, []string{tt.abi}, []string{tt.bytecode}, "bindtest") + bind, err := Bind([]string{tt.name}, []string{tt.abi}, []string{tt.bytecode}, "bindtest", LangGo) if err != nil { t.Fatalf("test %d: failed to generate binding: %v", i, err) } diff --git a/accounts/abi/bind/template.go b/accounts/abi/bind/template.go index 523122213f..64dd598c0b 100644 --- a/accounts/abi/bind/template.go +++ b/accounts/abi/bind/template.go @@ -42,9 +42,16 @@ type tmplMethod struct { Structured bool // Whether the returns should be accumulated into a contract } -// tmplSource is the Go source template use to generate the contract binding +// tmplSource is language to template mapping containing all the supported +// programming languages the package can generate to. +var tmplSource = map[Lang]string{ + LangGo: tmplSourceGo, + LangJava: tmplSourceJava, +} + +// tmplSourceGo is the Go source template use to generate the contract binding // based on. -const tmplSource = ` +const tmplSourceGo = ` // This file is an automatically generated Go binding. Do not modify as any // change will likely be lost upon the next re-generation! @@ -52,7 +59,7 @@ package {{.Package}} {{range $contract := .Contracts}} // {{.Type}}ABI is the input ABI used to generate the binding from. - const {{.Type}}ABI = ` + "`" + `{{.InputABI}}` + "`" + ` + const {{.Type}}ABI = "{{.InputABI}}" {{if .InputBin}} // {{.Type}}Bin is the compiled bytecode used for deploying new contracts. @@ -258,3 +265,105 @@ package {{.Package}} {{end}} {{end}} ` + +// tmplSourceJava is the Java source template use to generate the contract binding +// based on. +const tmplSourceJava = ` +// This file is an automatically generated Java binding. Do not modify as any +// change will likely be lost upon the next re-generation! + +package {{.Package}}; + +import org.ethereum.geth.*; +import org.ethereum.geth.internal.*; + +{{range $contract := .Contracts}} + public class {{.Type}} { + // ABI is the input ABI used to generate the binding from. + public final static String ABI = "{{.InputABI}}"; + + {{if .InputBin}} + // BYTECODE is the compiled bytecode used for deploying new contracts. + public final static byte[] BYTECODE = "{{.InputBin}}".getBytes(); + + // deploy deploys a new Ethereum contract, binding an instance of {{.Type}} to it. + public static {{.Type}} deploy(TransactOpts auth, EthereumClient client{{range .Constructor.Inputs}}, {{bindtype .Type}} {{.Name}}{{end}}) throws Exception { + Interfaces args = Geth.newInterfaces({{(len .Constructor.Inputs)}}); + {{range $index, $element := .Constructor.Inputs}} + args.set({{$index}}, Geth.newInterface()); args.get({{$index}}).set{{namedtype (bindtype .Type) .Type}}({{.Name}}); + {{end}} + return new {{.Type}}(Geth.deployContract(auth, ABI, BYTECODE, client, args)); + } + + // Internal constructor used by contract deployment. + private {{.Type}}(BoundContract deployment) { + this.Address = deployment.getAddress(); + this.Deployer = deployment.getDeployer(); + this.Contract = deployment; + } + {{end}} + + // Ethereum address where this contract is located at. + public final Address Address; + + // Ethereum transaction in which this contract was deployed (if known!). + public final Transaction Deployer; + + // Contract instance bound to a blockchain address. + private final BoundContract Contract; + + // Creates a new instance of {{.Type}}, bound to a specific deployed contract. + public {{.Type}}(Address address, EthereumClient client) throws Exception { + this(Geth.bindContract(address, ABI, client)); + } + + {{range .Calls}} + {{if gt (len .Normalized.Outputs) 1}} + // {{capitalise .Normalized.Name}}Results is the output of a call to {{.Normalized.Name}}. + public class {{capitalise .Normalized.Name}}Results { + {{range $index, $item := .Normalized.Outputs}}public {{bindtype .Type}} {{if ne .Name ""}}{{.Name}}{{else}}Return{{$index}}{{end}}; + {{end}} + } + {{end}} + + // {{.Normalized.Name}} is a free data retrieval call binding the contract method 0x{{printf "%x" .Original.Id}}. + // + // Solidity: {{.Original.String}} + public {{if gt (len .Normalized.Outputs) 1}}{{capitalise .Normalized.Name}}Results{{else}}{{range .Normalized.Outputs}}{{bindtype .Type}}{{end}}{{end}} {{.Normalized.Name}}(CallOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type}} {{.Name}}{{end}}) throws Exception { + Interfaces args = Geth.newInterfaces({{(len .Normalized.Inputs)}}); + {{range $index, $item := .Normalized.Inputs}}args.set({{$index}}, Geth.newInterface()); args.get({{$index}}).set{{namedtype (bindtype .Type) .Type}}({{.Name}}); + {{end}} + + Interfaces results = Geth.newInterfaces({{(len .Normalized.Outputs)}}); + {{range $index, $item := .Normalized.Outputs}}Interface result{{$index}} = Geth.newInterface(); result{{$index}}.setDefault{{namedtype (bindtype .Type) .Type}}(); results.set({{$index}}, result{{$index}}); + {{end}} + + if (opts == null) { + opts = Geth.newCallOpts(); + } + this.Contract.call(opts, results, "{{.Original.Name}}", args); + {{if gt (len .Normalized.Outputs) 1}} + {{capitalise .Normalized.Name}}Results result = new {{capitalise .Normalized.Name}}Results(); + {{range $index, $item := .Normalized.Outputs}}result.{{if ne .Name ""}}{{.Name}}{{else}}Return{{$index}}{{end}} = results.get({{$index}}).get{{namedtype (bindtype .Type) .Type}}(); + {{end}} + return result; + {{else}}{{range .Normalized.Outputs}}return results.get(0).get{{namedtype (bindtype .Type) .Type}}();{{end}} + {{end}} + } + {{end}} + + {{range .Transacts}} + // {{.Normalized.Name}} is a paid mutator transaction binding the contract method 0x{{printf "%x" .Original.Id}}. + // + // Solidity: {{.Original.String}} + public Transaction {{.Normalized.Name}}(TransactOpts opts{{range .Normalized.Inputs}}, {{bindtype .Type}} {{.Name}}{{end}}) throws Exception { + Interfaces args = Geth.newInterfaces({{(len .Normalized.Inputs)}}); + {{range $index, $item := .Normalized.Inputs}}args.set({{$index}}, Geth.newInterface()); args.get({{$index}}).set{{namedtype (bindtype .Type) .Type}}({{.Name}}); + {{end}} + + return this.Contract.transact(opts, "{{.Original.Name}}" , args); + } + {{end}} + } +{{end}} +` diff --git a/accounts/key_store_passphrase.go b/accounts/key_store_passphrase.go index 3a5155e13f..4a777956d6 100644 --- a/accounts/key_store_passphrase.go +++ b/accounts/key_store_passphrase.go @@ -46,12 +46,20 @@ import ( const ( keyHeaderKDF = "scrypt" - // n,r,p = 2^18, 8, 1 uses 256MB memory and approx 1s CPU time on a modern CPU. + // StandardScryptN is the N parameter of Scrypt encryption algorithm, using 256MB + // memory and taking approximately 1s CPU time on a modern processor. StandardScryptN = 1 << 18 + + // StandardScryptP is the P parameter of Scrypt encryption algorithm, using 256MB + // memory and taking approximately 1s CPU time on a modern processor. StandardScryptP = 1 - // n,r,p = 2^12, 8, 6 uses 4MB memory and approx 100ms CPU time on a modern CPU. + // LightScryptN is the N parameter of Scrypt encryption algorithm, using 4MB + // memory and taking approximately 100ms CPU time on a modern processor. LightScryptN = 1 << 12 + + // LightScryptP is the P parameter of Scrypt encryption algorithm, using 4MB + // memory and taking approximately 100ms CPU time on a modern processor. LightScryptP = 6 scryptR = 8 diff --git a/build/ci.go b/build/ci.go index 691f5233ed..e8e08268be 100644 --- a/build/ci.go +++ b/build/ci.go @@ -29,6 +29,8 @@ Available commands are: importkeys -- imports signing keys from env debsrc [ -signer key-id ] [ -upload dest ] -- creates a debian source package nsis -- creates a Windows NSIS installer + aar [ -sign key-id ] [-deploy repo] [ -upload dest ] -- creates an Android archive + xcode [ -sign key-id ] [-deploy repo] [ -upload dest ] -- creates an iOS XCode framework xgo [ options ] -- cross builds according to options For all commands, -n prevents execution of external programs (dry run mode). @@ -37,6 +39,7 @@ For all commands, -n prevents execution of external programs (dry run mode). package main import ( + "bufio" "bytes" "encoding/base64" "flag" @@ -48,6 +51,7 @@ import ( "os" "os/exec" "path/filepath" + "regexp" "runtime" "strings" "time" @@ -125,6 +129,10 @@ func main() { doDebianSource(os.Args[2:]) case "nsis": doWindowsInstaller(os.Args[2:]) + case "aar": + doAndroidArchive(os.Args[2:]) + case "xcode": + doXCodeFramework(os.Args[2:]) case "xgo": doXgo(os.Args[2:]) default: @@ -331,14 +339,24 @@ func archiveBasename(arch string, env build.Environment) string { if arch == "arm" { platform += os.Getenv("GOARM") } - archive := platform + "-" + build.VERSION() + if arch == "android" { + platform = "android-all" + } + if arch == "ios" { + platform = "ios-all" + } + return platform + "-" + archiveVersion(env) +} + +func archiveVersion(env build.Environment) string { + version := build.VERSION() if isUnstableBuild(env) { - archive += "-unstable" + version += "-unstable" } if env.Commit != "" { - archive += "-" + env.Commit[:8] + version += "-" + env.Commit[:8] } - return archive + return version } func archiveUpload(archive string, blobstore string, signer string) error { @@ -632,6 +650,204 @@ func doWindowsInstaller(cmdline []string) { } } +// Android archives + +func doAndroidArchive(cmdline []string) { + var ( + signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. ANDROID_SIGNING_KEY)`) + deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "https://oss.sonatype.org")`) + upload = flag.String("upload", "", `Destination to upload the archive (usually "gethstore/builds")`) + ) + flag.CommandLine.Parse(cmdline) + env := build.Env() + + // Build the Android archive and Maven resources + build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile")) + build.MustRun(gomobileTool("init")) + build.MustRun(gomobileTool("bind", "--target", "android", "--javapkg", "org.ethereum", "-v", "github.com/ethereum/go-ethereum/mobile")) + + meta := newMavenMetadata(env) + build.Render("build/mvn.pom", meta.Package+".pom", 0755, meta) + + // Skip Maven deploy and Azure upload for PR builds + maybeSkipArchive(env) + + // Sign and upload all the artifacts to Maven Central + os.Rename("geth.aar", meta.Package+".aar") + if *signer != "" && *deploy != "" { + // Import the signing key into the local GPG instance + if b64key := os.Getenv(*signer); b64key != "" { + key, err := base64.StdEncoding.DecodeString(b64key) + if err != nil { + log.Fatalf("invalid base64 %s", *signer) + } + gpg := exec.Command("gpg", "--import") + gpg.Stdin = bytes.NewReader(key) + build.MustRun(gpg) + } + // Upload the artifacts to Sonatype and/or Maven Central + repo := *deploy + "/service/local/staging/deploy/maven2" + if meta.Develop { + repo = *deploy + "/content/repositories/snapshots" + } + build.MustRunCommand("mvn", "gpg:sign-and-deploy-file", + "-settings=build/mvn.settings", "-Durl="+repo, "-DrepositoryId=ossrh", + "-DpomFile="+meta.Package+".pom", "-Dfile="+meta.Package+".aar") + } + // Sign and upload the archive to Azure + archive := "geth-" + archiveBasename("android", env) + ".aar" + os.Rename(meta.Package+".aar", archive) + + if err := archiveUpload(archive, *upload, *signer); err != nil { + log.Fatal(err) + } +} + +func gomobileTool(subcmd string, args ...string) *exec.Cmd { + cmd := exec.Command(filepath.Join(GOBIN, "gomobile"), subcmd) + cmd.Args = append(cmd.Args, args...) + cmd.Env = []string{ + "GOPATH=" + build.GOPATH(), + } + for _, e := range os.Environ() { + if strings.HasPrefix(e, "GOPATH=") { + continue + } + cmd.Env = append(cmd.Env, e) + } + return cmd +} + +type mavenMetadata struct { + Version string + Package string + Develop bool + Contributors []mavenContributor +} + +type mavenContributor struct { + Name string + Email string +} + +func newMavenMetadata(env build.Environment) mavenMetadata { + // Collect the list of authors from the repo root + contribs := []mavenContributor{} + if authors, err := os.Open("AUTHORS"); err == nil { + defer authors.Close() + + scanner := bufio.NewScanner(authors) + for scanner.Scan() { + // Skip any whitespace from the authors list + line := strings.TrimSpace(scanner.Text()) + if line == "" || line[0] == '#' { + continue + } + // Split the author and insert as a contributor + re := regexp.MustCompile("([^<]+) <(.+>)") + parts := re.FindStringSubmatch(line) + if len(parts) == 3 { + contribs = append(contribs, mavenContributor{Name: parts[1], Email: parts[2]}) + } + } + } + // Render the version and package strings + version := build.VERSION() + if isUnstableBuild(env) { + version += "-SNAPSHOT" + } + return mavenMetadata{ + Version: version, + Package: "geth-" + version, + Develop: isUnstableBuild(env), + Contributors: contribs, + } +} + +// XCode frameworks + +func doXCodeFramework(cmdline []string) { + var ( + signer = flag.String("signer", "", `Environment variable holding the signing key (e.g. IOS_SIGNING_KEY)`) + deploy = flag.String("deploy", "", `Destination to deploy the archive (usually "trunk")`) + upload = flag.String("upload", "", `Destination to upload the archives (usually "gethstore/builds")`) + ) + flag.CommandLine.Parse(cmdline) + env := build.Env() + + // Build the iOS XCode framework + build.MustRun(goTool("get", "golang.org/x/mobile/cmd/gomobile")) + build.MustRun(gomobileTool("init")) + + archive := "geth-" + archiveBasename("ios", env) + if err := os.Mkdir(archive, os.ModePerm); err != nil { + log.Fatal(err) + } + bind := gomobileTool("bind", "--target", "ios", "--tags", "ios", "--prefix", "GE", "-v", "github.com/ethereum/go-ethereum/mobile") + bind.Dir, _ = filepath.Abs(archive) + build.MustRun(bind) + build.MustRunCommand("tar", "-zcvf", archive+".tar.gz", archive) + + // Skip CocoaPods deploy and Azure upload for PR builds + maybeSkipArchive(env) + + // Sign and upload the framework to Azure + if err := archiveUpload(archive+".tar.gz", *upload, *signer); err != nil { + log.Fatal(err) + } + // Prepare and upload a PodSpec to CocoaPods + if *deploy != "" { + meta := newPodMetadata(env) + build.Render("build/pod.podspec", meta.Name+".podspec", 0755, meta) + build.MustRunCommand("pod", *deploy, "push", meta.Name+".podspec", "--allow-warnings") + } +} + +type podMetadata struct { + Name string + Version string + Commit string + Contributors []podContributor +} + +type podContributor struct { + Name string + Email string +} + +func newPodMetadata(env build.Environment) podMetadata { + // Collect the list of authors from the repo root + contribs := []podContributor{} + if authors, err := os.Open("AUTHORS"); err == nil { + defer authors.Close() + + scanner := bufio.NewScanner(authors) + for scanner.Scan() { + // Skip any whitespace from the authors list + line := strings.TrimSpace(scanner.Text()) + if line == "" || line[0] == '#' { + continue + } + // Split the author and insert as a contributor + re := regexp.MustCompile("([^<]+) <(.+>)") + parts := re.FindStringSubmatch(line) + if len(parts) == 3 { + contribs = append(contribs, podContributor{Name: parts[1], Email: parts[2]}) + } + } + } + name := "Geth" + if isUnstableBuild(env) { + name += "Develop" + } + return podMetadata{ + Name: name, + Version: archiveVersion(env), + Commit: env.Commit, + Contributors: contribs, + } +} + // Cross compilation func doXgo(cmdline []string) { diff --git a/build/mvn.pom b/build/mvn.pom new file mode 100644 index 0000000000..7670246ba9 --- /dev/null +++ b/build/mvn.pom @@ -0,0 +1,57 @@ + + 4.0.0 + + org.ethereum + geth + {{.Version}} + aar + + Android Ethereum Client + Android port of the go-ethereum libraries and node + https://github.com/ethereum/go-ethereum + 2015 + + + + GNU Lesser General Public License, Version 3.0 + https://www.gnu.org/licenses/lgpl-3.0.en.html + repo + + + + + Ethereum + https://ethereum.org + + + + + karalabe + Péter Szilágyi + peterke@gmail.com + https://github.com/karalabe + + https://www.gravatar.com/avatar/2ecbf0f5b4b79eebf8c193e5d324357f?s=256 + + + + + {{range .Contributors}} + + {{.Name}} + {{.Email}} + {{end}} + + + + GitHub Issues + https://github.com/ethereum/go-ethereum/issues/ + + + + https://github.com/ethereum/go-ethereum + + diff --git a/build/mvn.settings b/build/mvn.settings new file mode 100644 index 0000000000..406b409b9b --- /dev/null +++ b/build/mvn.settings @@ -0,0 +1,24 @@ + + + + ossrh + ${env.ANDROID_SONATYPE_USERNAME} + ${env.ANDROID_SONATYPE_PASSWORD} + + + + + ossrh + + true + + + gpg + + + + + diff --git a/build/pod.podspec b/build/pod.podspec new file mode 100644 index 0000000000..c43af3e824 --- /dev/null +++ b/build/pod.podspec @@ -0,0 +1,22 @@ +Pod::Spec.new do |spec| + spec.name = '{{.Name}}' + spec.version = '{{.Version}}' + spec.license = { :type => 'GNU Lesser General Public License, Version 3.0' } + spec.homepage = 'https://github.com/ethereum/go-ethereum' + spec.authors = { {{range .Contributors}} + '{{.Name}}' => '{{.Email}}',{{end}} + } + spec.summary = 'iOS Ethereum Client' + spec.source = { :git => 'https://github.com/ethereum/go-ethereum.git', :commit => '{{.Commit}}' } + + spec.platform = :ios + spec.ios.deployment_target = '9.0' + spec.ios.vendored_frameworks = 'Frameworks/Geth.framework' + + spec.prepare_command = <<-CMD + curl https://gethstore.blob.core.windows.net/builds/geth-ios-all-{{.Version}}.tar.gz | tar -xvz + mkdir Frameworks + mv geth-ios-all-{{.Version}}/Geth.framework Frameworks + rm -rf geth-ios-all-{{.Version}} + CMD +end diff --git a/cmd/abigen/main.go b/cmd/abigen/main.go index 9c9ab6d31a..dfbd025dad 100644 --- a/cmd/abigen/main.go +++ b/cmd/abigen/main.go @@ -31,14 +31,15 @@ import ( var ( abiFlag = flag.String("abi", "", "Path to the Ethereum contract ABI json to bind") binFlag = flag.String("bin", "", "Path to the Ethereum contract bytecode (generate deploy method)") - typFlag = flag.String("type", "", "Go struct name for the binding (default = package name)") + typFlag = flag.String("type", "", "Struct name for the binding (default = package name)") solFlag = flag.String("sol", "", "Path to the Ethereum contract Solidity source to build and bind") solcFlag = flag.String("solc", "solc", "Solidity compiler to use if source builds are requested") excFlag = flag.String("exc", "", "Comma separated types to exclude from binding") - pkgFlag = flag.String("pkg", "", "Go package name to generate the binding into") - outFlag = flag.String("out", "", "Output file for the generated binding (default = stdout)") + pkgFlag = flag.String("pkg", "", "Package name to generate the binding into") + outFlag = flag.String("out", "", "Output file for the generated binding (default = stdout)") + langFlag = flag.String("lang", "go", "Destination language for the bindings (go, java, objc)") ) func main() { @@ -53,7 +54,19 @@ func main() { os.Exit(-1) } if *pkgFlag == "" { - fmt.Printf("No destination Go package specified (--pkg)\n") + fmt.Printf("No destination package specified (--pkg)\n") + os.Exit(-1) + } + var lang bind.Lang + switch *langFlag { + case "go": + lang = bind.LangGo + case "java": + lang = bind.LangJava + case "objc": + lang = bind.LangObjC + default: + fmt.Printf("Unsupported destination language \"%s\" (--lang)\n", *langFlag) os.Exit(-1) } // If the entire solidity code was specified, build and bind based on that @@ -108,7 +121,7 @@ func main() { types = append(types, kind) } // Generate the contract binding - code, err := bind.Bind(types, abis, bins, *pkgFlag) + code, err := bind.Bind(types, abis, bins, *pkgFlag, lang) if err != nil { fmt.Printf("Failed to generate ABI binding: %v\n", err) os.Exit(-1) diff --git a/cmd/utils/bootnodes.go b/cmd/utils/bootnodes.go deleted file mode 100644 index fbbaa1f227..0000000000 --- a/cmd/utils/bootnodes.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of go-ethereum. -// -// go-ethereum is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// go-ethereum is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with go-ethereum. If not, see . - -package utils - -import "github.com/ethereum/go-ethereum/p2p/discover" - -// FrontierBootNodes are the enode URLs of the P2P bootstrap nodes running on -// the Frontier network. -var FrontierBootNodes = []*discover.Node{ - // ETH/DEV Go Bootnodes - discover.MustParseNode("enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303"), // IE - discover.MustParseNode("enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303"), // BR - discover.MustParseNode("enode://1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082@52.74.57.123:30303"), // SG - - // ETH/DEV Cpp Bootnodes - discover.MustParseNode("enode://979b7fa28feeb35a4741660a16076f1943202cb72b6af70d327f053e248bab9ba81760f39d0701ef1d8f89cc1fbd2cacba0710a12cd5314d5e0c9021aa3637f9@5.1.83.226:30303"), -} - -// TestNetBootNodes are the enode URLs of the P2P bootstrap nodes running on the -// Morden test network. -var TestNetBootNodes = []*discover.Node{ - // ETH/DEV Go Bootnodes - discover.MustParseNode("enode://e4533109cc9bd7604e4ff6c095f7a1d807e15b38e9bfeb05d3b7c423ba86af0a9e89abbf40bd9dde4250fef114cd09270fa4e224cbeef8b7bf05a51e8260d6b8@94.242.229.4:40404"), - discover.MustParseNode("enode://8c336ee6f03e99613ad21274f269479bf4413fb294d697ef15ab897598afb931f56beb8e97af530aee20ce2bcba5776f4a312bc168545de4d43736992c814592@94.242.229.203:30303"), - - // ETH/DEV Cpp Bootnodes -} diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index 2d6bb4f5bd..1ba9056571 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -46,6 +46,7 @@ import ( "github.com/ethereum/go-ethereum/metrics" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/nat" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/pow" @@ -487,9 +488,9 @@ func MakeBootstrapNodes(ctx *cli.Context) []*discover.Node { // Return pre-configured nodes if none were manually requested if !ctx.GlobalIsSet(BootnodesFlag.Name) { if ctx.GlobalBool(TestNetFlag.Name) { - return TestNetBootNodes + return params.TestnetBootnodes } - return FrontierBootNodes + return params.MainnetBootnodes } // Otherwise parse and use the CLI bootstrap nodes bootnodes := []*discover.Node{} @@ -505,13 +506,36 @@ func MakeBootstrapNodes(ctx *cli.Context) []*discover.Node { return bootnodes } +// MakeBootstrapNodesV5 creates a list of bootstrap nodes from the command line +// flags, reverting to pre-configured ones if none have been specified. +func MakeBootstrapNodesV5(ctx *cli.Context) []*discv5.Node { + // Return pre-configured nodes if none were manually requested + if !ctx.GlobalIsSet(BootnodesFlag.Name) { + return params.DiscoveryV5Bootnodes + } + // Otherwise parse and use the CLI bootstrap nodes + bootnodes := []*discv5.Node{} + + for _, url := range strings.Split(ctx.GlobalString(BootnodesFlag.Name), ",") { + node, err := discv5.ParseNode(url) + if err != nil { + glog.V(logger.Error).Infof("Bootstrap URL %s: %v\n", url, err) + continue + } + bootnodes = append(bootnodes, node) + } + return bootnodes +} + // MakeListenAddress creates a TCP listening address string from set command // line flags. func MakeListenAddress(ctx *cli.Context) string { return fmt.Sprintf(":%d", ctx.GlobalInt(ListenPortFlag.Name)) } -func MakeListenAddressV5(ctx *cli.Context) string { +// MakeDiscoveryV5Address creates a UDP listening address string from set command +// line flags for the V5 discovery protocol. +func MakeDiscoveryV5Address(ctx *cli.Context) string { return fmt.Sprintf(":%d", ctx.GlobalInt(ListenPortFlag.Name)+1) } @@ -647,9 +671,10 @@ func MakeNode(ctx *cli.Context, name, gitCommit string) *node.Node { UserIdent: makeNodeUserIdent(ctx), NoDiscovery: ctx.GlobalBool(NoDiscoverFlag.Name) || ctx.GlobalBool(LightModeFlag.Name), DiscoveryV5: ctx.GlobalBool(DiscoveryV5Flag.Name) || ctx.GlobalBool(LightModeFlag.Name) || ctx.GlobalInt(LightServFlag.Name) > 0, + DiscoveryV5Addr: MakeDiscoveryV5Address(ctx), BootstrapNodes: MakeBootstrapNodes(ctx), + BootstrapNodesV5: MakeBootstrapNodesV5(ctx), ListenAddr: MakeListenAddress(ctx), - ListenAddrV5: MakeListenAddressV5(ctx), NAT: MakeNAT(ctx), MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name), MaxPendingPeers: ctx.GlobalInt(MaxPendingPeersFlag.Name), diff --git a/common/types.go b/common/types.go index d008844847..70b7e7aae8 100644 --- a/common/types.go +++ b/common/types.go @@ -35,7 +35,10 @@ const ( var hashJsonLengthErr = errors.New("common: unmarshalJSON failed: hash must be exactly 32 bytes") type ( - Hash [HashLength]byte + // Hash represents the 32 byte Keccak256 hash of arbitrary data. + Hash [HashLength]byte + + // Address represents the 20 byte address of an Ethereum account. Address [AddressLength]byte ) diff --git a/core/types/block.go b/core/types/block.go index fedcfdbbed..68504ffcc8 100644 --- a/core/types/block.go +++ b/core/types/block.go @@ -79,7 +79,7 @@ func (n *BlockNonce) UnmarshalJSON(input []byte) error { return nil } -// Header represents Ethereum block headers. +// Header represents a block header in the Ethereum blockchain. type Header struct { ParentHash common.Hash // Hash to the previous block UncleHash common.Hash // Uncles of this block @@ -214,7 +214,7 @@ type Body struct { Uncles []*Header } -// Block represents a block in the Ethereum blockchain. +// Block represents an entire block in the Ethereum blockchain. type Block struct { header *Header uncles []*Header diff --git a/internal/build/util.go b/internal/build/util.go index ce17ce220b..c7e0614f24 100644 --- a/internal/build/util.go +++ b/internal/build/util.go @@ -56,7 +56,7 @@ func GOPATH() string { if len(path) == 0 { log.Fatal("GOPATH is not set") } - // Ensure that our internal vendor folder in on GOPATH + // Ensure that our internal vendor folder is on GOPATH vendor, _ := filepath.Abs(filepath.Join("build", "_vendor")) for _, dir := range path { if dir == vendor { diff --git a/mobile/accounts.go b/mobile/accounts.go new file mode 100644 index 0000000000..41498b6f01 --- /dev/null +++ b/mobile/accounts.go @@ -0,0 +1,181 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains all the wrappers from the accounts package to support client side key +// management on mobile platforms. + +package geth + +import ( + "errors" + "time" + + "github.com/ethereum/go-ethereum/accounts" +) + +const ( + // StandardScryptN is the N parameter of Scrypt encryption algorithm, using 256MB + // memory and taking approximately 1s CPU time on a modern processor. + StandardScryptN = int(accounts.StandardScryptN) + + // StandardScryptP is the P parameter of Scrypt encryption algorithm, using 256MB + // memory and taking approximately 1s CPU time on a modern processor. + StandardScryptP = int(accounts.StandardScryptP) + + // LightScryptN is the N parameter of Scrypt encryption algorithm, using 4MB + // memory and taking approximately 100ms CPU time on a modern processor. + LightScryptN = int(accounts.LightScryptN) + + // LightScryptP is the P parameter of Scrypt encryption algorithm, using 4MB + // memory and taking approximately 100ms CPU time on a modern processor. + LightScryptP = int(accounts.LightScryptP) +) + +// Account represents a stored key. +type Account struct{ account accounts.Account } + +// Accounts represents a slice of accounts. +type Accounts struct{ accounts []accounts.Account } + +// Size returns the number of accounts in the slice. +func (a *Accounts) Size() int { + return len(a.accounts) +} + +// Get returns the account at the given index from the slice. +func (a *Accounts) Get(index int) (*Account, error) { + if index < 0 || index >= len(a.accounts) { + return nil, errors.New("index out of bounds") + } + return &Account{a.accounts[index]}, nil +} + +// Set sets the account at the given index in the slice. +func (a *Accounts) Set(index int, account *Account) error { + if index < 0 || index >= len(a.accounts) { + return errors.New("index out of bounds") + } + a.accounts[index] = account.account + return nil +} + +// GetAddress retrieves the address associated with the account. +func (a *Account) GetAddress() *Address { + return &Address{a.account.Address} +} + +// GetFile retrieves the path of the file containing the account key. +func (a *Account) GetFile() string { + return a.account.File +} + +// AccountManager manages a key storage directory on disk. +type AccountManager struct{ manager *accounts.Manager } + +// NewAccountManager creates a manager for the given directory. +func NewAccountManager(keydir string, scryptN, scryptP int) *AccountManager { + return &AccountManager{manager: accounts.NewManager(keydir, scryptN, scryptP)} +} + +// HasAddress reports whether a key with the given address is present. +func (am *AccountManager) HasAddress(addr *Address) bool { + return am.manager.HasAddress(addr.address) +} + +// GetAccounts returns all key files present in the directory. +func (am *AccountManager) GetAccounts() *Accounts { + return &Accounts{am.manager.Accounts()} +} + +// DeleteAccount deletes the key matched by account if the passphrase is correct. +// If a contains no filename, the address must match a unique key. +func (am *AccountManager) DeleteAccount(a *Account, passphrase string) error { + return am.manager.DeleteAccount(accounts.Account{ + Address: a.account.Address, + File: a.account.File, + }, passphrase) +} + +// Sign signs hash with an unlocked private key matching the given address. +func (am *AccountManager) Sign(addr *Address, hash []byte) ([]byte, error) { + return am.manager.Sign(addr.address, hash) +} + +// SignWithPassphrase signs hash if the private key matching the given address can be +// decrypted with the given passphrase. +func (am *AccountManager) SignWithPassphrase(addr *Address, passphrase string, hash []byte) ([]byte, error) { + return am.manager.SignWithPassphrase(addr.address, passphrase, hash) +} + +// Unlock unlocks the given account indefinitely. +func (am *AccountManager) Unlock(a *Account, passphrase string) error { + return am.manager.TimedUnlock(a.account, passphrase, 0) +} + +// Lock removes the private key with the given address from memory. +func (am *AccountManager) Lock(addr *Address) error { + return am.manager.Lock(addr.address) +} + +// TimedUnlock unlocks the given account with the passphrase. The account +// stays unlocked for the duration of timeout. A timeout of 0 unlocks the account +// until the program exits. The account must match a unique key file. +// +// If the account address is already unlocked for a duration, TimedUnlock extends or +// shortens the active unlock timeout. If the address was previously unlocked +// indefinitely the timeout is not altered. +func (am *AccountManager) TimedUnlock(a *Account, passphrase string, timeout int64) error { + return am.manager.TimedUnlock(a.account, passphrase, time.Duration(timeout)) +} + +// NewAccount generates a new key and stores it into the key directory, +// encrypting it with the passphrase. +func (am *AccountManager) NewAccount(passphrase string) (*Account, error) { + account, err := am.manager.NewAccount(passphrase) + if err != nil { + return nil, err + } + return &Account{account}, nil +} + +// ExportKey exports as a JSON key, encrypted with newPassphrase. +func (am *AccountManager) ExportKey(a *Account, passphrase, newPassphrase string) ([]byte, error) { + return am.manager.Export(a.account, passphrase, newPassphrase) +} + +// ImportKey stores the given encrypted JSON key into the key directory. +func (am *AccountManager) ImportKey(keyJSON []byte, passphrase, newPassphrase string) (*Account, error) { + account, err := am.manager.Import(keyJSON, passphrase, newPassphrase) + if err != nil { + return nil, err + } + return &Account{account}, nil +} + +// Update changes the passphrase of an existing account. +func (am *AccountManager) Update(a *Account, passphrase, newPassphrase string) error { + return am.manager.Update(a.account, passphrase, newPassphrase) +} + +// ImportPreSaleKey decrypts the given Ethereum presale wallet and stores +// a key file in the key directory. The key file is encrypted with the same passphrase. +func (am *AccountManager) ImportPreSaleKey(keyJSON []byte, passphrase string) (*Account, error) { + account, err := am.manager.ImportPreSaleKey(keyJSON, passphrase) + if err != nil { + return nil, err + } + return &Account{account}, nil +} diff --git a/mobile/android_test.go b/mobile/android_test.go new file mode 100644 index 0000000000..0a3fa93ae0 --- /dev/null +++ b/mobile/android_test.go @@ -0,0 +1,203 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains all the wrappers from the accounts package to support client side key +// management on mobile platforms. + +package geth + +import ( + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "runtime" + "testing" + "time" + + "github.com/ethereum/go-ethereum/internal/build" +) + +// androidTestClass is a Java class to do some lightweight tests against the Android +// bindings. The goal is not to test each individual functionality, rather just to +// catch breaking API and/or implementation changes. +const androidTestClass = ` +package go; + +import android.test.InstrumentationTestCase; +import android.test.MoreAsserts; + +import org.ethereum.geth.*; + +public class AndroidTest extends InstrumentationTestCase { + public AndroidTest() {} + + public void testAccountManagement() { + try { + AccountManager am = new AccountManager(getInstrumentation().getContext().getFilesDir() + "/keystore", Geth.LightScryptN, Geth.LightScryptP); + + Account newAcc = am.newAccount("Creation password"); + byte[] jsonAcc = am.exportKey(newAcc, "Creation password", "Export password"); + + am.deleteAccount(newAcc, "Creation password"); + Account impAcc = am.importKey(jsonAcc, "Export password", "Import password"); + } catch (Exception e) { + fail(e.toString()); + } + } + + public void testInprocNode() { + Context ctx = new Context(); + + try { + // Start up a new inprocess node + Node node = new Node(getInstrumentation().getContext().getFilesDir() + "/.ethereum", new NodeConfig()); + node.start(); + + // Retrieve some data via function calls (we don't really care about the results) + NodeInfo info = node.getNodeInfo(); + info.getName(); + info.getListenerAddress(); + info.getProtocols(); + + // Retrieve some data via the APIs (we don't really care about the results) + EthereumClient ec = node.getEthereumClient(); + ec.getBlockByNumber(ctx, -1).getNumber(); + + NewHeadHandler handler = new NewHeadHandler() { + @Override public void onError(String error) {} + @Override public void onNewHead(final Header header) {} + }; + ec.subscribeNewHead(ctx, handler, 16); + } catch (Exception e) { + fail(e.toString()); + } + } +} +` + +// TestAndroid runs the Android java test class specified above. +// +// This requires the gradle command in PATH and the Android SDK whose path is available +// through ANDROID_HOME environment variable. To successfully run the tests, an Android +// device must also be available with debugging enabled. +// +// This method has been adapted from golang.org/x/mobile/bind/java/seq_test.go/runTest +func TestAndroid(t *testing.T) { + // Skip tests on Windows altogether + if runtime.GOOS == "windows" { + t.Skip("cannot test Android bindings on Windows, skipping") + } + // Make sure all the Android tools are installed + if _, err := exec.Command("which", "gradle").CombinedOutput(); err != nil { + t.Skip("command gradle not found, skipping") + } + if sdk := os.Getenv("ANDROID_HOME"); sdk == "" { + t.Skip("ANDROID_HOME environment var not set, skipping") + } + if _, err := exec.Command("which", "gomobile").CombinedOutput(); err != nil { + t.Log("gomobile missing, installing it...") + if _, err := exec.Command("go", "install", "golang.org/x/mobile/cmd/gomobile").CombinedOutput(); err != nil { + t.Fatalf("install failed: %v", err) + } + t.Log("initializing gomobile...") + start := time.Now() + if _, err := exec.Command("gomobile", "init").CombinedOutput(); err != nil { + t.Fatalf("initialization failed: %v", err) + } + t.Logf("initialization took %v", time.Since(start)) + } + // Create and switch to a temporary workspace + workspace, err := ioutil.TempDir("", "geth-android-") + if err != nil { + t.Fatalf("failed to create temporary workspace: %v", err) + } + defer os.RemoveAll(workspace) + + pwd, err := os.Getwd() + if err != nil { + t.Fatalf("failed to get current working directory: %v", err) + } + if err := os.Chdir(workspace); err != nil { + t.Fatalf("failed to switch to temporary workspace: %v", err) + } + defer os.Chdir(pwd) + + // Create the skeleton of the Android project + for _, dir := range []string{"src/main", "src/androidTest/java/org/ethereum/gethtest", "libs"} { + err = os.MkdirAll(dir, os.ModePerm) + if err != nil { + t.Fatal(err) + } + } + // Generate the mobile bindings for Geth and add the tester class + gobind := exec.Command("gomobile", "bind", "-javapkg", "org.ethereum", "github.com/ethereum/go-ethereum/mobile") + if output, err := gobind.CombinedOutput(); err != nil { + t.Logf("%s", output) + t.Fatalf("failed to run gomobile bind: %v", err) + } + build.CopyFile(filepath.Join("libs", "geth.aar"), "geth.aar", os.ModePerm) + + if err = ioutil.WriteFile(filepath.Join("src", "androidTest", "java", "org", "ethereum", "gethtest", "AndroidTest.java"), []byte(androidTestClass), os.ModePerm); err != nil { + t.Fatalf("failed to write Android test class: %v", err) + } + // Finish creating the project and run the tests via gradle + if err = ioutil.WriteFile(filepath.Join("src", "main", "AndroidManifest.xml"), []byte(androidManifest), os.ModePerm); err != nil { + t.Fatalf("failed to write Android manifest: %v", err) + } + if err = ioutil.WriteFile("build.gradle", []byte(gradleConfig), os.ModePerm); err != nil { + t.Fatalf("failed to write gradle build file: %v", err) + } + if output, err := exec.Command("gradle", "connectedAndroidTest").CombinedOutput(); err != nil { + t.Logf("%s", output) + t.Errorf("failed to run gradle test: %v", err) + } +} + +const androidManifest = ` + + + +` + +const gradleConfig = `buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:1.5.0' + } +} +allprojects { + repositories { jcenter() } +} +apply plugin: 'com.android.library' +android { + compileSdkVersion 'android-19' + buildToolsVersion '21.1.2' + defaultConfig { minSdkVersion 15 } +} +repositories { + flatDir { dirs 'libs' } +} +dependencies { + compile 'com.android.support:appcompat-v7:19.0.0' + compile(name: "geth", ext: "aar") +} +` diff --git a/mobile/big.go b/mobile/big.go new file mode 100644 index 0000000000..6a358ba285 --- /dev/null +++ b/mobile/big.go @@ -0,0 +1,95 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains all the wrappers from the math/big package. + +package geth + +import ( + "errors" + "math/big" +) + +// A BigInt represents a signed multi-precision integer. +type BigInt struct { + bigint *big.Int +} + +// NewBigInt allocates and returns a new BigInt set to x. +func NewBigInt(x int64) *BigInt { + return &BigInt{big.NewInt(x)} +} + +// GetBytes returns the absolute value of x as a big-endian byte slice. +func (bi *BigInt) GetBytes() []byte { + return bi.bigint.Bytes() +} + +// String returns the value of x as a formatted decimal string. +func (bi *BigInt) String() string { + return bi.bigint.String() +} + +// GetInt64 returns the int64 representation of x. If x cannot be represented in +// an int64, the result is undefined. +func (bi *BigInt) GetInt64() int64 { + return bi.bigint.Int64() +} + +// SetBytes interprets buf as the bytes of a big-endian unsigned integer and sets +// the big int to that value. +func (bi *BigInt) SetBytes(buf []byte) { + bi.bigint.SetBytes(buf) +} + +// SetInt64 sets the big int to x. +func (bi *BigInt) SetInt64(x int64) { + bi.bigint.SetInt64(x) +} + +// SetString sets the big int to x. +// +// The string prefix determines the actual conversion base. A prefix of "0x" or +// "0X" selects base 16; the "0" prefix selects base 8, and a "0b" or "0B" prefix +// selects base 2. Otherwise the selected base is 10. +func (bi *BigInt) SetString(x string, base int) { + bi.bigint.SetString(x, base) +} + +// BigInts represents a slice of big ints. +type BigInts struct{ bigints []*big.Int } + +// Size returns the number of big ints in the slice. +func (bi *BigInts) Size() int { + return len(bi.bigints) +} + +// Get returns the bigint at the given index from the slice. +func (bi *BigInts) Get(index int) (*BigInt, error) { + if index < 0 || index >= len(bi.bigints) { + return nil, errors.New("index out of bounds") + } + return &BigInt{bi.bigints[index]}, nil +} + +// Set sets the big int at the given index in the slice. +func (bi *BigInts) Set(index int, bigint *BigInt) error { + if index < 0 || index >= len(bi.bigints) { + return errors.New("index out of bounds") + } + bi.bigints[index] = bigint.bigint + return nil +} diff --git a/mobile/big_go1.7.go b/mobile/big_go1.7.go new file mode 100644 index 0000000000..0447e1f66e --- /dev/null +++ b/mobile/big_go1.7.go @@ -0,0 +1,26 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains the wrappers from the math/big package that require Go 1.7 and above. + +// +build go1.7 + +package geth + +// GetString returns the value of x as a formatted string in some number base. +func (bi *BigInt) GetString(base int) string { + return bi.bigint.Text(base) +} diff --git a/mobile/bind.go b/mobile/bind.go new file mode 100644 index 0000000000..50adc6b0f1 --- /dev/null +++ b/mobile/bind.go @@ -0,0 +1,202 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains all the wrappers from the bind package. + +package geth + +import ( + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +// Signer is an interaface defining the callback when a contract requires a +// method to sign the transaction before submission. +type Signer interface { + Sign(*Address, *Transaction) (*Transaction, error) +} + +type signer struct { + sign bind.SignerFn +} + +func (s *signer) Sign(addr *Address, tx *Transaction) (*Transaction, error) { + sig, err := s.sign(types.HomesteadSigner{}, addr.address, tx.tx) + if err != nil { + return nil, err + } + return &Transaction{sig}, nil +} + +// CallOpts is the collection of options to fine tune a contract call request. +type CallOpts struct { + opts bind.CallOpts +} + +// NewCallOpts creates a new option set for contract calls. +func NewCallOpts() *CallOpts { + return new(CallOpts) +} + +func (opts *CallOpts) IsPending() bool { return opts.opts.Pending } +func (opts *CallOpts) GetGasLimit() int64 { return 0 /* TODO(karalabe) */ } + +// GetContext cannot be reliably implemented without identity preservation (https://github.com/golang/go/issues/16876) +// Even then it's awkward to unpack the subtleties of a Go context out to Java. +// func (opts *CallOpts) GetContext() *Context { return &Context{opts.opts.Context} } + +func (opts *CallOpts) SetPending(pending bool) { opts.opts.Pending = pending } +func (opts *CallOpts) SetGasLimit(limit int64) { /* TODO(karalabe) */ } +func (opts *CallOpts) SetContext(context *Context) { opts.opts.Context = context.context } + +// TransactOpts is the collection of authorization data required to create a +// valid Ethereum transaction. +type TransactOpts struct { + opts bind.TransactOpts +} + +func (opts *TransactOpts) GetFrom() *Address { return &Address{opts.opts.From} } +func (opts *TransactOpts) GetNonce() int64 { return opts.opts.Nonce.Int64() } +func (opts *TransactOpts) GetValue() *BigInt { return &BigInt{opts.opts.Value} } +func (opts *TransactOpts) GetGasPrice() *BigInt { return &BigInt{opts.opts.GasPrice} } +func (opts *TransactOpts) GetGasLimit() int64 { return opts.opts.GasLimit.Int64() } + +// GetSigner cannot be reliably implemented without identity preservation (https://github.com/golang/go/issues/16876) +// func (opts *TransactOpts) GetSigner() Signer { return &signer{opts.opts.Signer} } + +// GetContext cannot be reliably implemented without identity preservation (https://github.com/golang/go/issues/16876) +// Even then it's awkward to unpack the subtleties of a Go context out to Java. +//func (opts *TransactOpts) GetContext() *Context { return &Context{opts.opts.Context} } + +func (opts *TransactOpts) SetFrom(from *Address) { opts.opts.From = from.address } +func (opts *TransactOpts) SetNonce(nonce int64) { opts.opts.Nonce = big.NewInt(nonce) } +func (opts *TransactOpts) SetSigner(s Signer) { + opts.opts.Signer = func(signer types.Signer, addr common.Address, tx *types.Transaction) (*types.Transaction, error) { + sig, err := s.Sign(&Address{addr}, &Transaction{tx}) + if err != nil { + return nil, err + } + return sig.tx, nil + } +} +func (opts *TransactOpts) SetValue(value *BigInt) { opts.opts.Value = value.bigint } +func (opts *TransactOpts) SetGasPrice(price *BigInt) { opts.opts.GasPrice = price.bigint } +func (opts *TransactOpts) SetGasLimit(limit int64) { opts.opts.GasLimit = big.NewInt(limit) } +func (opts *TransactOpts) SetContext(context *Context) { opts.opts.Context = context.context } + +// BoundContract is the base wrapper object that reflects a contract on the +// Ethereum network. It contains a collection of methods that are used by the +// higher level contract bindings to operate. +type BoundContract struct { + contract *bind.BoundContract + address common.Address + deployer *types.Transaction +} + +// DeployContract deploys a contract onto the Ethereum blockchain and binds the +// deployment address with a wrapper. +func DeployContract(opts *TransactOpts, abiJSON string, bytecode []byte, client *EthereumClient, args *Interfaces) (*BoundContract, error) { + // Convert all the deployment parameters to Go types + params := make([]interface{}, len(args.objects)) + for i, obj := range args.objects { + params[i] = obj + } + // Deploy the contract to the network + parsed, err := abi.JSON(strings.NewReader(abiJSON)) + if err != nil { + return nil, err + } + addr, tx, bound, err := bind.DeployContract(&opts.opts, parsed, bytecode, client.client, params...) + if err != nil { + return nil, err + } + return &BoundContract{ + contract: bound, + address: addr, + deployer: tx, + }, nil +} + +// BindContract creates a low level contract interface through which calls and +// transactions may be made through. +func BindContract(address *Address, abiJSON string, client *EthereumClient) (*BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(abiJSON)) + if err != nil { + return nil, err + } + return &BoundContract{ + contract: bind.NewBoundContract(address.address, parsed, client.client, client.client), + address: address.address, + }, nil +} + +func (c *BoundContract) GetAddress() *Address { return &Address{c.address} } +func (c *BoundContract) GetDeployer() *Transaction { + if c.deployer == nil { + return nil + } + return &Transaction{c.deployer} +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. +func (c *BoundContract) Call(opts *CallOpts, out *Interfaces, method string, args *Interfaces) error { + // Convert all the input and output parameters to Go types + params := make([]interface{}, len(args.objects)) + for i, obj := range args.objects { + params[i] = obj + } + results := make([]interface{}, len(out.objects)) + for i, obj := range out.objects { + results[i] = obj + } + // Execute the call to the contract and wrap any results + if err := c.contract.Call(&opts.opts, &results, method, params...); err != nil { + return err + } + for i, res := range results { + out.objects[i] = res + } + return nil +} + +// Transact invokes the (paid) contract method with params as input values. +func (c *BoundContract) Transact(opts *TransactOpts, method string, args *Interfaces) (*Transaction, error) { + params := make([]interface{}, len(args.objects)) + for i, obj := range args.objects { + params[i] = obj + } + tx, err := c.contract.Transact(&opts.opts, method, params) + if err != nil { + return nil, err + } + return &Transaction{tx}, nil +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (c *BoundContract) Transfer(opts *TransactOpts) (*Transaction, error) { + tx, err := c.contract.Transfer(&opts.opts) + if err != nil { + return nil, err + } + return &Transaction{tx}, nil +} diff --git a/mobile/common.go b/mobile/common.go new file mode 100644 index 0000000000..ab1810bf16 --- /dev/null +++ b/mobile/common.go @@ -0,0 +1,187 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains all the wrappers from the common package. + +package geth + +import ( + "encoding/hex" + "errors" + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/common" +) + +// Hash represents the 32 byte Keccak256 hash of arbitrary data. +type Hash struct { + hash common.Hash +} + +// NewHashFromBytes converts a slice of bytes to a hash value. +func NewHashFromBytes(hash []byte) (*Hash, error) { + h := new(Hash) + if err := h.SetBytes(hash); err != nil { + return nil, err + } + return h, nil +} + +// NewHashFromHex converts a hex string to a hash value. +func NewHashFromHex(hash string) (*Hash, error) { + h := new(Hash) + if err := h.SetHex(hash); err != nil { + return nil, err + } + return h, nil +} + +// SetBytes sets the specified slice of bytes as the hash value. +func (h *Hash) SetBytes(hash []byte) error { + if length := len(hash); length != common.HashLength { + return fmt.Errorf("invalid hash length: %v != %v", length, common.HashLength) + } + copy(h.hash[:], hash) + return nil +} + +// GetBytes retrieves the byte representation of the hash. +func (h *Hash) GetBytes() []byte { + return h.hash[:] +} + +// SetHex sets the specified hex string as the hash value. +func (h *Hash) SetHex(hash string) error { + hash = strings.ToLower(hash) + if len(hash) >= 2 && hash[:2] == "0x" { + hash = hash[2:] + } + if length := len(hash); length != 2*common.HashLength { + return fmt.Errorf("invalid hash hex length: %v != %v", length, 2*common.HashLength) + } + bin, err := hex.DecodeString(hash) + if err != nil { + return err + } + copy(h.hash[:], bin) + return nil +} + +// GetHex retrieves the hex string representation of the hash. +func (h *Hash) GetHex() string { + return h.hash.Hex() +} + +// Hashes represents a slice of hashes. +type Hashes struct{ hashes []common.Hash } + +// Size returns the number of hashes in the slice. +func (h *Hashes) Size() int { + return len(h.hashes) +} + +// Get returns the hash at the given index from the slice. +func (h *Hashes) Get(index int) (*Hash, error) { + if index < 0 || index >= len(h.hashes) { + return nil, errors.New("index out of bounds") + } + return &Hash{h.hashes[index]}, nil +} + +// Address represents the 20 byte address of an Ethereum account. +type Address struct { + address common.Address +} + +// NewAddressFromBytes converts a slice of bytes to a hash value. +func NewAddressFromBytes(address []byte) (*Address, error) { + a := new(Address) + if err := a.SetBytes(address); err != nil { + return nil, err + } + return a, nil +} + +// NewAddressFromHex converts a hex string to a address value. +func NewAddressFromHex(address string) (*Address, error) { + a := new(Address) + if err := a.SetHex(address); err != nil { + return nil, err + } + return a, nil +} + +// SetBytes sets the specified slice of bytes as the address value. +func (a *Address) SetBytes(address []byte) error { + if length := len(address); length != common.AddressLength { + return fmt.Errorf("invalid address length: %v != %v", length, common.AddressLength) + } + copy(a.address[:], address) + return nil +} + +// GetBytes retrieves the byte representation of the address. +func (a *Address) GetBytes() []byte { + return a.address[:] +} + +// SetHex sets the specified hex string as the address value. +func (a *Address) SetHex(address string) error { + address = strings.ToLower(address) + if len(address) >= 2 && address[:2] == "0x" { + address = address[2:] + } + if length := len(address); length != 2*common.AddressLength { + return fmt.Errorf("invalid address hex length: %v != %v", length, 2*common.AddressLength) + } + bin, err := hex.DecodeString(address) + if err != nil { + return err + } + copy(a.address[:], bin) + return nil +} + +// GetHex retrieves the hex string representation of the address. +func (a *Address) GetHex() string { + return a.address.Hex() +} + +// Addresses represents a slice of addresses. +type Addresses struct{ addresses []common.Address } + +// Size returns the number of addresses in the slice. +func (a *Addresses) Size() int { + return len(a.addresses) +} + +// Get returns the address at the given index from the slice. +func (a *Addresses) Get(index int) (*Address, error) { + if index < 0 || index >= len(a.addresses) { + return nil, errors.New("index out of bounds") + } + return &Address{a.addresses[index]}, nil +} + +// Set sets the address at the given index in the slice. +func (a *Addresses) Set(index int, address *Address) error { + if index < 0 || index >= len(a.addresses) { + return errors.New("index out of bounds") + } + a.addresses[index] = address.address + return nil +} diff --git a/mobile/context.go b/mobile/context.go new file mode 100644 index 0000000000..9df94b6896 --- /dev/null +++ b/mobile/context.go @@ -0,0 +1,81 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains all the wrappers from the golang.org/x/net/context package to support +// client side context management on mobile platforms. + +package geth + +import ( + "time" + + "golang.org/x/net/context" +) + +// Context carries a deadline, a cancelation signal, and other values across API +// boundaries. +type Context struct { + context context.Context + cancel context.CancelFunc +} + +// NewContext returns a non-nil, empty Context. It is never canceled, has no +// values, and has no deadline. It is typically used by the main function, +// initialization, and tests, and as the top-level Context for incoming requests. +func NewContext() *Context { + return &Context{ + context: context.Background(), + } +} + +// WithCancel returns a copy of the original context with cancellation mechanism +// included. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func (c *Context) WithCancel() *Context { + child, cancel := context.WithCancel(c.context) + return &Context{ + context: child, + cancel: cancel, + } +} + +// WithDeadline returns a copy of the original context with the deadline adjusted +// to be no later than the specified time. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func (c *Context) WithDeadline(sec int64, nsec int64) *Context { + child, cancel := context.WithDeadline(c.context, time.Unix(sec, nsec)) + return &Context{ + context: child, + cancel: cancel, + } +} + +// WithTimeout returns a copy of the original context with the deadline adjusted +// to be no later than now + the duration specified. +// +// Canceling this context releases resources associated with it, so code should +// call cancel as soon as the operations running in this Context complete. +func (c *Context) WithTimeout(nsec int64) *Context { + child, cancel := context.WithTimeout(c.context, time.Duration(nsec)) + return &Context{ + context: child, + cancel: cancel, + } +} diff --git a/mobile/discover.go b/mobile/discover.go new file mode 100644 index 0000000000..9df2d04c3e --- /dev/null +++ b/mobile/discover.go @@ -0,0 +1,104 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains all the wrappers from the accounts package to support client side enode +// management on mobile platforms. + +package geth + +import ( + "errors" + + "github.com/ethereum/go-ethereum/p2p/discv5" +) + +// Enode represents a host on the network. +type Enode struct { + node *discv5.Node +} + +// NewEnode parses a node designator. +// +// There are two basic forms of node designators +// - incomplete nodes, which only have the public key (node ID) +// - complete nodes, which contain the public key and IP/Port information +// +// For incomplete nodes, the designator must look like one of these +// +// enode:// +// +// +// For complete nodes, the node ID is encoded in the username portion +// of the URL, separated from the host by an @ sign. The hostname can +// only be given as an IP address, DNS domain names are not allowed. +// The port in the host name section is the TCP listening port. If the +// TCP and UDP (discovery) ports differ, the UDP port is specified as +// query parameter "discport". +// +// In the following example, the node URL describes +// a node with IP address 10.3.58.6, TCP listening port 30303 +// and UDP discovery port 30301. +// +// enode://@10.3.58.6:30303?discport=30301 +func NewEnode(rawurl string) (*Enode, error) { + node, err := discv5.ParseNode(rawurl) + if err != nil { + return nil, err + } + return &Enode{node}, nil +} + +// Enodes represents a slice of accounts. +type Enodes struct{ nodes []*discv5.Node } + +// NewEnodes creates a slice of uninitialized enodes. +func NewEnodes(size int) *Enodes { + return &Enodes{ + nodes: make([]*discv5.Node, size), + } +} + +// NewEnodesEmpty creates an empty slice of Enode values. +func NewEnodesEmpty() *Enodes { + return NewEnodes(0) +} + +// Size returns the number of enodes in the slice. +func (e *Enodes) Size() int { + return len(e.nodes) +} + +// Get returns the enode at the given index from the slice. +func (e *Enodes) Get(index int) (*Enode, error) { + if index < 0 || index >= len(e.nodes) { + return nil, errors.New("index out of bounds") + } + return &Enode{e.nodes[index]}, nil +} + +// Set sets the enode at the given index in the slice. +func (e *Enodes) Set(index int, enode *Enode) error { + if index < 0 || index >= len(e.nodes) { + return errors.New("index out of bounds") + } + e.nodes[index] = enode.node + return nil +} + +// Append adds a new enode element to the end of the slice. +func (e *Enodes) Append(enode *Enode) { + e.nodes = append(e.nodes, enode.node) +} diff --git a/mobile/doc.go b/mobile/doc.go new file mode 100644 index 0000000000..50cc7f4f8c --- /dev/null +++ b/mobile/doc.go @@ -0,0 +1,57 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Package geth contains the simplified mobile APIs to go-ethereum. +// +// The scope of this package is *not* to allow writing a custom Ethereun client +// with pieces plucked from go-ethereum, rather to allow writing native dapps on +// mobile platforms. Keep this in mind when using or extending this package! +// +// API limitations +// +// Since gomobile cannot bridge arbitrary types between Go and Android/iOS, the +// exposed APIs need to be manually wrapped into simplified types, with custom +// constructors and getters/setters to ensure that they can be meaninfully used +// from Java/ObjC too. +// +// With this in mind, please try to limit the scope of this package and only add +// essentials without which mobile support cannot work, especially since manually +// syncing the code will be unwieldy otherwise. In the long term we might consider +// writing custom library generators, but those are out of scope now. +// +// Content wise each file in this package corresponds to an entire Go package +// from the go-ethereum repository. Please adhere to this scoping to prevent this +// package getting unmaintainable. +// +// Wrapping guidelines: +// +// Every type that is to be exposed should be wrapped into its own plain struct, +// which internally contains a single field: the original go-ethereum version. +// This is needed because gomobile cannot expose named types for now. +// +// Whenever a method argument or a return type is a custom struct, the pointer +// variant should always be used as value types crossing over between language +// boundaries might have strange behaviors. +// +// Slices of types should be converted into a single multiplicative type wrapping +// a go slice with the methods `Size`, `Get` and `Set`. Further slice operations +// should not be provided to limit the remote code complexity. Arrays should be +// avoided as much as possible since they complicate bounds checking. +// +// Note, a panic *cannot* cross over language boundaries, instead will result in +// an undebuggable SEGFAULT in the process. For error handling only ever use error +// returns, which may be the only or the second return. +package geth diff --git a/mobile/ethclient.go b/mobile/ethclient.go new file mode 100644 index 0000000000..668d65e322 --- /dev/null +++ b/mobile/ethclient.go @@ -0,0 +1,305 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains a wrapper for the Ethereum client. + +package geth + +import ( + "math/big" + + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/ethclient" +) + +// EthereumClient provides access to the Ethereum APIs. +type EthereumClient struct { + client *ethclient.Client +} + +// NewEthereumClient connects a client to the given URL. +func NewEthereumClient(rawurl string) (*EthereumClient, error) { + client, err := ethclient.Dial(rawurl) + return &EthereumClient{client}, err +} + +// GetBlockByHash returns the given full block. +func (ec *EthereumClient) GetBlockByHash(ctx *Context, hash *Hash) (*Block, error) { + block, err := ec.client.BlockByHash(ctx.context, hash.hash) + return &Block{block}, err +} + +// GetBlockByNumber returns a block from the current canonical chain. If number is <0, the +// latest known block is returned. +func (ec *EthereumClient) GetBlockByNumber(ctx *Context, number int64) (*Block, error) { + if number < 0 { + block, err := ec.client.BlockByNumber(ctx.context, nil) + return &Block{block}, err + } + block, err := ec.client.BlockByNumber(ctx.context, big.NewInt(number)) + return &Block{block}, err +} + +// GetHeaderByHash returns the block header with the given hash. +func (ec *EthereumClient) GetHeaderByHash(ctx *Context, hash *Hash) (*Header, error) { + header, err := ec.client.HeaderByHash(ctx.context, hash.hash) + return &Header{header}, err +} + +// GetHeaderByNumber returns a block header from the current canonical chain. If number is <0, +// the latest known header is returned. +func (ec *EthereumClient) GetHeaderByNumber(ctx *Context, number int64) (*Header, error) { + if number < 0 { + header, err := ec.client.HeaderByNumber(ctx.context, nil) + return &Header{header}, err + } + header, err := ec.client.HeaderByNumber(ctx.context, big.NewInt(number)) + return &Header{header}, err +} + +// GetTransactionByHash returns the transaction with the given hash. +func (ec *EthereumClient) GetTransactionByHash(ctx *Context, hash *Hash) (*Transaction, error) { + tx, err := ec.client.TransactionByHash(ctx.context, hash.hash) + return &Transaction{tx}, err +} + +// GetTransactionCount returns the total number of transactions in the given block. +func (ec *EthereumClient) GetTransactionCount(ctx *Context, hash *Hash) (int, error) { + count, err := ec.client.TransactionCount(ctx.context, hash.hash) + return int(count), err +} + +// GetTransactionInBlock returns a single transaction at index in the given block. +func (ec *EthereumClient) GetTransactionInBlock(ctx *Context, hash *Hash, index int) (*Transaction, error) { + tx, err := ec.client.TransactionInBlock(ctx.context, hash.hash, uint(index)) + return &Transaction{tx}, err + +} + +// GetTransactionReceipt returns the receipt of a transaction by transaction hash. +// Note that the receipt is not available for pending transactions. +func (ec *EthereumClient) GetTransactionReceipt(ctx *Context, hash *Hash) (*Receipt, error) { + receipt, err := ec.client.TransactionReceipt(ctx.context, hash.hash) + return &Receipt{receipt}, err +} + +// SyncProgress retrieves the current progress of the sync algorithm. If there's +// no sync currently running, it returns nil. +func (ec *EthereumClient) SyncProgress(ctx *Context) (*SyncProgress, error) { + progress, err := ec.client.SyncProgress(ctx.context) + if progress == nil { + return nil, err + } + return &SyncProgress{*progress}, err +} + +// NewHeadHandler is a client-side subscription callback to invoke on events and +// subscription failure. +type NewHeadHandler interface { + OnNewHead(header *Header) + OnError(failure string) +} + +// SubscribeNewHead subscribes to notifications about the current blockchain head +// on the given channel. +func (ec *EthereumClient) SubscribeNewHead(ctx *Context, handler NewHeadHandler, buffer int) (*Subscription, error) { + // Subscribe to the event internally + ch := make(chan *types.Header, buffer) + sub, err := ec.client.SubscribeNewHead(ctx.context, ch) + if err != nil { + return nil, err + } + // Start up a dispatcher to feed into the callback + go func() { + for { + select { + case header := <-ch: + handler.OnNewHead(&Header{header}) + + case err := <-sub.Err(): + handler.OnError(err.Error()) + return + } + } + }() + return &Subscription{sub}, nil +} + +// State Access + +// GetBalanceAt returns the wei balance of the given account. +// The block number can be <0, in which case the balance is taken from the latest known block. +func (ec *EthereumClient) GetBalanceAt(ctx *Context, account *Address, number int64) (*BigInt, error) { + if number < 0 { + balance, err := ec.client.BalanceAt(ctx.context, account.address, nil) + return &BigInt{balance}, err + } + balance, err := ec.client.BalanceAt(ctx.context, account.address, big.NewInt(number)) + return &BigInt{balance}, err +} + +// GetStorageAt returns the value of key in the contract storage of the given account. +// The block number can be <0, in which case the value is taken from the latest known block. +func (ec *EthereumClient) GetStorageAt(ctx *Context, account *Address, key *Hash, number int64) ([]byte, error) { + if number < 0 { + return ec.client.StorageAt(ctx.context, account.address, key.hash, nil) + } + return ec.client.StorageAt(ctx.context, account.address, key.hash, big.NewInt(number)) +} + +// GetCodeAt returns the contract code of the given account. +// The block number can be <0, in which case the code is taken from the latest known block. +func (ec *EthereumClient) GetCodeAt(ctx *Context, account *Address, number int64) ([]byte, error) { + if number < 0 { + return ec.client.CodeAt(ctx.context, account.address, nil) + } + return ec.client.CodeAt(ctx.context, account.address, big.NewInt(number)) +} + +// GetNonceAt returns the account nonce of the given account. +// The block number can be <0, in which case the nonce is taken from the latest known block. +func (ec *EthereumClient) GetNonceAt(ctx *Context, account *Address, number int64) (int64, error) { + if number < 0 { + nonce, err := ec.client.NonceAt(ctx.context, account.address, nil) + return int64(nonce), err + } + nonce, err := ec.client.NonceAt(ctx.context, account.address, big.NewInt(number)) + return int64(nonce), err +} + +// Filters + +// FilterLogs executes a filter query. +func (ec *EthereumClient) FilterLogs(ctx *Context, query *FilterQuery) (*Logs, error) { + logs, err := ec.client.FilterLogs(ctx.context, query.query) + if err != nil { + return nil, err + } + // Temp hack due to vm.Logs being []*vm.Log + res := make(vm.Logs, len(logs)) + for i, log := range logs { + res[i] = &log + } + return &Logs{res}, nil +} + +// FilterLogsHandler is a client-side subscription callback to invoke on events and +// subscription failure. +type FilterLogsHandler interface { + OnFilterLogs(log *Log) + OnError(failure string) +} + +// SubscribeFilterLogs subscribes to the results of a streaming filter query. +func (ec *EthereumClient) SubscribeFilterLogs(ctx *Context, query *FilterQuery, handler FilterLogsHandler, buffer int) (*Subscription, error) { + // Subscribe to the event internally + ch := make(chan vm.Log, buffer) + sub, err := ec.client.SubscribeFilterLogs(ctx.context, query.query, ch) + if err != nil { + return nil, err + } + // Start up a dispatcher to feed into the callback + go func() { + for { + select { + case log := <-ch: + handler.OnFilterLogs(&Log{&log}) + + case err := <-sub.Err(): + handler.OnError(err.Error()) + return + } + } + }() + return &Subscription{sub}, nil +} + +// Pending State + +// GetPendingBalanceAt returns the wei balance of the given account in the pending state. +func (ec *EthereumClient) GetPendingBalanceAt(ctx *Context, account *Address) (*BigInt, error) { + balance, err := ec.client.PendingBalanceAt(ctx.context, account.address) + return &BigInt{balance}, err +} + +// GetPendingStorageAt returns the value of key in the contract storage of the given account in the pending state. +func (ec *EthereumClient) GetPendingStorageAt(ctx *Context, account *Address, key *Hash) ([]byte, error) { + return ec.client.PendingStorageAt(ctx.context, account.address, key.hash) +} + +// GetPendingCodeAt returns the contract code of the given account in the pending state. +func (ec *EthereumClient) GetPendingCodeAt(ctx *Context, account *Address) ([]byte, error) { + return ec.client.PendingCodeAt(ctx.context, account.address) +} + +// GetPendingNonceAt returns the account nonce of the given account in the pending state. +// This is the nonce that should be used for the next transaction. +func (ec *EthereumClient) GetPendingNonceAt(ctx *Context, account *Address) (int64, error) { + nonce, err := ec.client.PendingNonceAt(ctx.context, account.address) + return int64(nonce), err +} + +// GetPendingTransactionCount returns the total number of transactions in the pending state. +func (ec *EthereumClient) GetPendingTransactionCount(ctx *Context) (int, error) { + count, err := ec.client.PendingTransactionCount(ctx.context) + return int(count), err +} + +// Contract Calling + +// CallContract executes a message call transaction, which is directly executed in the VM +// of the node, but never mined into the blockchain. +// +// blockNumber selects the block height at which the call runs. It can be <0, in which +// case the code is taken from the latest known block. Note that state from very old +// blocks might not be available. +func (ec *EthereumClient) CallContract(ctx *Context, msg *CallMsg, number int64) ([]byte, error) { + if number < 0 { + return ec.client.CallContract(ctx.context, msg.msg, nil) + } + return ec.client.CallContract(ctx.context, msg.msg, big.NewInt(number)) +} + +// PendingCallContract executes a message call transaction using the EVM. +// The state seen by the contract call is the pending state. +func (ec *EthereumClient) PendingCallContract(ctx *Context, msg *CallMsg) ([]byte, error) { + return ec.client.PendingCallContract(ctx.context, msg.msg) +} + +// SuggestGasPrice retrieves the currently suggested gas price to allow a timely +// execution of a transaction. +func (ec *EthereumClient) SuggestGasPrice(ctx *Context) (*BigInt, error) { + price, err := ec.client.SuggestGasPrice(ctx.context) + return &BigInt{price}, err +} + +// EstimateGas tries to estimate the gas needed to execute a specific transaction based on +// the current pending state of the backend blockchain. There is no guarantee that this is +// the true gas limit requirement as other transactions may be added or removed by miners, +// but it should provide a basis for setting a reasonable default. +func (ec *EthereumClient) EstimateGas(ctx *Context, msg *CallMsg) (*BigInt, error) { + price, err := ec.client.EstimateGas(ctx.context, msg.msg) + return &BigInt{price}, err +} + +// SendTransaction injects a signed transaction into the pending pool for execution. +// +// If the transaction was a contract creation use the TransactionReceipt method to get the +// contract address after the transaction has been mined. +func (ec *EthereumClient) SendTransaction(ctx *Context, tx *Transaction) error { + return ec.client.SendTransaction(ctx.context, tx.tx) +} diff --git a/mobile/ethereum.go b/mobile/ethereum.go new file mode 100644 index 0000000000..6e8046ac9b --- /dev/null +++ b/mobile/ethereum.go @@ -0,0 +1,125 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains all the wrappers from the go-ethereum root package. + +package geth + +import ( + "errors" + "math/big" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/common" +) + +// Subscription represents an event subscription where events are +// delivered on a data channel. +type Subscription struct { + sub ethereum.Subscription +} + +// Unsubscribe cancels the sending of events to the data channel +// and closes the error channel. +func (s *Subscription) Unsubscribe() { + s.sub.Unsubscribe() +} + +// CallMsg contains parameters for contract calls. +type CallMsg struct { + msg ethereum.CallMsg +} + +// NewCallMsg creates an empty contract call parameter list. +func NewCallMsg() *CallMsg { + return new(CallMsg) +} + +func (msg *CallMsg) GetFrom() *Address { return &Address{msg.msg.From} } +func (msg *CallMsg) GetGas() int64 { return msg.msg.Gas.Int64() } +func (msg *CallMsg) GetGasPrice() *BigInt { return &BigInt{msg.msg.GasPrice} } +func (msg *CallMsg) GetValue() *BigInt { return &BigInt{msg.msg.Value} } +func (msg *CallMsg) GetData() []byte { return msg.msg.Data } +func (msg *CallMsg) GetTo() *Address { + if to := msg.msg.To; to != nil { + return &Address{*msg.msg.To} + } + return nil +} + +func (msg *CallMsg) SetFrom(address *Address) { msg.msg.From = address.address } +func (msg *CallMsg) SetGas(gas int64) { msg.msg.Gas = big.NewInt(gas) } +func (msg *CallMsg) SetGasPrice(price *BigInt) { msg.msg.GasPrice = price.bigint } +func (msg *CallMsg) SetValue(value *BigInt) { msg.msg.Value = value.bigint } +func (msg *CallMsg) SetData(data []byte) { msg.msg.Data = data } +func (msg *CallMsg) SetTo(address *Address) { + if address == nil { + msg.msg.To = nil + } + msg.msg.To = &address.address +} + +// SyncProgress gives progress indications when the node is synchronising with +// the Ethereum network. +type SyncProgress struct { + progress ethereum.SyncProgress +} + +func (p *SyncProgress) GetStartingBlock() int64 { return int64(p.progress.StartingBlock) } +func (p *SyncProgress) GetCurrentBlock() int64 { return int64(p.progress.CurrentBlock) } +func (p *SyncProgress) GetHighestBlock() int64 { return int64(p.progress.HighestBlock) } +func (p *SyncProgress) GetPulledStates() int64 { return int64(p.progress.PulledStates) } +func (p *SyncProgress) GetKnownStates() int64 { return int64(p.progress.KnownStates) } + +// Topics is a set of topic lists to filter events with. +type Topics struct{ topics [][]common.Hash } + +// Size returns the number of topic lists inside the set +func (t *Topics) Size() int { + return len(t.topics) +} + +// Get returns the topic list at the given index from the slice. +func (t *Topics) Get(index int) (*Hashes, error) { + if index < 0 || index >= len(t.topics) { + return nil, errors.New("index out of bounds") + } + return &Hashes{t.topics[index]}, nil +} + +// Set sets the topic list at the given index in the slice. +func (t *Topics) Set(index int, topics *Hashes) error { + if index < 0 || index >= len(t.topics) { + return errors.New("index out of bounds") + } + t.topics[index] = topics.hashes + return nil +} + +// FilterQuery contains options for contact log filtering. +type FilterQuery struct { + query ethereum.FilterQuery +} + +// NewFilterQuery creates an empty filter query for contact log filtering. +func NewFilterQuery() *FilterQuery { + return new(FilterQuery) +} + +func (fq *FilterQuery) GetFromBlock() *BigInt { return &BigInt{fq.query.FromBlock} } +func (fq *FilterQuery) GetToBlock() *BigInt { return &BigInt{fq.query.ToBlock} } +func (fq *FilterQuery) GetAddresses() *Addresses { return &Addresses{fq.query.Addresses} } +func (fq *FilterQuery) GetTopics() *Topics { return &Topics{fq.query.Topics} } diff --git a/mobile/geth.go b/mobile/geth.go new file mode 100644 index 0000000000..d7f0800e0b --- /dev/null +++ b/mobile/geth.go @@ -0,0 +1,200 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains all the wrappers from the node package to support client side node +// management on mobile platforms. + +package geth + +import ( + "fmt" + "math/big" + "path/filepath" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/ethclient" + "github.com/ethereum/go-ethereum/les" + "github.com/ethereum/go-ethereum/light" + "github.com/ethereum/go-ethereum/node" + "github.com/ethereum/go-ethereum/p2p/nat" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/whisper/whisperv2" +) + +// NodeConfig represents the collection of configuration values to fine tune the Geth +// node embedded into a mobile process. The available values are a subset of the +// entire API provided by go-ethereum to reduce the maintenance surface and dev +// complexity. +type NodeConfig struct { + // Bootstrap nodes used to establish connectivity with the rest of the network. + BootstrapNodes *Enodes + + // MaxPeers is the maximum number of peers that can be connected. If this is + // set to zero, then only the configured static and trusted peers can connect. + MaxPeers int + + // EthereumEnabled specifies whether the node should run the Ethereum protocol. + EthereumEnabled bool + + // EthereumNetworkID is the network identifier used by the Ethereum protocol to + // decide if remote peers should be accepted or not. + EthereumNetworkID int + + // EthereumChainConfig is the default parameters of the blockchain to use. If no + // configuration is specified, it defaults to the main network. + EthereumChainConfig *ChainConfig + + // EthereumGenesis is the genesis JSON to use to seed the blockchain with. An + // empty genesis state is equivalent to using the mainnet's state. + EthereumGenesis string + + // EthereumTestnetNonces specifies whether to use account nonces from the testnet + // range (2^20) or from the mainnet one (0). + EthereumTestnetNonces bool + + // EthereumDatabaseCache is the system memory in MB to allocate for database caching. + // A minimum of 16MB is always reserved. + EthereumDatabaseCache int + + // WhisperEnabled specifies whether the node should run the Whisper protocol. + WhisperEnabled bool +} + +// defaultNodeConfig contains the default node configuration values to use if all +// or some fields are missing from the user's specified list. +var defaultNodeConfig = &NodeConfig{ + BootstrapNodes: FoundationBootnodes(), + MaxPeers: 25, + EthereumEnabled: true, + EthereumNetworkID: 1, + EthereumChainConfig: MainnetChainConfig(), + EthereumDatabaseCache: 16, +} + +// NewNodeConfig creates a new node option set, initialized to the default values. +func NewNodeConfig() *NodeConfig { + config := *defaultNodeConfig + return &config +} + +// Node represents a Geth Ethereum node instance. +type Node struct { + node *node.Node +} + +// NewNode creates and configures a new Geth node. +func NewNode(datadir string, config *NodeConfig) (*Node, error) { + // If no or partial configurations were specified, use defaults + if config == nil { + config = NewNodeConfig() + } + if config.MaxPeers == 0 { + config.MaxPeers = defaultNodeConfig.MaxPeers + } + if config.BootstrapNodes == nil || config.BootstrapNodes.Size() == 0 { + config.BootstrapNodes = defaultNodeConfig.BootstrapNodes + } + // Create the empty networking stack + nodeConf := &node.Config{ + Name: clientIdentifier, + DataDir: datadir, + KeyStoreDir: filepath.Join(datadir, "keystore"), // Mobile should never use internal keystores! + NoDiscovery: true, + DiscoveryV5: true, + DiscoveryV5Addr: ":0", + BootstrapNodesV5: config.BootstrapNodes.nodes, + ListenAddr: ":0", + NAT: nat.Any(), + MaxPeers: config.MaxPeers, + } + stack, err := node.New(nodeConf) + if err != nil { + return nil, err + } + // Register the Ethereum protocol if requested + if config.EthereumEnabled { + ethConf := ð.Config{ + ChainConfig: ¶ms.ChainConfig{ + HomesteadBlock: big.NewInt(config.EthereumChainConfig.HomesteadBlock), + DAOForkBlock: big.NewInt(config.EthereumChainConfig.DAOForkBlock), + DAOForkSupport: config.EthereumChainConfig.DAOForkSupport, + EIP150Block: big.NewInt(config.EthereumChainConfig.EIP150Block), + EIP150Hash: config.EthereumChainConfig.EIP150Hash.hash, + EIP155Block: big.NewInt(config.EthereumChainConfig.EIP155Block), + EIP158Block: big.NewInt(config.EthereumChainConfig.EIP158Block), + }, + Genesis: config.EthereumGenesis, + LightMode: true, + DatabaseCache: config.EthereumDatabaseCache, + NetworkId: config.EthereumNetworkID, + GasPrice: new(big.Int).Mul(big.NewInt(20), common.Shannon), + GpoMinGasPrice: new(big.Int).Mul(big.NewInt(20), common.Shannon), + GpoMaxGasPrice: new(big.Int).Mul(big.NewInt(500), common.Shannon), + GpoFullBlockRatio: 80, + GpobaseStepDown: 10, + GpobaseStepUp: 100, + GpobaseCorrectionFactor: 110, + } + if config.EthereumTestnetNonces { + state.StartingNonce = 1048576 // (2**20) + light.StartingNonce = 1048576 // (2**20) + } + if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) { + return les.New(ctx, ethConf) + }); err != nil { + return nil, fmt.Errorf("ethereum init: %v", err) + } + } + // Register the Whisper protocol if requested + if config.WhisperEnabled { + if err := stack.Register(func(*node.ServiceContext) (node.Service, error) { return whisperv2.New(), nil }); err != nil { + return nil, fmt.Errorf("whisper init: %v", err) + } + } + return &Node{stack}, nil +} + +// Start creates a live P2P node and starts running it. +func (n *Node) Start() error { + return n.node.Start() +} + +// Stop terminates a running node along with all it's services. In the node was +// not started, an error is returned. +func (n *Node) Stop() error { + return n.node.Stop() +} + +// GetEthereumClient retrieves a client to access the Ethereum subsystem. +func (n *Node) GetEthereumClient() (*EthereumClient, error) { + rpc, err := n.node.Attach() + if err != nil { + return nil, err + } + return &EthereumClient{ethclient.NewClient(rpc)}, nil +} + +// GetNodeInfo gathers and returns a collection of metadata known about the host. +func (n *Node) GetNodeInfo() *NodeInfo { + return &NodeInfo{n.node.Server().NodeInfo()} +} + +// GetPeersInfo returns an array of metadata objects describing connected peers. +func (n *Node) GetPeersInfo() *PeerInfos { + return &PeerInfos{n.node.Server().PeersInfo()} +} diff --git a/mobile/geth_android.go b/mobile/geth_android.go new file mode 100644 index 0000000000..8e4ebe638f --- /dev/null +++ b/mobile/geth_android.go @@ -0,0 +1,22 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// +build android + +package geth + +// clientIdentifier is a hard coded identifier to report into the network. +var clientIdentifier = "GethDroid" diff --git a/mobile/geth_ios.go b/mobile/geth_ios.go new file mode 100644 index 0000000000..307cd08580 --- /dev/null +++ b/mobile/geth_ios.go @@ -0,0 +1,22 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// +build ios + +package geth + +// clientIdentifier is a hard coded identifier to report into the network. +var clientIdentifier = "iGeth" diff --git a/mobile/geth_other.go b/mobile/geth_other.go new file mode 100644 index 0000000000..6f0c5dda68 --- /dev/null +++ b/mobile/geth_other.go @@ -0,0 +1,22 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// +build !android,!ios + +package geth + +// clientIdentifier is a hard coded identifier to report into the network. +var clientIdentifier = "GethMobile" diff --git a/mobile/init.go b/mobile/init.go new file mode 100644 index 0000000000..0fbc6bd3e8 --- /dev/null +++ b/mobile/init.go @@ -0,0 +1,35 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains initialization code for the mbile library. + +package geth + +import ( + "runtime" + + "github.com/ethereum/go-ethereum/logger" + "github.com/ethereum/go-ethereum/logger/glog" +) + +func init() { + // Initialize the logger + glog.SetV(logger.Info) + glog.SetToStderr(true) + + // Initialize the goroutine count + runtime.GOMAXPROCS(runtime.NumCPU()) +} diff --git a/mobile/interface.go b/mobile/interface.go new file mode 100644 index 0000000000..b585b86420 --- /dev/null +++ b/mobile/interface.go @@ -0,0 +1,148 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains perverted wrappers to allow crossing over empty interfaces. + +package geth + +import ( + "errors" + "math/big" + + "github.com/ethereum/go-ethereum/common" +) + +// Interface represents a wrapped version of Go's interface{}, with the capacity +// to store arbitrary data types. +// +// Since it's impossible to get the arbitrary-ness converted between Go and mobile +// platforms, we're using explicit getters and setters for the conversions. There +// is of course no point in enumerating everything, just enough to support the +// contract bindins requiring client side generated code. +type Interface struct { + object interface{} +} + +// NewInterface creates a new empty interface that can be used to pass around +// generic types. +func NewInterface() *Interface { + return new(Interface) +} + +func (i *Interface) SetBool(b bool) { i.object = &b } +func (i *Interface) SetBools(bs []bool) { i.object = &bs } +func (i *Interface) SetString(str string) { i.object = &str } +func (i *Interface) SetStrings(strs *Strings) { i.object = &strs.strs } +func (i *Interface) SetBinary(binary []byte) { i.object = &binary } +func (i *Interface) SetBinaries(binaries [][]byte) { i.object = &binaries } +func (i *Interface) SetAddress(address *Address) { i.object = &address.address } +func (i *Interface) SetAddresses(addrs *Addresses) { i.object = &addrs.addresses } +func (i *Interface) SetHash(hash *Hash) { i.object = &hash.hash } +func (i *Interface) SetHashes(hashes *Hashes) { i.object = &hashes.hashes } +func (i *Interface) SetInt8(n int8) { i.object = &n } +func (i *Interface) SetInt16(n int16) { i.object = &n } +func (i *Interface) SetInt32(n int32) { i.object = &n } +func (i *Interface) SetInt64(n int64) { i.object = &n } +func (i *Interface) SetUint8(bigint *BigInt) { n := uint8(bigint.bigint.Uint64()); i.object = &n } +func (i *Interface) SetUint16(bigint *BigInt) { n := uint16(bigint.bigint.Uint64()); i.object = &n } +func (i *Interface) SetUint32(bigint *BigInt) { n := uint32(bigint.bigint.Uint64()); i.object = &n } +func (i *Interface) SetUint64(bigint *BigInt) { n := uint64(bigint.bigint.Uint64()); i.object = &n } +func (i *Interface) SetBigInt(bigint *BigInt) { i.object = &bigint.bigint } +func (i *Interface) SetBigInts(bigints *BigInts) { i.object = &bigints.bigints } + +func (i *Interface) SetDefaultBool() { i.object = new(bool) } +func (i *Interface) SetDefaultBools() { i.object = new([]bool) } +func (i *Interface) SetDefaultString() { i.object = new(string) } +func (i *Interface) SetDefaultStrings() { i.object = new([]string) } +func (i *Interface) SetDefaultBinary() { i.object = new([]byte) } +func (i *Interface) SetDefaultBinaries() { i.object = new([][]byte) } +func (i *Interface) SetDefaultAddress() { i.object = new(common.Address) } +func (i *Interface) SetDefaultAddresses() { i.object = new([]common.Address) } +func (i *Interface) SetDefaultHash() { i.object = new(common.Hash) } +func (i *Interface) SetDefaultHashes() { i.object = new([]common.Hash) } +func (i *Interface) SetDefaultInt8() { i.object = new(int8) } +func (i *Interface) SetDefaultInt16() { i.object = new(int16) } +func (i *Interface) SetDefaultInt32() { i.object = new(int32) } +func (i *Interface) SetDefaultInt64() { i.object = new(int64) } +func (i *Interface) SetDefaultUint8() { i.object = new(uint8) } +func (i *Interface) SetDefaultUint16() { i.object = new(uint16) } +func (i *Interface) SetDefaultUint32() { i.object = new(uint32) } +func (i *Interface) SetDefaultUint64() { i.object = new(uint64) } +func (i *Interface) SetDefaultBigInt() { i.object = new(*big.Int) } +func (i *Interface) SetDefaultBigInts() { i.object = new([]*big.Int) } + +func (i *Interface) GetBool() bool { return *i.object.(*bool) } +func (i *Interface) GetBools() []bool { return *i.object.(*[]bool) } +func (i *Interface) GetString() string { return *i.object.(*string) } +func (i *Interface) GetStrings() *Strings { return &Strings{*i.object.(*[]string)} } +func (i *Interface) GetBinary() []byte { return *i.object.(*[]byte) } +func (i *Interface) GetBinaries() [][]byte { return *i.object.(*[][]byte) } +func (i *Interface) GetAddress() *Address { return &Address{*i.object.(*common.Address)} } +func (i *Interface) GetAddresses() *Addresses { return &Addresses{*i.object.(*[]common.Address)} } +func (i *Interface) GetHash() *Hash { return &Hash{*i.object.(*common.Hash)} } +func (i *Interface) GetHashes() *Hashes { return &Hashes{*i.object.(*[]common.Hash)} } +func (i *Interface) GetInt8() int8 { return *i.object.(*int8) } +func (i *Interface) GetInt16() int16 { return *i.object.(*int16) } +func (i *Interface) GetInt32() int32 { return *i.object.(*int32) } +func (i *Interface) GetInt64() int64 { return *i.object.(*int64) } +func (i *Interface) GetUint8() *BigInt { + return &BigInt{new(big.Int).SetUint64(uint64(*i.object.(*uint8)))} +} +func (i *Interface) GetUint16() *BigInt { + return &BigInt{new(big.Int).SetUint64(uint64(*i.object.(*uint16)))} +} +func (i *Interface) GetUint32() *BigInt { + return &BigInt{new(big.Int).SetUint64(uint64(*i.object.(*uint32)))} +} +func (i *Interface) GetUint64() *BigInt { + return &BigInt{new(big.Int).SetUint64(*i.object.(*uint64))} +} +func (i *Interface) GetBigInt() *BigInt { return &BigInt{*i.object.(**big.Int)} } +func (i *Interface) GetBigInts() *BigInts { return &BigInts{*i.object.(*[]*big.Int)} } + +// Interfaces is a slices of wrapped generic objects. +type Interfaces struct { + objects []interface{} +} + +// NewInterfaces creates a slice of uninitialized interfaces. +func NewInterfaces(size int) *Interfaces { + return &Interfaces{ + objects: make([]interface{}, size), + } +} + +// Size returns the number of interfaces in the slice. +func (i *Interfaces) Size() int { + return len(i.objects) +} + +// Get returns the bigint at the given index from the slice. +func (i *Interfaces) Get(index int) (*Interface, error) { + if index < 0 || index >= len(i.objects) { + return nil, errors.New("index out of bounds") + } + return &Interface{i.objects[index]}, nil +} + +// Set sets the big int at the given index in the slice. +func (i *Interfaces) Set(index int, object *Interface) error { + if index < 0 || index >= len(i.objects) { + return errors.New("index out of bounds") + } + i.objects[index] = object.object + return nil +} diff --git a/mobile/p2p.go b/mobile/p2p.go new file mode 100644 index 0000000000..97ae626dc2 --- /dev/null +++ b/mobile/p2p.go @@ -0,0 +1,74 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received pi copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains wrappers for the p2p package. + +package geth + +import ( + "errors" + + "github.com/ethereum/go-ethereum/p2p" +) + +// NodeInfo represents pi short summary of the information known about the host. +type NodeInfo struct { + info *p2p.NodeInfo +} + +func (ni *NodeInfo) GetID() string { return ni.info.ID } +func (ni *NodeInfo) GetName() string { return ni.info.Name } +func (ni *NodeInfo) GetEnode() string { return ni.info.Enode } +func (ni *NodeInfo) GetIP() string { return ni.info.IP } +func (ni *NodeInfo) GetDiscoveryPort() int { return ni.info.Ports.Discovery } +func (ni *NodeInfo) GetListenerPort() int { return ni.info.Ports.Listener } +func (ni *NodeInfo) GetListenerAddress() string { return ni.info.ListenAddr } +func (ni *NodeInfo) GetProtocols() *Strings { + protos := []string{} + for proto, _ := range ni.info.Protocols { + protos = append(protos, proto) + } + return &Strings{protos} +} + +// PeerInfo represents pi short summary of the information known about pi connected peer. +type PeerInfo struct { + info *p2p.PeerInfo +} + +func (pi *PeerInfo) GetID() string { return pi.info.ID } +func (pi *PeerInfo) GetName() string { return pi.info.Name } +func (pi *PeerInfo) GetCaps() *Strings { return &Strings{pi.info.Caps} } +func (pi *PeerInfo) GetLocalAddress() string { return pi.info.Network.LocalAddress } +func (pi *PeerInfo) GetRemoteAddress() string { return pi.info.Network.RemoteAddress } + +// PeerInfos represents a slice of infos about remote peers. +type PeerInfos struct { + infos []*p2p.PeerInfo +} + +// Size returns the number of peer info entries in the slice. +func (pi *PeerInfos) Size() int { + return len(pi.infos) +} + +// Get returns the peer info at the given index from the slice. +func (pi *PeerInfos) Get(index int) (*PeerInfo, error) { + if index < 0 || index >= len(pi.infos) { + return nil, errors.New("index out of bounds") + } + return &PeerInfo{pi.infos[index]}, nil +} diff --git a/mobile/params.go b/mobile/params.go new file mode 100644 index 0000000000..48344a5387 --- /dev/null +++ b/mobile/params.go @@ -0,0 +1,89 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains all the wrappers from the params package. + +package geth + +import ( + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/p2p/discv5" + "github.com/ethereum/go-ethereum/params" +) + +// MainnetChainConfig returns the chain configurations for the main Ethereum network. +func MainnetChainConfig() *ChainConfig { + return &ChainConfig{ + HomesteadBlock: params.MainNetHomesteadBlock.Int64(), + DAOForkBlock: params.MainNetDAOForkBlock.Int64(), + DAOForkSupport: true, + EIP150Block: params.MainNetHomesteadGasRepriceBlock.Int64(), + EIP150Hash: Hash{params.MainNetHomesteadGasRepriceHash}, + EIP155Block: params.MainNetSpuriousDragon.Int64(), + EIP158Block: params.MainNetSpuriousDragon.Int64(), + } +} + +// MainnetGenesis returns the JSON spec to use for the main Ethereum network. It +// is actually empty since that defaults to the hard coded binary genesis block. +func MainnetGenesis() string { + return "" +} + +// TestnetChainConfig returns the chain configurations for the Ethereum test network. +func TestnetChainConfig() *ChainConfig { + return &ChainConfig{ + HomesteadBlock: params.TestNetHomesteadBlock.Int64(), + DAOForkBlock: 0, + DAOForkSupport: false, + EIP150Block: params.TestNetHomesteadGasRepriceBlock.Int64(), + EIP150Hash: Hash{params.TestNetHomesteadGasRepriceHash}, + EIP155Block: params.TestNetSpuriousDragon.Int64(), + EIP158Block: params.TestNetSpuriousDragon.Int64(), + } +} + +// TestnetGenesis returns the JSON spec to use for the Ethereum test network. +func TestnetGenesis() string { + return core.TestNetGenesisBlock() +} + +// ChainConfig is the core config which determines the blockchain settings. +type ChainConfig struct { + HomesteadBlock int64 // Homestead switch block + DAOForkBlock int64 // TheDAO hard-fork switch block + DAOForkSupport bool // Whether the nodes supports or opposes the DAO hard-fork + EIP150Block int64 // Homestead gas reprice switch block + EIP150Hash Hash // Homestead gas reprice switch block hash + EIP155Block int64 // Replay protection switch block + EIP158Block int64 // Empty account pruning switch block +} + +// NewChainConfig creates a new chain configuration that transitions immediately +// to homestead and has no notion of the DAO fork (ideal for a private network). +func NewChainConfig() *ChainConfig { + return new(ChainConfig) +} + +// FoundationBootnodes returns the enode URLs of the P2P bootstrap nodes operated +// by the foundation running the V5 discovery protocol. +func FoundationBootnodes() *Enodes { + nodes := &Enodes{nodes: make([]*discv5.Node, len(params.DiscoveryV5Bootnodes))} + for i, node := range params.DiscoveryV5Bootnodes { + nodes.nodes[i] = node + } + return nodes +} diff --git a/mobile/primitives.go b/mobile/primitives.go new file mode 100644 index 0000000000..28f402d4f1 --- /dev/null +++ b/mobile/primitives.go @@ -0,0 +1,54 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received s copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains various wrappers for primitive types. + +package geth + +import ( + "errors" + "fmt" +) + +// Strings represents s slice of strs. +type Strings struct{ strs []string } + +// Size returns the number of strs in the slice. +func (s *Strings) Size() int { + return len(s.strs) +} + +// Get returns the string at the given index from the slice. +func (s *Strings) Get(index int) (string, error) { + if index < 0 || index >= len(s.strs) { + return "", errors.New("index out of bounds") + } + return s.strs[index], nil +} + +// Set sets the string at the given index in the slice. +func (s *Strings) Set(index int, str string) error { + if index < 0 || index >= len(s.strs) { + return errors.New("index out of bounds") + } + s.strs[index] = str + return nil +} + +// String implements the Stringer interface. +func (s *Strings) String() string { + return fmt.Sprintf("%v", s.strs) +} diff --git a/mobile/types.go b/mobile/types.go new file mode 100644 index 0000000000..bb5ccc6251 --- /dev/null +++ b/mobile/types.go @@ -0,0 +1,189 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains all the wrappers from the core/types package. + +package geth + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/core/types" +) + +// A Nonce is a 64-bit hash which proves (combined with the mix-hash) that +// a sufficient amount of computation has been carried out on a block. +type Nonce struct { + nonce types.BlockNonce +} + +// GetBytes retrieves the byte representation of the block nonce. +func (n *Nonce) GetBytes() []byte { + return n.nonce[:] +} + +// GetHex retrieves the hex string representation of the block nonce. +func (n *Nonce) GetHex() string { + return fmt.Sprintf("0x%x", n.nonce[:]) +} + +// Bloom represents a 256 bit bloom filter. +type Bloom struct { + bloom types.Bloom +} + +// GetBytes retrieves the byte representation of the bloom filter. +func (b *Bloom) GetBytes() []byte { + return b.bloom[:] +} + +// GetHex retrieves the hex string representation of the bloom filter. +func (b *Bloom) GetHex() string { + return fmt.Sprintf("0x%x", b.bloom[:]) +} + +// Header represents a block header in the Ethereum blockchain. +type Header struct { + header *types.Header +} + +func (h *Header) GetParentHash() *Hash { return &Hash{h.header.ParentHash} } +func (h *Header) GetUncleHash() *Hash { return &Hash{h.header.UncleHash} } +func (h *Header) GetCoinbase() *Address { return &Address{h.header.Coinbase} } +func (h *Header) GetRoot() *Hash { return &Hash{h.header.Root} } +func (h *Header) GetTxHash() *Hash { return &Hash{h.header.TxHash} } +func (h *Header) GetReceiptHash() *Hash { return &Hash{h.header.ReceiptHash} } +func (h *Header) GetBloom() *Bloom { return &Bloom{h.header.Bloom} } +func (h *Header) GetDifficulty() *BigInt { return &BigInt{h.header.Difficulty} } +func (h *Header) GetNumber() int64 { return h.header.Number.Int64() } +func (h *Header) GetGasLimit() int64 { return h.header.GasLimit.Int64() } +func (h *Header) GetGasUsed() int64 { return h.header.GasUsed.Int64() } +func (h *Header) GetTime() int64 { return h.header.Time.Int64() } +func (h *Header) GetExtra() []byte { return h.header.Extra } +func (h *Header) GetMixDigest() *Hash { return &Hash{h.header.MixDigest} } +func (h *Header) GetNonce() *Nonce { return &Nonce{h.header.Nonce} } + +func (h *Header) GetHash() *Hash { return &Hash{h.header.Hash()} } +func (h *Header) GetHashNoNonce() *Hash { return &Hash{h.header.HashNoNonce()} } + +// Headers represents a slice of headers. +type Headers struct{ headers []*types.Header } + +// Size returns the number of headers in the slice. +func (h *Headers) Size() int { + return len(h.headers) +} + +// Get returns the header at the given index from the slice. +func (h *Headers) Get(index int) (*Header, error) { + if index < 0 || index >= len(h.headers) { + return nil, errors.New("index out of bounds") + } + return &Header{h.headers[index]}, nil +} + +// Block represents an entire block in the Ethereum blockchain. +type Block struct { + block *types.Block +} + +func (b *Block) GetParentHash() *Hash { return &Hash{b.block.ParentHash()} } +func (b *Block) GetUncleHash() *Hash { return &Hash{b.block.UncleHash()} } +func (b *Block) GetCoinbase() *Address { return &Address{b.block.Coinbase()} } +func (b *Block) GetRoot() *Hash { return &Hash{b.block.Root()} } +func (b *Block) GetTxHash() *Hash { return &Hash{b.block.TxHash()} } +func (b *Block) GetReceiptHash() *Hash { return &Hash{b.block.ReceiptHash()} } +func (b *Block) GetBloom() *Bloom { return &Bloom{b.block.Bloom()} } +func (b *Block) GetDifficulty() *BigInt { return &BigInt{b.block.Difficulty()} } +func (b *Block) GetNumber() int64 { return b.block.Number().Int64() } +func (b *Block) GetGasLimit() int64 { return b.block.GasLimit().Int64() } +func (b *Block) GetGasUsed() int64 { return b.block.GasUsed().Int64() } +func (b *Block) GetTime() int64 { return b.block.Time().Int64() } +func (b *Block) GetExtra() []byte { return b.block.Extra() } +func (b *Block) GetMixDigest() *Hash { return &Hash{b.block.MixDigest()} } +func (b *Block) GetNonce() int64 { return int64(b.block.Nonce()) } + +func (b *Block) GetHash() *Hash { return &Hash{b.block.Hash()} } +func (b *Block) GetHashNoNonce() *Hash { return &Hash{b.block.HashNoNonce()} } + +func (b *Block) GetHeader() *Header { return &Header{b.block.Header()} } +func (b *Block) GetUncles() *Headers { return &Headers{b.block.Uncles()} } +func (b *Block) GetTransactions() *Transactions { return &Transactions{b.block.Transactions()} } +func (b *Block) GetTransaction(hash *Hash) *Transaction { + return &Transaction{b.block.Transaction(hash.hash)} +} + +// Transaction represents a single Ethereum transaction. +type Transaction struct { + tx *types.Transaction +} + +func (tx *Transaction) GetData() []byte { return tx.tx.Data() } +func (tx *Transaction) GetGas() int64 { return tx.tx.Gas().Int64() } +func (tx *Transaction) GetGasPrice() *BigInt { return &BigInt{tx.tx.GasPrice()} } +func (tx *Transaction) GetValue() *BigInt { return &BigInt{tx.tx.Value()} } +func (tx *Transaction) GetNonce() int64 { return int64(tx.tx.Nonce()) } + +func (tx *Transaction) GetHash() *Hash { return &Hash{tx.tx.Hash()} } +func (tx *Transaction) GetSigHash() *Hash { return &Hash{tx.tx.SigHash(types.HomesteadSigner{})} } +func (tx *Transaction) GetCost() *BigInt { return &BigInt{tx.tx.Cost()} } + +func (tx *Transaction) GetFrom() (*Address, error) { + from, err := types.Sender(types.HomesteadSigner{}, tx.tx) + return &Address{from}, err +} + +func (tx *Transaction) GetTo() *Address { + if to := tx.tx.To(); to != nil { + return &Address{*to} + } + return nil +} + +func (tx *Transaction) WithSignature(sig []byte) (*Transaction, error) { + t, err := tx.tx.WithSignature(types.HomesteadSigner{}, sig) + return &Transaction{t}, err +} + +// Transactions represents a slice of transactions. +type Transactions struct{ txs types.Transactions } + +// Size returns the number of transactions in the slice. +func (t *Transactions) Size() int { + return len(t.txs) +} + +// Get returns the transaction at the given index from the slice. +func (t *Transactions) Get(index int) (*Transaction, error) { + if index < 0 || index >= len(t.txs) { + return nil, errors.New("index out of bounds") + } + return &Transaction{t.txs[index]}, nil +} + +// Receipt represents the results of a transaction. +type Receipt struct { + receipt *types.Receipt +} + +func (r *Receipt) GetPostState() []byte { return r.receipt.PostState } +func (r *Receipt) GetCumulativeGasUsed() *BigInt { return &BigInt{r.receipt.CumulativeGasUsed} } +func (r *Receipt) GetBloom() *Bloom { return &Bloom{r.receipt.Bloom} } +func (r *Receipt) GetLogs() *Logs { return &Logs{r.receipt.Logs} } +func (r *Receipt) GetTxHash() *Hash { return &Hash{r.receipt.TxHash} } +func (r *Receipt) GetContractAddress() *Address { return &Address{r.receipt.ContractAddress} } +func (r *Receipt) GetGasUsed() *BigInt { return &BigInt{r.receipt.GasUsed} } diff --git a/mobile/vm.go b/mobile/vm.go new file mode 100644 index 0000000000..a68917ca65 --- /dev/null +++ b/mobile/vm.go @@ -0,0 +1,56 @@ +// Copyright 2016 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +// Contains all the wrappers from the core/types package. + +package geth + +import ( + "errors" + + "github.com/ethereum/go-ethereum/core/vm" +) + +// Log represents a contract log event. These events are generated by the LOG +// opcode and stored/indexed by the node. +type Log struct { + log *vm.Log +} + +func (l *Log) GetAddress() *Address { return &Address{l.log.Address} } +func (l *Log) GetTopics() *Hashes { return &Hashes{l.log.Topics} } +func (l *Log) GetData() []byte { return l.log.Data } +func (l *Log) GetBlockNumber() int64 { return int64(l.log.BlockNumber) } +func (l *Log) GetTxHash() *Hash { return &Hash{l.log.TxHash} } +func (l *Log) GetTxIndex() int { return int(l.log.TxIndex) } +func (l *Log) GetBlockHash() *Hash { return &Hash{l.log.BlockHash} } +func (l *Log) GetIndex() int { return int(l.log.Index) } + +// Logs represents a slice of VM logs. +type Logs struct{ logs vm.Logs } + +// Size returns the number of logs in the slice. +func (l *Logs) Size() int { + return len(l.logs) +} + +// Get returns the log at the given index from the slice. +func (l *Logs) Get(index int) (*Log, error) { + if index < 0 || index >= len(l.logs) { + return nil, errors.New("index out of bounds") + } + return &Log{l.logs[index]}, nil +} diff --git a/node/config.go b/node/config.go index dbefcb8a56..62655f2370 100644 --- a/node/config.go +++ b/node/config.go @@ -32,6 +32,7 @@ import ( "github.com/ethereum/go-ethereum/logger" "github.com/ethereum/go-ethereum/logger/glog" "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/p2p/discv5" "github.com/ethereum/go-ethereum/p2p/nat" ) @@ -95,16 +96,23 @@ type Config struct { // or not. Disabling is usually useful for protocol debugging (manual topology). NoDiscovery bool + // DiscoveryV5 specifies whether the the new topic-discovery based V5 discovery + // protocol should be started or not. DiscoveryV5 bool - // Bootstrap nodes used to establish connectivity with the rest of the network. + // Listener address for the V5 discovery protocol UDP traffic. + DiscoveryV5Addr string + + // BootstrapNodes used to establish connectivity with the rest of the network. BootstrapNodes []*discover.Node + // BootstrapNodesV5 used to establish connectivity with the rest of the network + // using the V5 discovery protocol. + BootstrapNodesV5 []*discv5.Node + // Network interface address on which the node should listen for inbound peers. ListenAddr string - ListenAddrV5 string - // If set to a non-nil value, the given NAT port mapper is used to make the // listening port available to the Internet. NAT nat.Interface diff --git a/node/node.go b/node/node.go index fb11696faa..d49ae3a457 100644 --- a/node/node.go +++ b/node/node.go @@ -154,21 +154,22 @@ func (n *Node) Start() error { // Initialize the p2p server. This creates the node key and // discovery databases. n.serverConfig = p2p.Config{ - PrivateKey: n.config.NodeKey(), - Name: n.config.NodeName(), - Discovery: !n.config.NoDiscovery, - DiscoveryV5: n.config.DiscoveryV5, - BootstrapNodes: n.config.BootstrapNodes, - StaticNodes: n.config.StaticNodes(), - TrustedNodes: n.config.TrusterNodes(), - NodeDatabase: n.config.NodeDB(), - ListenAddr: n.config.ListenAddr, - ListenAddrV5: n.config.ListenAddrV5, - NAT: n.config.NAT, - Dialer: n.config.Dialer, - NoDial: n.config.NoDial, - MaxPeers: n.config.MaxPeers, - MaxPendingPeers: n.config.MaxPendingPeers, + PrivateKey: n.config.NodeKey(), + Name: n.config.NodeName(), + Discovery: !n.config.NoDiscovery, + DiscoveryV5: n.config.DiscoveryV5, + DiscoveryV5Addr: n.config.DiscoveryV5Addr, + BootstrapNodes: n.config.BootstrapNodes, + BootstrapNodesV5: n.config.BootstrapNodesV5, + StaticNodes: n.config.StaticNodes(), + TrustedNodes: n.config.TrusterNodes(), + NodeDatabase: n.config.NodeDB(), + ListenAddr: n.config.ListenAddr, + NAT: n.config.NAT, + Dialer: n.config.Dialer, + NoDial: n.config.NoDial, + MaxPeers: n.config.MaxPeers, + MaxPendingPeers: n.config.MaxPendingPeers, } running := &p2p.Server{Config: n.serverConfig} glog.V(logger.Info).Infoln("instance:", n.serverConfig.Name) diff --git a/p2p/discover/node.go b/p2p/discover/node.go index 139a95d803..eec0bae0c4 100644 --- a/p2p/discover/node.go +++ b/p2p/discover/node.go @@ -35,7 +35,7 @@ import ( "github.com/ethereum/go-ethereum/crypto/secp256k1" ) -const nodeIDBits = 512 +const NodeIDBits = 512 // Node represents a host on the network. // The fields of Node may not be modified. @@ -209,7 +209,7 @@ func MustParseNode(rawurl string) *Node { // NodeID is a unique identifier for each node. // The node identifier is a marshaled elliptic curve public key. -type NodeID [nodeIDBits / 8]byte +type NodeID [NodeIDBits / 8]byte // NodeID prints as a long hexadecimal number. func (n NodeID) String() string { diff --git a/p2p/discv5/net.go b/p2p/discv5/net.go index 71eaec3c42..7ad6f1e5b9 100644 --- a/p2p/discv5/net.go +++ b/p2p/discv5/net.go @@ -60,14 +60,6 @@ func debugLog(s string) { } } -// BootNodes are the enode URLs of the P2P bootstrap nodes for the experimental RLPx v5 "Topic Discovery" network -// warning: local bootnodes for testing!!! -var BootNodes = []*Node{ - MustParseNode("enode://0cc5f5ffb5d9098c8b8c62325f3797f56509bff942704687b6530992ac706e2cb946b90a34f1f19548cd3c7baccbcaea354531e5983c7d1bc0dee16ce4b6440b@40.118.3.223:30305"), - MustParseNode("enode://1c7a64d76c0334b0418c004af2f67c50e36a3be60b5e4790bdac0439d21603469a85fad36f2473c9a80eb043ae60936df905fa28f1ff614c3e5dc34f15dcd2dc@40.118.3.223:30308"), - MustParseNode("enode://85c85d7143ae8bb96924f2b54f1b3e70d8c4d367af305325d30a61385a432f247d2c75c45c6b4a60335060d072d7f5b35dd1d4c45f76941f62a4f83b6e75daaf@40.118.3.223:30309"), -} - // Network manages the table and all protocol interaction. type Network struct { db *nodeDB // database of known nodes diff --git a/p2p/server.go b/p2p/server.go index 649fbfb82d..7381127dc9 100644 --- a/p2p/server.go +++ b/p2p/server.go @@ -73,16 +73,26 @@ type Config struct { // or not. Disabling is usually useful for protocol debugging (manual topology). Discovery bool + // DiscoveryV5 specifies whether the the new topic-discovery based V5 discovery + // protocol should be started or not. DiscoveryV5 bool + // Listener address for the V5 discovery protocol UDP traffic. + DiscoveryV5Addr string + // Name sets the node name of this server. // Use common.MakeName to create a name that follows existing conventions. Name string - // Bootstrap nodes are used to establish connectivity + // BootstrapNodes are used to establish connectivity // with the rest of the network. BootstrapNodes []*discover.Node + // BootstrapNodesV5 are used to establish connectivity + // with the rest of the network using the V5 discovery + // protocol. + BootstrapNodesV5 []*discv5.Node + // Static nodes are used as pre-configured connections which are always // maintained and re-connected on disconnects. StaticNodes []*discover.Node @@ -108,8 +118,6 @@ type Config struct { // the server is started. ListenAddr string - ListenAddrV5 string - // If set to a non-nil value, the given NAT port mapper // is used to make the listening port available to the // Internet. @@ -359,11 +367,11 @@ func (srv *Server) Start() (err error) { } if srv.DiscoveryV5 { - ntab, err := discv5.ListenUDP(srv.PrivateKey, srv.ListenAddrV5, srv.NAT, "") //srv.NodeDatabase) + ntab, err := discv5.ListenUDP(srv.PrivateKey, srv.DiscoveryV5Addr, srv.NAT, "") //srv.NodeDatabase) if err != nil { return err } - if err := ntab.SetFallbackNodes(discv5.BootNodes); err != nil { + if err := ntab.SetFallbackNodes(srv.BootstrapNodesV5); err != nil { return err } srv.DiscV5 = ntab @@ -748,7 +756,7 @@ type NodeInfo struct { Protocols map[string]interface{} `json:"protocols"` } -// Info gathers and returns a collection of metadata known about the host. +// NodeInfo gathers and returns a collection of metadata known about the host. func (srv *Server) NodeInfo() *NodeInfo { node := srv.Self() diff --git a/params/bootnodes.go b/params/bootnodes.go new file mode 100644 index 0000000000..830b309d69 --- /dev/null +++ b/params/bootnodes.go @@ -0,0 +1,52 @@ +// Copyright 2015 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see . + +package params + +import ( + "github.com/ethereum/go-ethereum/p2p/discover" + "github.com/ethereum/go-ethereum/p2p/discv5" +) + +// MainnetBootnodes are the enode URLs of the P2P bootstrap nodes running on +// the main Ethereum network. +var MainnetBootnodes = []*discover.Node{ + // ETH/DEV Go Bootnodes + discover.MustParseNode("enode://a979fb575495b8d6db44f750317d0f4622bf4c2aa3365d6af7c284339968eef29b69ad0dce72a4d8db5ebb4968de0e3bec910127f134779fbcb0cb6d3331163c@52.16.188.185:30303"), // IE + discover.MustParseNode("enode://de471bccee3d042261d52e9bff31458daecc406142b401d4cd848f677479f73104b9fdeb090af9583d3391b7f10cb2ba9e26865dd5fca4fcdc0fb1e3b723c786@54.94.239.50:30303"), // BR + discover.MustParseNode("enode://1118980bf48b0a3640bdba04e0fe78b1add18e1cd99bf22d53daac1fd9972ad650df52176e7c7d89d1114cfef2bc23a2959aa54998a46afcf7d91809f0855082@52.74.57.123:30303"), // SG + + // ETH/DEV Cpp Bootnodes + discover.MustParseNode("enode://979b7fa28feeb35a4741660a16076f1943202cb72b6af70d327f053e248bab9ba81760f39d0701ef1d8f89cc1fbd2cacba0710a12cd5314d5e0c9021aa3637f9@5.1.83.226:30303"), +} + +// TestnetBootnodes are the enode URLs of the P2P bootstrap nodes running on the +// Morden test network. +var TestnetBootnodes = []*discover.Node{ + // ETH/DEV Go Bootnodes + discover.MustParseNode("enode://e4533109cc9bd7604e4ff6c095f7a1d807e15b38e9bfeb05d3b7c423ba86af0a9e89abbf40bd9dde4250fef114cd09270fa4e224cbeef8b7bf05a51e8260d6b8@94.242.229.4:40404"), + discover.MustParseNode("enode://8c336ee6f03e99613ad21274f269479bf4413fb294d697ef15ab897598afb931f56beb8e97af530aee20ce2bcba5776f4a312bc168545de4d43736992c814592@94.242.229.203:30303"), + + // ETH/DEV Cpp Bootnodes +} + +// DiscoveryV5Bootnodes are the enode URLs of the P2P bootstrap nodes for the +// experimental RLPx v5 topic-discovery network. +var DiscoveryV5Bootnodes = []*discv5.Node{ + discv5.MustParseNode("enode://0cc5f5ffb5d9098c8b8c62325f3797f56509bff942704687b6530992ac706e2cb946b90a34f1f19548cd3c7baccbcaea354531e5983c7d1bc0dee16ce4b6440b@40.118.3.223:30305"), + discv5.MustParseNode("enode://1c7a64d76c0334b0418c004af2f67c50e36a3be60b5e4790bdac0439d21603469a85fad36f2473c9a80eb043ae60936df905fa28f1ff614c3e5dc34f15dcd2dc@40.118.3.223:30308"), + discv5.MustParseNode("enode://85c85d7143ae8bb96924f2b54f1b3e70d8c4d367af305325d30a61385a432f247d2c75c45c6b4a60335060d072d7f5b35dd1d4c45f76941f62a4f83b6e75daaf@40.118.3.223:30309"), +} diff --git a/params/config.go b/params/config.go index d63236ef83..d27973a329 100644 --- a/params/config.go +++ b/params/config.go @@ -22,6 +22,28 @@ import ( "github.com/ethereum/go-ethereum/common" ) +// MainnetChainConfig is the chain parameters to run a node on the main network. +var MainnetChainConfig = &ChainConfig{ + HomesteadBlock: MainNetHomesteadBlock, + DAOForkBlock: MainNetDAOForkBlock, + DAOForkSupport: true, + EIP150Block: MainNetHomesteadGasRepriceBlock, + EIP150Hash: MainNetHomesteadGasRepriceHash, + EIP155Block: MainNetSpuriousDragon, + EIP158Block: MainNetSpuriousDragon, +} + +// TestnetChainConfig is the chain parameters to run a node on the test network. +var TestnetChainConfig = &ChainConfig{ + HomesteadBlock: TestNetHomesteadBlock, + DAOForkBlock: TestNetDAOForkBlock, + DAOForkSupport: false, + EIP150Block: TestNetHomesteadGasRepriceBlock, + EIP150Hash: TestNetHomesteadGasRepriceHash, + EIP155Block: TestNetSpuriousDragon, + EIP158Block: TestNetSpuriousDragon, +} + // ChainConfig is the core config which determines the blockchain settings. // // ChainConfig is stored in the database on a per block basis. This means