├── vm-service ├── .gitignore ├── wasm │ └── engine_optimized.wasm ├── vm_test.js ├── README.org ├── package.json ├── app.js ├── api.js ├── vm.ts └── vm.js ├── docs ├── thor_workflow.png └── gettingstarted.md ├── thor-core ├── libs │ └── sirius-core.jar ├── src │ ├── test │ │ ├── resources │ │ │ └── engine.wasm │ │ └── kotlin │ │ │ └── org │ │ │ ├── starcoin │ │ │ └── thor │ │ │ │ └── core │ │ │ │ └── arbitrate │ │ │ │ └── ArbitrateTest.kt │ │ │ └── statcoin │ │ │ └── thor │ │ │ └── test │ │ │ └── sign │ │ │ └── SignTest.kt │ └── main │ │ └── kotlin │ │ └── org │ │ └── starcoin │ │ └── thor │ │ ├── core │ │ ├── arbitrate │ │ │ ├── ContractInput.kt │ │ │ ├── Arbitrate.kt │ │ │ ├── ArbitrateData.kt │ │ │ ├── Contract.kt │ │ │ ├── WitnessContractInput.kt │ │ │ ├── ContractImpl.kt │ │ │ └── ArbitrateImpl.kt │ │ ├── UserInfo.kt │ │ ├── MsgObject.kt │ │ ├── GameInfo.kt │ │ ├── Room.kt │ │ └── HttpMsg.kt │ │ └── utils │ │ ├── Extensions.kt │ │ └── ThorUtil.kt └── build.gradle ├── thor-app ├── src │ ├── renderer │ │ ├── assets │ │ │ └── logo.png │ │ ├── vue-shims.d.ts │ │ ├── sdk │ │ │ ├── test-vm-minimal.js │ │ │ ├── GameEngine.ts │ │ │ ├── GameGUI.ts │ │ │ ├── crypto.ts │ │ │ ├── vm-minimal.ts │ │ │ ├── util.ts │ │ │ ├── lightning.ts │ │ │ └── vm.ts │ │ ├── components │ │ │ ├── Msgbus.ts │ │ │ ├── JsonObjView.ts │ │ │ ├── Debug.ts │ │ │ ├── Config.ts │ │ │ ├── storage.ts │ │ │ ├── Gameboard.ts │ │ │ ├── Wallet.ts │ │ │ ├── Solo.ts │ │ │ └── Gamelobby.ts │ │ └── index.ts │ ├── index.ejs │ └── main │ │ └── index.ts ├── .babelrc ├── webpack.renderer.additions.js ├── .editorconfig ├── .gitignore ├── README.md ├── package.json └── tsconfig.json ├── lightning ├── src │ ├── main │ │ └── java │ │ │ └── org │ │ │ └── starcoin │ │ │ └── lightning │ │ │ └── client │ │ │ ├── AsyncClient.java │ │ │ ├── core │ │ │ ├── SignMessageRequest.java │ │ │ ├── SettleInvoiceRequest.java │ │ │ ├── Payment.java │ │ │ ├── Channel.java │ │ │ ├── WalletBalanceResponse.java │ │ │ ├── InvoiceList.java │ │ │ ├── AddInvoiceResponse.java │ │ │ ├── PayReq.java │ │ │ ├── PaymentResponse.java │ │ │ ├── ChannelResponse.java │ │ │ └── Invoice.java │ │ │ ├── AddressFormatException.java │ │ │ ├── HashUtils.java │ │ │ ├── SyncClient.java │ │ │ └── Bech32.java │ └── test │ │ ├── resources │ │ ├── alice.cert │ │ ├── arb.cert │ │ └── bob.cert │ │ └── java │ │ └── org │ │ └── starcoin │ │ └── lightning │ │ └── client │ │ └── UtilsTest.java └── build.gradle ├── thor-arbitrate-service ├── src │ ├── main │ │ ├── resources │ │ │ ├── games │ │ │ │ └── gomoku │ │ │ │ │ ├── gui.wasm │ │ │ │ │ └── engine.wasm │ │ │ └── logback.xml │ │ └── kotlin │ │ │ └── org │ │ │ └── starcoin │ │ │ └── thor │ │ │ ├── server │ │ │ ├── RpcServer.kt │ │ │ ├── GameService.kt │ │ │ ├── PlayService.kt │ │ │ ├── JsonSerializableConverter.kt │ │ │ ├── GameServiceImpl.kt │ │ │ └── Main.kt │ │ │ └── manager │ │ │ ├── GameManager.kt │ │ │ ├── PaymentManager.kt │ │ │ ├── RoomManager.kt │ │ │ └── CommonUserManager.kt │ └── test │ │ └── kotlin │ │ └── org │ │ └── starcoin │ │ └── thor │ │ └── server │ │ ├── JsonTest.kt │ │ └── ArbitrateServerTest.kt ├── README.MD └── build.gradle ├── .gitignore ├── settings.gradle ├── scripts └── lnd │ ├── lnd.sh │ ├── docker │ ├── lnd │ │ ├── Dockerfile │ │ └── start-lnd.sh │ ├── btcd │ │ ├── start-btcctl.sh │ │ ├── Dockerfile │ │ └── start-btcd.sh │ └── docker-compose.yml │ └── util.sh ├── lightning-proto ├── build.gradle └── src │ └── main │ └── proto │ └── invoices.proto ├── gradlew.bat ├── README.md └── gradlew /vm-service/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | yarn.lock -------------------------------------------------------------------------------- /docs/thor_workflow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starcoinorg/thor/HEAD/docs/thor_workflow.png -------------------------------------------------------------------------------- /thor-core/libs/sirius-core.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starcoinorg/thor/HEAD/thor-core/libs/sirius-core.jar -------------------------------------------------------------------------------- /thor-app/src/renderer/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starcoinorg/thor/HEAD/thor-app/src/renderer/assets/logo.png -------------------------------------------------------------------------------- /thor-app/src/renderer/vue-shims.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import Vue from 'vue' 3 | export default Vue 4 | } 5 | -------------------------------------------------------------------------------- /vm-service/wasm/engine_optimized.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starcoinorg/thor/HEAD/vm-service/wasm/engine_optimized.wasm -------------------------------------------------------------------------------- /thor-core/src/test/resources/engine.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starcoinorg/thor/HEAD/thor-core/src/test/resources/engine.wasm -------------------------------------------------------------------------------- /lightning/src/main/java/org/starcoin/lightning/client/AsyncClient.java: -------------------------------------------------------------------------------- 1 | package org.starcoin.lightning.client; 2 | 3 | public class AsyncClient { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /thor-app/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | [ 4 | "env", 5 | { 6 | "modules": false 7 | } 8 | ], 9 | "stage-3" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /thor-arbitrate-service/src/main/resources/games/gomoku/gui.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starcoinorg/thor/HEAD/thor-arbitrate-service/src/main/resources/games/gomoku/gui.wasm -------------------------------------------------------------------------------- /thor-app/src/renderer/sdk/test-vm-minimal.js: -------------------------------------------------------------------------------- 1 | const vm=require("./vm-minimal.js") 2 | vm.init(2) 3 | vm.update(1,2) 4 | vm.isGameOver() 5 | vm.loadState(1) 6 | // vm.getWinner() 7 | -------------------------------------------------------------------------------- /thor-app/webpack.renderer.additions.js: -------------------------------------------------------------------------------- 1 | var HtmlWebpackPlugin = require('html-webpack-plugin'); 2 | module.exports = { 3 | plugins: [ 4 | new HtmlWebpackPlugin() 5 | ] 6 | }; 7 | -------------------------------------------------------------------------------- /thor-arbitrate-service/src/main/resources/games/gomoku/engine.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/starcoinorg/thor/HEAD/thor-arbitrate-service/src/main/resources/games/gomoku/engine.wasm -------------------------------------------------------------------------------- /thor-app/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /thor-app/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | npm-debug.log 5 | yarn-error.log 6 | 7 | # Editor directories and files 8 | .idea 9 | *.suo 10 | *.ntvs* 11 | *.njsproj 12 | *.sln 13 | .cache 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .gradle 3 | gradle 4 | *.iml 5 | *.class 6 | target/ 7 | build 8 | out 9 | logs 10 | */logs 11 | *.ipr 12 | *.iws 13 | .temp 14 | *.DS_Store 15 | *.classpath 16 | *.project 17 | *.settings/ 18 | *bin 19 | -------------------------------------------------------------------------------- /thor-core/src/main/kotlin/org/starcoin/thor/core/arbitrate/ContractInput.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.core.arbitrate 2 | 3 | interface ContractInput:Iterator{ 4 | fun reset() 5 | fun getUser(): String 6 | } 7 | -------------------------------------------------------------------------------- /vm-service/vm_test.js: -------------------------------------------------------------------------------- 1 | const vm = require("./vm.js") 2 | fs = require("fs") 3 | const vmins = vm.createVm(fs.readFileSync("./wasm/engine_optimized.wasm")) 4 | vmins.execute(1, 2, 8) 5 | vmins.execute(3,) 6 | vmins.wasm.exports.update(2, 5) -------------------------------------------------------------------------------- /thor-core/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile 'com.github.kittinunf.fuel:fuel:2.0.1' 3 | compile 'org.bitcoinj:bitcoinj-core:0.15' 4 | compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.0' 5 | 6 | compile project(":lightning") 7 | compile files('libs/sirius-core.jar') 8 | } -------------------------------------------------------------------------------- /thor-app/src/renderer/sdk/GameEngine.ts: -------------------------------------------------------------------------------- 1 | export interface GameEngine { 2 | 3 | init(): void; 4 | 5 | update(player: number, state: number): boolean; 6 | 7 | loadState(fullState: number): void; 8 | 9 | getState(): number; 10 | 11 | isGameOver(): boolean; 12 | 13 | getWinner(): number; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /lightning/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile project(":lightning-proto") 3 | compile "io.grpc:grpc-netty-shaded:$grpc_version" 4 | compile 'org.bouncycastle:bcprov-jdk15on:1.59' 5 | compile group: 'org.bitcoinj', name: 'bitcoinj-core', version: '0.15.1' 6 | 7 | testCompile("junit:junit:4.12") 8 | } 9 | -------------------------------------------------------------------------------- /thor-arbitrate-service/src/main/kotlin/org/starcoin/thor/server/RpcServer.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.server 2 | 3 | interface RpcServer { 4 | 5 | fun start() 6 | 7 | fun stop() 8 | 9 | fun registerService(service: S) 10 | 11 | @Throws(InterruptedException::class) 12 | fun awaitTermination() 13 | } -------------------------------------------------------------------------------- /thor-core/src/main/kotlin/org/starcoin/thor/core/arbitrate/Arbitrate.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.core.arbitrate 2 | 3 | interface Arbitrate { 4 | fun join(userId: String, contract: Contract): Boolean 5 | fun challenge(proof: ContractInput) 6 | fun getWinner(): String 7 | fun getStatus(): Status 8 | fun getLeftTime(): Long 9 | } -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | mavenCentral() 4 | maven { url 'https://plugins.gradle.org/m2/' } 5 | gradlePluginPortal() 6 | } 7 | } 8 | rootProject.name = 'thor' 9 | include ':thor-arbitrate-service' 10 | include ':lightning-proto' 11 | include ':lightning' 12 | include ':thor-core' 13 | -------------------------------------------------------------------------------- /thor-app/src/renderer/sdk/GameGUI.ts: -------------------------------------------------------------------------------- 1 | export interface GameGUI { 2 | 3 | isGameOver(): boolean 4 | 5 | getState(): number 6 | 7 | init(myRole: number, playWithAI: boolean): void 8 | 9 | startGame(): void; 10 | 11 | draw(): void; 12 | 13 | onClick(x: number, y: number): number 14 | 15 | rivalUpdate(newState: number): void 16 | 17 | } 18 | -------------------------------------------------------------------------------- /thor-app/README.md: -------------------------------------------------------------------------------- 1 | # thor-app 2 | 3 | ## pre requirement 4 | 5 | 1. node v11.10.0 6 | 2. yarn 1.13.0 7 | 8 | ## Build Setup 9 | 10 | ``` bash 11 | # install dependencies 12 | yarn install 13 | 14 | # run in dev mode 15 | yarn dev 16 | 17 | # build for production with minification 18 | yarn build 19 | 20 | # build system app dist 21 | yarn dist 22 | ``` 23 | -------------------------------------------------------------------------------- /thor-arbitrate-service/README.MD: -------------------------------------------------------------------------------- 1 | # Thor Arbitrate Server 2 | 3 | 4 | 5 | ## build 6 | 7 | ``` 8 | ../gradlew :thor-arbitrate-service:build 9 | ``` 10 | 11 | 12 | 13 | ## run 14 | 15 | ``` 16 | ../script/lnd/lnd.sh setup 17 | ../vm-service/app.js 18 | ../gradlew :thor-arbitrate-service:run 19 | ``` 20 | 21 | 22 | 23 | ## Note 24 | 25 | Arbitrate server and VM server must be started on the same server 26 | -------------------------------------------------------------------------------- /thor-app/src/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Thor 6 | 13 | 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /thor-arbitrate-service/src/main/kotlin/org/starcoin/thor/server/GameService.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.server 2 | 3 | import org.starcoin.thor.core.* 4 | 5 | interface GameService { 6 | fun createGame(req: CreateGameReq): CreateGameResp 7 | 8 | fun gameList(page: Int): GameListResp 9 | 10 | fun roomList(page: Int): List 11 | 12 | fun roomList(game: String): List? 13 | 14 | fun queryRoom(roomId: String): Room 15 | 16 | fun queryGame(gameId: String): GameInfo? 17 | } -------------------------------------------------------------------------------- /thor-arbitrate-service/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /scripts/lnd/lnd.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" 3 | source $SCRIPTPATH/util.sh 4 | usage(){ 5 | echo "Usage $(basename $0) [setup,stop,macaroon,lncli]" 6 | } 7 | 8 | if [ $# -lt 1 ]; then 9 | usage; 10 | fi 11 | case $"$1" in 12 | setup) 13 | setup_lnd 14 | ;; 15 | stop) 16 | clean 17 | ;; 18 | macaroon) 19 | echo -e "alice macaroon is:\n$(get_macaroon alice)\n" 20 | echo -e "bob macaroon is:\n$(get_macaroon bob)\n" 21 | ;; 22 | lncli) 23 | shift 1 24 | lncl $@ 25 | esac 26 | -------------------------------------------------------------------------------- /vm-service/README.org: -------------------------------------------------------------------------------- 1 | * Install 2 | #+BEGIN_SRC bash 3 | yarn install 4 | #+END_SRC 5 | 6 | * Run 7 | #+BEGIN_SRC bash 8 | ./app.js 9 | #+END_SRC 10 | 11 | * Test 12 | 1. Create testing vm 13 | #+BEGIN_SRC bash 14 | curl -d "id=1" -d "srcpath=wasm/engine_optimized.wasm" localhost:3000/api/vm 15 | #+END_SRC 16 | 2. Exeute cmd in vm 17 | #+BEGIN_SRC bash 18 | curl -d "id=1" -d"cmd=0" localhost:3000/api/execute 19 | curl -d "id=1" -d"cmd=1,1,2" localhost:3000/api/execute 20 | #+END_SRC 21 | 22 | -------------------------------------------------------------------------------- /lightning/src/main/java/org/starcoin/lightning/client/core/SignMessageRequest.java: -------------------------------------------------------------------------------- 1 | package org.starcoin.lightning.client.core; 2 | 3 | import com.google.protobuf.ByteString; 4 | import org.starcoin.lightning.proto.LightningOuterClass; 5 | 6 | public class SignMessageRequest { 7 | private byte[] msg; 8 | 9 | public SignMessageRequest(byte[] msg) { 10 | this.msg = msg; 11 | } 12 | 13 | public LightningOuterClass.SignMessageRequest toProto() { 14 | return LightningOuterClass.SignMessageRequest.newBuilder() 15 | .setMsg(ByteString.copyFrom(this.msg)) 16 | .build(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /thor-core/src/main/kotlin/org/starcoin/thor/utils/Extensions.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.utils 2 | 3 | import com.google.common.io.BaseEncoding 4 | import org.starcoin.sirius.serialization.ByteArrayWrapper 5 | import kotlin.experimental.and 6 | 7 | val base64 = BaseEncoding.base64() 8 | 9 | fun ByteArray.toBase64(): String { 10 | return base64.encode(this) 11 | } 12 | 13 | fun String.decodeBase64(): ByteArray { 14 | return base64.decode(this) 15 | } 16 | 17 | fun ByteArrayWrapper.toHex(): String { 18 | val result = StringBuilder() 19 | for (b in bytes) result.append(Integer.toString((b and 0xff.toByte()) + 0x100, 16).substring(1)) 20 | return result.toString() 21 | } -------------------------------------------------------------------------------- /vm-service/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@types/node": "^11.13.6", 4 | "assemblyscript": "github:assemblyscript/assemblyscript", 5 | "morgan": "^1.9.1", 6 | "express": "latest" 7 | }, 8 | "devDependencies": { 9 | "@types/webassembly-js-api": "0.0.2", 10 | "parcel-bundler": "^1.12.3", 11 | "ts-loader": "^5.3.3", 12 | "typescript": "^3.4.1", 13 | "debug": "latest" 14 | }, 15 | "name": "vm-service", 16 | "version": "1.0.0", 17 | "main": "api.js", 18 | "scripts": { 19 | "test": "echo \"Error: no test specified\" && exit 1" 20 | }, 21 | "keywords": [], 22 | "author": "", 23 | "license": "ISC", 24 | "description": "" 25 | } 26 | -------------------------------------------------------------------------------- /thor-core/src/main/kotlin/org/starcoin/thor/core/UserInfo.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.core 2 | 3 | import org.starcoin.thor.sign.getID 4 | import java.security.KeyPair 5 | import java.security.PrivateKey 6 | import java.security.PublicKey 7 | 8 | enum class UserStatus { 9 | NORMAL, ROOM, PLAYING; 10 | } 11 | 12 | data class UserSelf(val privateKey: PrivateKey, val userInfo: UserInfo) { 13 | 14 | companion object { 15 | fun parseFromKeyPair(keyPair: KeyPair): UserSelf { 16 | return UserSelf(keyPair.private, UserInfo(keyPair.public)) 17 | } 18 | } 19 | } 20 | 21 | data class UserInfo(val publicKey: PublicKey) { 22 | val id = publicKey.getID() 23 | var name = id.substring(2, 10) 24 | } -------------------------------------------------------------------------------- /lightning/src/main/java/org/starcoin/lightning/client/core/SettleInvoiceRequest.java: -------------------------------------------------------------------------------- 1 | package org.starcoin.lightning.client.core; 2 | 3 | import com.google.protobuf.ByteString; 4 | import org.starcoin.lightning.proto.InvoicesOuterClass.SettleInvoiceMsg; 5 | 6 | public class SettleInvoiceRequest { 7 | private byte[] preimage; 8 | 9 | public byte[] getPreimage() { 10 | return preimage; 11 | } 12 | 13 | public SettleInvoiceRequest(byte[] preimage) { 14 | this.preimage = preimage; 15 | } 16 | 17 | public SettleInvoiceMsg toProto(){ 18 | SettleInvoiceMsg.Builder builder = SettleInvoiceMsg.newBuilder(); 19 | builder.setPreimage(ByteString.copyFrom(this.preimage)); 20 | return builder.build(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /lightning/src/main/java/org/starcoin/lightning/client/core/Payment.java: -------------------------------------------------------------------------------- 1 | package org.starcoin.lightning.client.core; 2 | 3 | import org.starcoin.lightning.proto.LightningOuterClass; 4 | 5 | public class Payment { 6 | private String paymentRequest; 7 | 8 | public Payment(String paymentRequest) { 9 | this.paymentRequest = paymentRequest; 10 | } 11 | 12 | public String getPaymentRequest() { 13 | return paymentRequest; 14 | } 15 | 16 | public LightningOuterClass.SendRequest toProto(){ 17 | LightningOuterClass.SendRequest.Builder requestBuilder = LightningOuterClass.SendRequest.newBuilder(); 18 | requestBuilder.setPaymentRequest(this.getPaymentRequest()); 19 | return requestBuilder.build(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /thor-core/src/main/kotlin/org/starcoin/thor/core/arbitrate/ArbitrateData.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.core.arbitrate 2 | 3 | import org.starcoin.thor.core.WitnessData 4 | import org.starcoin.thor.utils.toHex 5 | 6 | interface ArbitrateData { 7 | fun data(): String 8 | fun timestamp(): Long? 9 | fun userId(): String 10 | } 11 | 12 | 13 | class ArbitrateDataImpl(private val userList: List, private val witnessData: WitnessData) : ArbitrateData { 14 | override fun data(): String { 15 | return witnessData.data.toHex() 16 | } 17 | 18 | override fun timestamp(): Long? { 19 | return witnessData.timestamp 20 | } 21 | 22 | override fun userId(): String { 23 | return (userList.indexOf(witnessData.userId) + 1).toString() 24 | } 25 | } -------------------------------------------------------------------------------- /lightning/src/test/resources/alice.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIB9DCCAZugAwIBAgIRAKPhmm7FeDgrVZNMLJe5fmgwCgYIKoZIzj0EAwIwPTEf 3 | MB0GA1UEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEaMBgGA1UEAxMRc3RhcmNv 4 | aW4tZmlyc3Rib3gwHhcNMTkwNDA5MDUxMDMwWhcNMjAwNjAzMDUxMDMwWjA9MR8w 5 | HQYDVQQKExZsbmQgYXV0b2dlbmVyYXRlZCBjZXJ0MRowGAYDVQQDExFzdGFyY29p 6 | bi1maXJzdGJveDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOrFRYAoLXmfR7PJ 7 | oodgvwBdO9hJCEBuoxPn85mcFw3gPffoBIJEsA6vIDhIxhUDtk5Hko9k6/OzZwbQ 8 | Owair8ijfDB6MA4GA1UdDwEB/wQEAwICpDAPBgNVHRMBAf8EBTADAQH/MFcGA1Ud 9 | EQRQME6CEXN0YXJjb2luLWZpcnN0Ym94gglsb2NhbGhvc3SCBHVuaXiCCnVuaXhw 10 | YWNrZXSHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAGHBKwRGPIwCgYIKoZIzj0EAwID 11 | RwAwRAIgWWvP42o6Kb3Tm+D61O5BU1mTwNPaM3QfA35u84r9vLECIAKcteN+JVPL 12 | 2gYbpRiPJmQAZaduMWXzaatFscxtCJeY 13 | -----END CERTIFICATE----- 14 | -------------------------------------------------------------------------------- /lightning/src/test/resources/arb.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIB9TCCAZugAwIBAgIRAJswyI0iqW3cjL9h2E3t+EYwCgYIKoZIzj0EAwIwPTEf 3 | MB0GA1UEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEaMBgGA1UEAxMRc3RhcmNv 4 | aW4tZmlyc3Rib3gwHhcNMTkwNDA5MDUxMDIyWhcNMjAwNjAzMDUxMDIyWjA9MR8w 5 | HQYDVQQKExZsbmQgYXV0b2dlbmVyYXRlZCBjZXJ0MRowGAYDVQQDExFzdGFyY29p 6 | bi1maXJzdGJveDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABItEpBKb3eNh+fYP 7 | OoGIemD2NNj11LuxlYjts145OBGT/mVovVcK1I3tjY0W9gxJ5Ysc8MyZDKfMfbcB 8 | XxaWdhGjfDB6MA4GA1UdDwEB/wQEAwICpDAPBgNVHRMBAf8EBTADAQH/MFcGA1Ud 9 | EQRQME6CEXN0YXJjb2luLWZpcnN0Ym94gglsb2NhbGhvc3SCBHVuaXiCCnVuaXhw 10 | YWNrZXSHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAGHBKwRGPIwCgYIKoZIzj0EAwID 11 | SAAwRQIgZcq8cRtolZSMzR93kWorqS1gO1gJ1ZdOzHOeWHHybtgCIQD29k6gGf9c 12 | y55ohOyjnm2+TWyPK43wY7Ii/TD05lEGLQ== 13 | -----END CERTIFICATE----- 14 | -------------------------------------------------------------------------------- /lightning/src/test/resources/bob.cert: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIB9TCCAZugAwIBAgIRAJIWZhGKyJKnReKPS3iIV+0wCgYIKoZIzj0EAwIwPTEf 3 | MB0GA1UEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEaMBgGA1UEAxMRc3RhcmNv 4 | aW4tZmlyc3Rib3gwHhcNMTkwNDA5MDUxMDI3WhcNMjAwNjAzMDUxMDI3WjA9MR8w 5 | HQYDVQQKExZsbmQgYXV0b2dlbmVyYXRlZCBjZXJ0MRowGAYDVQQDExFzdGFyY29p 6 | bi1maXJzdGJveDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABOZ2GJRaPJEE4XNr 7 | FXIPeg8kX5IpE/ECWrQopWsFTUusPJ2Qwx2a3idNTAkGYLrEEVQ3URErfYAux22Y 8 | wl+v4v2jfDB6MA4GA1UdDwEB/wQEAwICpDAPBgNVHRMBAf8EBTADAQH/MFcGA1Ud 9 | EQRQME6CEXN0YXJjb2luLWZpcnN0Ym94gglsb2NhbGhvc3SCBHVuaXiCCnVuaXhw 10 | YWNrZXSHBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAGHBKwRGPIwCgYIKoZIzj0EAwID 11 | SAAwRQIgD+FGwH4sDQGv+ME+7B+aFm+mcCYh7FV4vkyO3RkzPDICIQCwQgtQYAYx 12 | bMu1rieX4iKj/27W+YlJc/xotmLwn2Psiw== 13 | -----END CERTIFICATE----- 14 | -------------------------------------------------------------------------------- /thor-app/src/renderer/sdk/crypto.ts: -------------------------------------------------------------------------------- 1 | import {crypto as btccrypto, ECPair, Network} from "bitcoinjs-lib"; 2 | 3 | namespace crypto { 4 | 5 | export type ECPair = ECPair.ECPairInterface 6 | 7 | export function generateKeyPair(): ECPair { 8 | return ECPair.makeRandom() 9 | } 10 | 11 | export function toAddress(keyPair: ECPair): string { 12 | return "0x" + btccrypto.hash160(keyPair.publicKey!).toString('hex') 13 | } 14 | 15 | export function fromWIF(wifString: string, network?: Network | Network[]): ECPair { 16 | return ECPair.fromWIF(wifString, network) 17 | } 18 | 19 | export function fromPublicKey(buffer: Buffer): ECPair { 20 | return ECPair.fromPublicKey(buffer) 21 | } 22 | 23 | export function hash(buffer: Buffer): Buffer { 24 | return btccrypto.sha256(buffer) 25 | } 26 | 27 | } 28 | 29 | export default crypto; 30 | -------------------------------------------------------------------------------- /thor-core/src/main/kotlin/org/starcoin/thor/core/MsgObject.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.core 2 | 3 | import kotlinx.serialization.ImplicitReflectionSerializer 4 | import kotlinx.serialization.json.Json 5 | import kotlinx.serialization.serializer 6 | import kotlin.reflect.KClass 7 | 8 | @UseExperimental(ImplicitReflectionSerializer::class) 9 | abstract class MsgObject { 10 | companion object { 11 | fun fromJson(json: String, cla: KClass): T { 12 | return Json.parse(cla.serializer(), json) 13 | } 14 | } 15 | 16 | fun toJson(): String { 17 | return Json.stringify(this.javaClass.kotlin.serializer(), this) 18 | } 19 | 20 | override fun toString(): String { 21 | return this.toJson() 22 | } 23 | 24 | 25 | } 26 | 27 | @UseExperimental(ImplicitReflectionSerializer::class) 28 | abstract class Data : MsgObject() -------------------------------------------------------------------------------- /thor-app/src/renderer/components/Msgbus.ts: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import * as client from "../sdk/client"; 3 | import {WsMsg, WSMsgType} from "../sdk/client"; 4 | import * as lightning from "../sdk/lightning"; 5 | import storage from "./storage"; 6 | 7 | let bus = new Vue({ 8 | methods: { 9 | init() { 10 | let self = this; 11 | let keyPair = storage.loadOrGenerateKeyPair(); 12 | client.init(keyPair); 13 | let config = storage.loadConfig(); 14 | lightning.init(config); 15 | client.subscribe(function (msg: WsMsg): void { 16 | console.log("emit", msg); 17 | self.$emit(WSMsgType[msg.type], msg.data); 18 | }); 19 | } 20 | } 21 | }); 22 | 23 | export default bus 24 | 25 | export function newSuccessHandler(msg: string) { 26 | return function () { 27 | bus.$emit("info", msg) 28 | } 29 | } 30 | 31 | export function errorHandler(e: any) { 32 | bus.$emit("error", e) 33 | } 34 | -------------------------------------------------------------------------------- /thor-app/src/renderer/components/JsonObjView.ts: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | 3 | export default Vue.extend({ 4 | template: ` 5 | 6 | {{title}} 7 | 8 | 9 | 10 | {{name}} 11 | {{ value }} 12 | 13 | 14 | 15 | `, 16 | props: { 17 | object: { 18 | type: Object, 19 | // 对象或数组默认值必须从一个工厂函数获取 20 | default: function () { 21 | return {} 22 | } 23 | }, 24 | title: { 25 | type: String, 26 | default: "" 27 | } 28 | }, 29 | created() { 30 | }, 31 | methods: {}, 32 | computed: {} 33 | }); 34 | -------------------------------------------------------------------------------- /thor-arbitrate-service/src/main/kotlin/org/starcoin/thor/server/PlayService.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.server 2 | 3 | import io.ktor.http.cio.websocket.DefaultWebSocketSession 4 | import io.ktor.http.cio.websocket.Frame 5 | import org.starcoin.thor.core.Room 6 | import org.starcoin.thor.core.UserInfo 7 | import org.starcoin.thor.core.UserSelf 8 | import org.starcoin.thor.core.WsMsg 9 | import java.security.PrivateKey 10 | import java.security.PublicKey 11 | 12 | interface PlayService { 13 | 14 | fun sendNonce(sessionId: String, session: DefaultWebSocketSession): String 15 | 16 | fun clearSession(sessionId: String) 17 | 18 | fun storePubKey(sessionId: String, userInfo: UserInfo) 19 | 20 | fun compareNoce(sessionId: String, nonce: String): Boolean 21 | 22 | fun queryPubKey(sessionId: String): PublicKey? 23 | 24 | fun doCreateRoom(game: String, name: String, cost: Long, time: Long): Room 25 | 26 | fun doJoinRoom(sessionId: String, roomId: String, arbiter: UserSelf) 27 | 28 | fun doSign(msg: WsMsg, priKey: PrivateKey): Frame.Text 29 | } -------------------------------------------------------------------------------- /lightning/src/main/java/org/starcoin/lightning/client/core/Channel.java: -------------------------------------------------------------------------------- 1 | package org.starcoin.lightning.client.core; 2 | 3 | import org.starcoin.lightning.proto.LightningOuterClass; 4 | import org.starcoin.lightning.proto.LightningOuterClass.ListChannelsRequest; 5 | 6 | public class Channel { 7 | 8 | private boolean activeOnly; 9 | private boolean inactiveOnly; 10 | private boolean publicOnly; 11 | private boolean privateOnly; 12 | 13 | public Channel(boolean activeOnly, boolean inactiveOnly, boolean publicOnly, 14 | boolean privateOnly) { 15 | this.activeOnly = activeOnly; 16 | this.inactiveOnly = inactiveOnly; 17 | this.publicOnly = publicOnly; 18 | this.privateOnly = privateOnly; 19 | } 20 | 21 | public LightningOuterClass.ListChannelsRequest toProto() { 22 | ListChannelsRequest listChannelsRequest = ListChannelsRequest.newBuilder() 23 | .setActiveOnly(this.activeOnly).setInactiveOnly(this.inactiveOnly) 24 | .setPublicOnly(this.publicOnly).setPrivateOnly(this.privateOnly).build(); 25 | return listChannelsRequest; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /scripts/lnd/docker/lnd/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.12-alpine as builder 2 | 3 | MAINTAINER Olaoluwa Osuntokun 4 | 5 | # Copy in the local repository to build from. 6 | COPY . /go/src/github.com/lightningnetwork/lnd 7 | 8 | # Force Go to use the cgo based DNS resolver. This is required to ensure DNS 9 | # queries required to connect to linked containers succeed. 10 | ENV GODEBUG netdns=cgo 11 | 12 | # Install dependencies and install/build lnd. 13 | RUN apk add --no-cache --update alpine-sdk \ 14 | git \ 15 | make \ 16 | && cd /go/src/github.com/lightningnetwork/lnd \ 17 | && make \ 18 | && make install 19 | 20 | # Start a new, final image to reduce size. 21 | FROM alpine as final 22 | 23 | # Expose lnd ports (server, rpc). 24 | EXPOSE 9735 10009 25 | 26 | # Copy the binaries and entrypoint from the builder image. 27 | COPY --from=builder /go/bin/lncli /bin/ 28 | COPY --from=builder /go/bin/lnd /bin/ 29 | 30 | # Add bash. 31 | RUN apk add --no-cache \ 32 | bash 33 | 34 | # Copy the entrypoint script. 35 | COPY "docker/lnd/start-lnd.sh" . 36 | RUN chmod +x start-lnd.sh 37 | -------------------------------------------------------------------------------- /lightning/src/main/java/org/starcoin/lightning/client/core/WalletBalanceResponse.java: -------------------------------------------------------------------------------- 1 | package org.starcoin.lightning.client.core; 2 | 3 | import org.starcoin.lightning.proto.LightningOuterClass; 4 | 5 | public class WalletBalanceResponse { 6 | private long totalBalance; 7 | 8 | private long confirmedBalance; 9 | 10 | private long unconfirmedBalance; 11 | 12 | private WalletBalanceResponse(long totalBalance, long confirmedBalance, long unconfirmedBalance) { 13 | this.totalBalance = totalBalance; 14 | this.confirmedBalance = confirmedBalance; 15 | this.unconfirmedBalance = unconfirmedBalance; 16 | } 17 | 18 | public long getTotalBalance() { 19 | return totalBalance; 20 | } 21 | 22 | public long getConfirmedBalance() { 23 | return confirmedBalance; 24 | } 25 | 26 | public long getUnconfirmedBalance() { 27 | return unconfirmedBalance; 28 | } 29 | 30 | public static WalletBalanceResponse copyFrom(LightningOuterClass.WalletBalanceResponse response){ 31 | return new WalletBalanceResponse(response.getTotalBalance(),response.getConfirmedBalance(),response.getUnconfirmedBalance()); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /thor-app/src/renderer/components/Debug.ts: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Msgbus from "./Msgbus" 3 | 4 | export default Vue.extend({ 5 | template: ` 6 | 7 | 8 | 9 | Debug 10 | 11 | 12 | 13 | 14 | triggerError 15 | triggerInfo 16 | triggerSuccess 17 | 18 | 19 | 20 | `, 21 | data() { 22 | return {} 23 | }, 24 | created() { 25 | }, 26 | methods: { 27 | triggerError: function () { 28 | Msgbus.$emit("error", "test error msg."); 29 | }, 30 | triggerSuccess: function () { 31 | Msgbus.$emit("success", "test success msg."); 32 | }, 33 | triggerInfo: function () { 34 | Msgbus.$emit("info", "test info msg."); 35 | } 36 | }, 37 | computed: {} 38 | }); 39 | -------------------------------------------------------------------------------- /thor-core/src/main/kotlin/org/starcoin/thor/core/arbitrate/Contract.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.core.arbitrate 2 | 3 | abstract class Contract { 4 | abstract fun getSourceCode(): ByteArray 5 | abstract fun update(userId: String, state: String) 6 | abstract fun getWinner(): String 7 | fun updateAll(input: ContractInput): String? { 8 | input.forEach { update(it.userId(), it.data()) } 9 | return getWinner() 10 | } 11 | 12 | fun checkTimeout(input: ContractInput, startTime: Long): String? { 13 | val inputs = input.asSequence().partition { it.userId() == input.getUser() } 14 | 15 | val rivalTimes = inputs.second.map { it.timestamp() }.sortedByDescending { it } 16 | var rivalTimeEscape = 0.toLong() 17 | rivalTimes.forEach { 18 | rivalTimeEscape += startTime - it!! 19 | } 20 | 21 | val selfTimes = inputs.first.map { it.timestamp() }.sortedByDescending { it } 22 | var selfTimeEscape = 0.toLong() 23 | selfTimes.forEach { 24 | selfTimeEscape += startTime - it!! 25 | } 26 | return if (rivalTimeEscape > selfTimeEscape) input.getUser() else inputs.second.firstOrNull()?.userId() 27 | } 28 | } -------------------------------------------------------------------------------- /lightning-proto/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | jcenter() 5 | maven { 6 | url "https://plugins.gradle.org/m2/" 7 | } 8 | } 9 | dependencies { 10 | classpath "gradle.plugin.com.google.protobuf:protobuf-gradle-plugin:0.8.8" 11 | } 12 | } 13 | 14 | apply plugin: "com.google.protobuf" 15 | 16 | dependencies { 17 | compile "com.google.protobuf:protobuf-java:3.5.1" 18 | compile "io.grpc:grpc-protobuf:$grpc_version" 19 | compile "io.grpc:grpc-stub:$grpc_version" 20 | } 21 | 22 | sourceSets { 23 | main { 24 | proto { 25 | srcDir 'src/main/proto' 26 | } 27 | java { 28 | srcDirs 'build/generated/source/proto/main/grpc' 29 | srcDirs 'build/generated/source/proto/main/java' 30 | } 31 | } 32 | } 33 | 34 | protobuf { 35 | protoc { 36 | artifact = "com.google.protobuf:protoc:${protoc_version}" 37 | } 38 | plugins { 39 | grpc { 40 | artifact = "io.grpc:protoc-gen-grpc-java:${grpc_version}" 41 | } 42 | } 43 | generateProtoTasks { 44 | all()*.plugins { 45 | grpc {} 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /lightning/src/main/java/org/starcoin/lightning/client/core/InvoiceList.java: -------------------------------------------------------------------------------- 1 | package org.starcoin.lightning.client.core; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import org.starcoin.lightning.proto.LightningOuterClass; 6 | 7 | public class InvoiceList { 8 | 9 | private List invoices; 10 | 11 | private long lastIndexOffset; 12 | 13 | private long firstIndexOffset; 14 | 15 | public List getInvoices() { 16 | return invoices; 17 | } 18 | 19 | public long getLastIndexOffset() { 20 | return lastIndexOffset; 21 | } 22 | 23 | public long getFirstIndexOffset() { 24 | return firstIndexOffset; 25 | } 26 | 27 | private InvoiceList(List invoices, long lastIndexOffset, long firstIndexOffset) { 28 | this.invoices = invoices; 29 | this.lastIndexOffset = lastIndexOffset; 30 | this.firstIndexOffset = firstIndexOffset; 31 | } 32 | 33 | public static InvoiceList copyFrom(LightningOuterClass.ListInvoiceResponse response){ 34 | List invoices = new ArrayList<>(); 35 | for(LightningOuterClass.Invoice invoice:response.getInvoicesList()){ 36 | invoices.add(Invoice.copyFrom(invoice)); 37 | } 38 | return new InvoiceList(invoices,response.getLastIndexOffset(),response.getFirstIndexOffset()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lightning/src/main/java/org/starcoin/lightning/client/core/AddInvoiceResponse.java: -------------------------------------------------------------------------------- 1 | package org.starcoin.lightning.client.core; 2 | 3 | import org.starcoin.lightning.client.HashUtils; 4 | import org.starcoin.lightning.proto.LightningOuterClass; 5 | 6 | public class AddInvoiceResponse { 7 | 8 | private byte[] rHash; 9 | 10 | private String paymentRequest; 11 | 12 | private long addIndex; 13 | 14 | private AddInvoiceResponse(byte[] rHash, String paymentRequest, long addIndex) { 15 | this.rHash = rHash; 16 | this.paymentRequest = paymentRequest; 17 | this.addIndex = addIndex; 18 | } 19 | 20 | public static AddInvoiceResponse copyFrom(LightningOuterClass.AddInvoiceResponse response){ 21 | return new AddInvoiceResponse(response.getRHash().toByteArray(),response.getPaymentRequest(),response.getAddIndex()); 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return "AddInvoiceResponse{" + 27 | "rHash=" + HashUtils.bytesToHex(rHash) + 28 | ", paymentRequest='" + paymentRequest + '\'' + 29 | ", addIndex=" + addIndex + 30 | '}'; 31 | } 32 | 33 | public byte[] getrHash() { 34 | return rHash; 35 | } 36 | 37 | public String getPaymentRequest() { 38 | return paymentRequest; 39 | } 40 | 41 | public long getAddIndex() { 42 | return addIndex; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /thor-arbitrate-service/src/main/kotlin/org/starcoin/thor/manager/GameManager.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.manager 2 | 3 | import org.starcoin.thor.core.GameBaseInfo 4 | import org.starcoin.thor.core.GameInfo 5 | 6 | class GameManager { 7 | private val games = mutableMapOf() 8 | private val gameHashSet = mutableSetOf() 9 | private var count: Int = 0 10 | private val lock = Object() 11 | 12 | fun createGame(game: GameInfo): GameBaseInfo? { 13 | return synchronized(lock) { 14 | when (!gameHashSet.contains(game.base.hash)) { 15 | true -> { 16 | gameHashSet.add(game.base.hash) 17 | games[game.base.hash] = game 18 | count = count.inc() 19 | game.base 20 | } 21 | else -> null 22 | } 23 | } 24 | } 25 | 26 | fun count(): Int { 27 | return this.count 28 | } 29 | 30 | fun list(begin: Int, end: Int): List { 31 | val keys = gameHashSet.toList().subList(begin, end).toSet() 32 | return games.filterKeys { keys.contains(it) }.values.map { it.base } 33 | } 34 | 35 | fun queryGameBaseInfoByHash(hash: String): GameBaseInfo? { 36 | return games[hash]?.let { games[hash]!!.base } 37 | } 38 | 39 | fun queryGameInfoByHash(hash: String): GameInfo { 40 | return games[hash]!! 41 | } 42 | } -------------------------------------------------------------------------------- /lightning/src/main/java/org/starcoin/lightning/client/core/PayReq.java: -------------------------------------------------------------------------------- 1 | package org.starcoin.lightning.client.core; 2 | 3 | import org.starcoin.lightning.proto.LightningOuterClass; 4 | 5 | public class PayReq { 6 | 7 | private String destination; 8 | private String paymentHash; 9 | private long numSatoshis; 10 | private long timestamp; 11 | private long expiry; 12 | private String description; 13 | 14 | private PayReq(String destination, String paymentHash, long numSatoshis, long timestamp, 15 | long expiry, 16 | String description) { 17 | this.destination = destination; 18 | this.paymentHash = paymentHash; 19 | this.numSatoshis = numSatoshis; 20 | this.timestamp = timestamp; 21 | this.expiry = expiry; 22 | this.description = description; 23 | } 24 | 25 | public String getDestination() { 26 | return destination; 27 | } 28 | 29 | public String getPaymentHash() { 30 | return paymentHash; 31 | } 32 | 33 | public long getNumSatoshis() { 34 | return numSatoshis; 35 | } 36 | 37 | public long getTimestamp() { 38 | return timestamp; 39 | } 40 | 41 | public long getExpiry() { 42 | return expiry; 43 | } 44 | 45 | public String getDescription() { 46 | return description; 47 | } 48 | 49 | public static PayReq copyFrom(LightningOuterClass.PayReq req){ 50 | return new PayReq(req.getDestination(),req.getPaymentHash(),req.getNumSatoshis(),req.getTimestamp(),req.getExpiry(),req.getDescription()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /docs/gettingstarted.md: -------------------------------------------------------------------------------- 1 | # Getting started 2 | 3 | ## Required 4 | + Java version >= "1.8.0" 5 | + Gradle version >= "4.10.x" 6 | + Docker version >= 18 for testing enviroment of lnd 7 | + Node version "v11.10.0" 8 | + Yarn version "1.13.0" 9 | 10 | 11 | ## Run lnd node 12 | ``` 13 | ./scripts/lnd/lnd.sh setup 14 | ``` 15 | 16 | ## Setup arbitrate service 17 | 18 | ### Compile project 19 | You could compile source code by such command: 20 | ``` 21 | ./gradlew fatJar 22 | ``` 23 | 24 | ### Run arbitrate service 25 | You could run arbitrate service by such command: 26 | ``` 27 | java -jar thor-arbitrate-service/build/libs/thor-arbitrate-service-all.jar 28 | ``` 29 | 30 | ### Run VM service 31 | ### Compile VM service 32 | You could compile game client by such command(in vm-service directory): 33 | ``` 34 | yarn install 35 | ``` 36 | 37 | ### Run VM service 38 | You could compile game client by such command(in vm-service directory): 39 | ``` 40 | ./app.js 41 | ``` 42 | 43 | ## Setup game client 44 | ### Compile and package game client 45 | You could compile game client by such command(in thor-app directory): 46 | ``` 47 | yarn install 48 | yarn dist:dir 49 | ``` 50 | 51 | then go to dist/mac directory you could find packaged app. 52 | 53 | ### Config LND 54 | Get lnd account macaroon hex string by : 55 | ``` 56 | ./scripts/lnd/lnd.sh macaroon 57 | ``` 58 | Copy account macaroon to thor.app. Lnd url of alice is https://localhost:8080 and lnd url of bob is https://localhost:8081 . Make sure lnd url and lnd macaroon should be matched. 59 | -------------------------------------------------------------------------------- /lightning/src/main/java/org/starcoin/lightning/client/core/PaymentResponse.java: -------------------------------------------------------------------------------- 1 | package org.starcoin.lightning.client.core; 2 | 3 | import org.starcoin.lightning.client.HashUtils; 4 | import org.starcoin.lightning.proto.LightningOuterClass; 5 | 6 | public class PaymentResponse { 7 | 8 | private String paymentError; 9 | private byte[] paymentPreimage; 10 | private byte[] paymentHash; 11 | 12 | public String getPaymentError() { 13 | return paymentError; 14 | } 15 | 16 | public byte[] getPaymentPreimage() { 17 | return paymentPreimage; 18 | } 19 | 20 | public byte[] getPaymentHashByte() { 21 | return paymentHash; 22 | } 23 | 24 | public String getPaymentHash() { 25 | return HashUtils.bytesToHex(paymentHash); 26 | } 27 | 28 | private PaymentResponse(String paymentError, byte[] paymentHash, byte[] paymentPreimage) { 29 | this.paymentError = paymentError; 30 | this.paymentHash = paymentHash; 31 | this.paymentPreimage = paymentPreimage; 32 | } 33 | 34 | public static PaymentResponse copyFrom(LightningOuterClass.SendResponse sendResponse) { 35 | return new PaymentResponse(sendResponse.getPaymentError(), 36 | sendResponse.getPaymentHash().toByteArray(), 37 | sendResponse.getPaymentPreimage().toByteArray()); 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return "PaymentResponse{" + 43 | "paymentError='" + paymentError + '\'' + 44 | ", paymentPreimage=" + HashUtils.bytesToHex(paymentPreimage) + 45 | ", paymentHash=" + HashUtils.bytesToHex(paymentHash) + 46 | '}'; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /thor-core/src/main/kotlin/org/starcoin/thor/core/arbitrate/WitnessContractInput.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.core.arbitrate 2 | 3 | import org.starcoin.thor.core.WitnessData 4 | import java.security.PublicKey 5 | 6 | data class WitnessContractInput(val userList: List, val userId: String, val publicKeys: Triple, val data: List, var begin: Long = 0) : ContractInput { 7 | private var current = 0 8 | private val size = data.size 9 | private var flag = true 10 | 11 | override fun getUser(): String { 12 | return this.userId 13 | } 14 | 15 | override fun hasNext(): Boolean { 16 | return synchronized(this) { 17 | if (current < size && flag) { 18 | val first = ((current % 2) == 0) 19 | 20 | val tmpPk = if (first) { 21 | publicKeys.second 22 | } else { 23 | publicKeys.third 24 | } 25 | flag = data[current].checkSign(tmpPk) 26 | if (flag) { 27 | flag = data[current].checkArbiterSign(publicKeys.first) 28 | } 29 | } 30 | current < size && flag 31 | } 32 | } 33 | 34 | override fun reset() { 35 | synchronized(this) { 36 | current = 0 37 | } 38 | } 39 | 40 | override fun next(): ArbitrateData { 41 | synchronized(this) { 42 | val arbitrateData = ArbitrateDataImpl(userList, data[current]) 43 | current = ++current 44 | return arbitrateData 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /thor-arbitrate-service/src/main/kotlin/org/starcoin/thor/server/JsonSerializableConverter.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.server 2 | 3 | import io.ktor.application.* 4 | import io.ktor.content.* 5 | import io.ktor.features.* 6 | import io.ktor.http.* 7 | import io.ktor.request.* 8 | import io.ktor.util.KtorExperimentalAPI 9 | import kotlinx.coroutines.io.* 10 | import kotlinx.coroutines.io.jvm.javaio.* 11 | import kotlinx.serialization.* 12 | import kotlinx.serialization.json.* 13 | import io.ktor.util.pipeline.PipelineContext 14 | 15 | @UseExperimental(ImplicitReflectionSerializer::class, KtorExperimentalAPI::class, UnstableDefault::class) 16 | class JsonSerializableConverter(private val json: Json = Json.plain) : ContentConverter { 17 | override suspend fun convertForReceive(context: PipelineContext): Any? { 18 | val request = context.subject 19 | val type = request.type 20 | val value = request.value as? ByteReadChannel ?: return null 21 | val text = value.toInputStream().reader(context.call.request.contentCharset() ?: Charsets.UTF_8).readText() 22 | println(text) 23 | return json.parse(type.serializer(), text) 24 | } 25 | 26 | override suspend fun convertForSend(context: PipelineContext, contentType: ContentType, value: Any): Any? { 27 | println(value.javaClass.kotlin) 28 | return TextContent( 29 | text = json.stringify(value.javaClass.kotlin.serializer(), value), 30 | contentType = ContentType.Application.Json.withCharset(context.call.suitableCharset()) 31 | ) 32 | } 33 | } -------------------------------------------------------------------------------- /thor-app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thor", 3 | "description": "Thor App", 4 | "version": "0.0.1", 5 | "author": "jolestar ", 6 | "license": "MIT", 7 | "private": true, 8 | "scripts": { 9 | "electron-rebuild": "./node_modules/.bin/electron-rebuild", 10 | "dev": "yarn electron-rebuild && electron-webpack dev", 11 | "build": "electron-webpack", 12 | "dist": "yarn build && electron-builder", 13 | "dist:dir": "yarn dist --dir -c.compression=store -c.mac.identity=null" 14 | }, 15 | "dependencies": { 16 | "source-map-support": "^0.5.12" 17 | }, 18 | "browserslist": [ 19 | "> 1%", 20 | "last 2 versions", 21 | "not ie <= 8" 22 | ], 23 | "devDependencies": { 24 | "@chenfengyuan/vue-countdown": "^1.1.2", 25 | "@types/webassembly-js-api": "0.0.3", 26 | "as2d": "github:as2d/as2d", 27 | "assemblyscript": "github:AssemblyScript/assemblyscript", 28 | "bitcoinjs-lib": "^5.0.2", 29 | "electron": "4.0.1", 30 | "electron-builder": "20.38.4", 31 | "electron-context-menu": "^0.12.0", 32 | "electron-debug": "^2.2.0", 33 | "electron-rebuild": "^1.8.4", 34 | "electron-webpack": "^2.6.2", 35 | "electron-webpack-ts": "^3.1.1", 36 | "electron-webpack-vue": "^2.2.3", 37 | "html-webpack-plugin": "^3.2.0", 38 | "int64-buffer": "^0.99.1007", 39 | "material-design-icons": "^3.0.1", 40 | "node-fetch": "^2.4.1", 41 | "typeface-roboto": "^0.0.54", 42 | "typescript": "^3.4.3", 43 | "vue": "^2.6.10", 44 | "vue-router": "^3.0.6", 45 | "vuetify": "^1.5.13", 46 | "webpack": "4.28.4", 47 | "websocket": "^1.0.28" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /scripts/lnd/docker/btcd/start-btcctl.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # exit from script if error was raised. 4 | set -e 5 | 6 | # error function is used within a bash function in order to send the error 7 | # message directly to the stderr output and exit. 8 | error() { 9 | echo "$1" > /dev/stderr 10 | exit 0 11 | } 12 | 13 | # return is used within bash function in order to return the value. 14 | return() { 15 | echo "$1" 16 | } 17 | 18 | # set_default function gives the ability to move the setting of default 19 | # env variable from docker file to the script thereby giving the ability to the 20 | # user override it durin container start. 21 | set_default() { 22 | # docker initialized env variables with blank string and we can't just 23 | # use -z flag as usually. 24 | BLANK_STRING='""' 25 | 26 | VARIABLE="$1" 27 | DEFAULT="$2" 28 | 29 | if [[ -z "$VARIABLE" || "$VARIABLE" == "$BLANK_STRING" ]]; then 30 | 31 | if [ -z "$DEFAULT" ]; then 32 | error "You should specify default variable" 33 | else 34 | VARIABLE="$DEFAULT" 35 | fi 36 | fi 37 | 38 | return "$VARIABLE" 39 | } 40 | 41 | # Set default variables if needed. 42 | RPCUSER=$(set_default "$RPCUSER" "devuser") 43 | RPCPASS=$(set_default "$RPCPASS" "devpass") 44 | NETWORK=$(set_default "$NETWORK" "simnet") 45 | 46 | PARAMS="" 47 | if [ "$NETWORK" != "mainnet" ]; then 48 | PARAMS=$(echo --$NETWORK) 49 | fi 50 | 51 | PARAMS=$(echo $PARAMS \ 52 | "--rpccert=/rpc/rpc.cert" \ 53 | "--rpcuser=$RPCUSER" \ 54 | "--rpcpass=$RPCPASS" \ 55 | "--rpcserver=rpcserver" \ 56 | ) 57 | 58 | PARAMS="$PARAMS $@" 59 | exec btcctl $PARAMS 60 | -------------------------------------------------------------------------------- /thor-arbitrate-service/src/test/kotlin/org/starcoin/thor/server/JsonTest.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.server 2 | 3 | import kotlinx.serialization.ImplicitReflectionSerializer 4 | import kotlinx.serialization.Serializable 5 | import kotlinx.serialization.json.Json 6 | import kotlinx.serialization.parse 7 | import kotlinx.serialization.stringify 8 | import org.junit.Assert 9 | import org.junit.Test 10 | import org.starcoin.sirius.serialization.ByteArrayWrapper 11 | import org.starcoin.thor.core.* 12 | import org.starcoin.thor.utils.randomString 13 | 14 | 15 | enum class TestType { 16 | ONE, TWO 17 | } 18 | 19 | @Serializable 20 | data class TestClass(val type: TestType, val name: String) 21 | 22 | @UseExperimental(ImplicitReflectionSerializer::class) 23 | class JsonTest { 24 | 25 | @Test 26 | fun testHttpMsgJson() { 27 | val httpMsg = HttpMsg(HttpType.GAME_LIST, GameListReq(10)) 28 | val str = httpMsg.toJson() 29 | println(str) 30 | val httpMsg2 = Json.parse(str) 31 | Assert.assertEquals(httpMsg, httpMsg2) 32 | } 33 | 34 | @Test 35 | fun testWsMsgJson() { 36 | val wsMsg = WsMsg(MsgType.NONCE, "1", Nonce(randomString(), ByteArrayWrapper(ByteArray(10)))) 37 | val str = wsMsg.toJson() 38 | println(str) 39 | val httpMsg2 = Json.parse(str) 40 | Assert.assertEquals(wsMsg, httpMsg2) 41 | } 42 | 43 | @Test 44 | fun enumJsonTest() { 45 | val testObj = TestClass(TestType.ONE, "test") 46 | val str = Json.stringify(testObj) 47 | println(str) 48 | val testObj2 = Json.parse(str) 49 | Assert.assertEquals(testObj, testObj2) 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /thor-arbitrate-service/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin:'application' 2 | mainClassName = "org.starcoin.thor.server.MainKt" 3 | 4 | dependencies { 5 | testCompile("junit:junit:4.12") 6 | compile project(":thor-core") 7 | compile project(":lightning") 8 | 9 | compile "io.grpc:grpc-netty-shaded:$grpc_version" 10 | compile "io.ktor:ktor-websockets:$ktor_version" 11 | compile "io.ktor:ktor-server-core:$ktor_version" 12 | compile "io.ktor:ktor-server-netty:$ktor_version" 13 | 14 | //compile "org.slf4j:slf4j-simple:1.7.25" 15 | compile "ch.qos.logback:logback-classic:1.2.3" 16 | 17 | testCompile project(":thor-core") 18 | testCompile "io.ktor:ktor-client-json:$ktor_version" 19 | testCompile "io.ktor:ktor-client-jackson:$ktor_version" 20 | testCompile "io.ktor:ktor-client-cio:$ktor_version" 21 | testCompile "io.ktor:ktor-client-websocket:$ktor_version" 22 | } 23 | 24 | jar { 25 | manifest { 26 | attributes('Implementation-Title': project.name, 27 | 'Implementation-Version': project.version, 28 | 'Main-Class': 'org.starcoin.thor.server.MainKt' 29 | ) 30 | } 31 | } 32 | 33 | task fatJar(type: Jar) { 34 | manifest { 35 | attributes('Implementation-Title': project.name, 36 | 'Implementation-Version': project.version, 37 | 'Main-Class': 'org.starcoin.thor.server.MainKt' 38 | ) 39 | } 40 | classifier = 'all' 41 | from { configurations.compile.collect { it.isDirectory() ? it : zipTree(it) } 42 | }{ 43 | exclude "META-INF/*.SF" 44 | exclude "META-INF/*.DSA" 45 | exclude "META-INF/*.RSA" 46 | } 47 | with jar 48 | } 49 | 50 | artifacts { 51 | archives fatJar 52 | } 53 | -------------------------------------------------------------------------------- /vm-service/app.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var http = require('http'); 4 | var debug = require('debug')('vm-service:server'); 5 | 6 | const express = require('express'); 7 | const logger = require('morgan'); 8 | const apiRouter = require('./api.js') 9 | const app = express(); 10 | 11 | app.use(logger('dev')); 12 | app.use(express.json()); 13 | app.use(express.urlencoded({ extended: false })); 14 | app.use('/api',apiRouter); 15 | 16 | const port = normalizePort(process.env.PORT || '3000'); 17 | app.set('port', port); 18 | 19 | const server = http.createServer(app); 20 | 21 | server.listen(port); 22 | server.on('error', onError); 23 | server.on('listening', onListening); 24 | 25 | function onError(error) { 26 | if (error.syscall !== 'listen') { 27 | throw error; 28 | } 29 | 30 | const bind = typeof port === 'string' 31 | ? 'Pipe ' + port 32 | : 'Port ' + port; 33 | 34 | // handle specific listen errors with friendly messages 35 | switch (error.code) { 36 | case 'EACCES': 37 | console.error(bind + ' requires elevated privileges'); 38 | process.exit(1); 39 | break; 40 | case 'EADDRINUSE': 41 | console.error(bind + ' is already in use'); 42 | process.exit(1); 43 | break; 44 | default: 45 | throw error; 46 | } 47 | } 48 | 49 | 50 | function onListening() { 51 | const addr = server.address(); 52 | const bind = typeof addr === 'string' 53 | ? 'pipe ' + addr 54 | : 'port ' + addr.port; 55 | debug('Listening on ' + bind); 56 | } 57 | 58 | function normalizePort(val) { 59 | var port = parseInt(val, 10); 60 | 61 | if (isNaN(port)) { 62 | // named pipe 63 | return val; 64 | } 65 | 66 | if (port >= 0) { 67 | // port number 68 | return port; 69 | } 70 | 71 | return false; 72 | } 73 | -------------------------------------------------------------------------------- /thor-core/src/main/kotlin/org/starcoin/thor/core/GameInfo.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.core 2 | 3 | import kotlinx.serialization.SerialId 4 | import kotlinx.serialization.Serializable 5 | import org.starcoin.sirius.serialization.ByteArrayWrapper 6 | import java.security.MessageDigest 7 | import java.util.* 8 | 9 | @Serializable 10 | data class GameBaseInfo(@SerialId(1) val hash: String, @SerialId(2) val gameName: String) : Data() 11 | 12 | @Serializable 13 | data class GameInfo(@SerialId(1) val base: GameBaseInfo, @SerialId(2) val engineBytes: ByteArrayWrapper, val guiBytes: ByteArrayWrapper) : Data() { 14 | constructor(gameName: String, engineBytes: ByteArray, guiBytes: ByteArray) : this(gameName, ByteArrayWrapper(engineBytes), ByteArrayWrapper(guiBytes)) 15 | constructor(gameName: String, engineBytes: ByteArrayWrapper, guiBytes: ByteArrayWrapper) : this(GameBaseInfo(gameHash(engineBytes.bytes), gameName), engineBytes, guiBytes) 16 | 17 | companion object { 18 | fun gameHash(bytes: ByteArray): String { 19 | val md = MessageDigest.getInstance("MD5") 20 | md.update(bytes) 21 | return Base64.getEncoder().encodeToString(md.digest()) 22 | } 23 | } 24 | 25 | override fun equals(other: Any?): Boolean { 26 | if (this === other) return true 27 | if (other !is GameInfo) return false 28 | 29 | if (base != other.base) return false 30 | if (engineBytes != other.engineBytes) return false 31 | if (guiBytes != other.guiBytes) return false 32 | 33 | return true 34 | } 35 | 36 | override fun hashCode(): Int { 37 | var result = base.hashCode() 38 | result = 31 * result + engineBytes.hashCode() 39 | result = 31 * result + guiBytes.hashCode() 40 | return result 41 | } 42 | } -------------------------------------------------------------------------------- /thor-app/src/renderer/components/Config.ts: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import * as lightning from "../sdk/lightning"; 3 | import storage from "./storage"; 4 | import Msgbus from "./Msgbus"; 5 | 6 | export default Vue.extend({ 7 | template: ` 8 | 9 | 10 | Config 11 | 12 | 13 | 14 | 15 | 16 | Test Save 17 | 18 | `, 19 | data() { 20 | return { 21 | lndUrl: '', 22 | lndMacaroon: '', 23 | } 24 | }, 25 | created() { 26 | let config = storage.loadConfig(); 27 | this.lndUrl = config.lndUrl; 28 | this.lndMacaroon = config.lndMacaroon; 29 | }, 30 | methods: { 31 | testConnect() { 32 | if (this.lndUrl == '' || this.lndMacaroon == '') { 33 | Msgbus.$emit("error", "Please set lndUrl and lndMacaroon"); 34 | return 35 | } 36 | lightning.init(this.$data); 37 | lightning.getinfo().then(json => { 38 | Msgbus.$emit("success", "connect " + this.lndUrl + " success") 39 | }).catch(e => Msgbus.$emit("error", "connect fail:" + e)); 40 | }, 41 | save() { 42 | if (this.lndUrl == '' || this.lndMacaroon == '') { 43 | Msgbus.$emit("error", "Please set lndUrl and lndMacaroon"); 44 | return 45 | } 46 | storage.saveConfig(this.$data) 47 | Msgbus.$emit("success", "Save success."); 48 | } 49 | }, 50 | computed: {} 51 | }); 52 | -------------------------------------------------------------------------------- /vm-service/api.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const asm = require("./vm"); 3 | const vms = new Map(); 4 | const fs = require("fs"); 5 | 6 | const router = express.Router(); 7 | router.post("/vm", async function (req, res) { 8 | let vmid = req.body.id; 9 | if (vmid == null) { 10 | return res.status(400).send("vm id not set"); 11 | } 12 | 13 | let vm = vms.get(vmid); 14 | let codeSrc = ""; 15 | res.id = vmid; 16 | if (vm != null) { 17 | return res.send("Vm created before"); 18 | } 19 | if (req.body.source != null && req.body.source != 0) { 20 | codeSrc = Buffer.from(req.body.source, "base64"); 21 | 22 | } else if (req.srcpath != null) { 23 | try { 24 | codeSrc = fs.readFileSync(req.srcpath); 25 | } catch (e) { 26 | return res.status(400).send("Read source of vm from srcpath failed"); 27 | } 28 | 29 | } else { 30 | return res.status(400).send("Invalid vm source code"); 31 | } 32 | try { 33 | let vm = asm.createVm(codeSrc); 34 | vms.set(vmid, vm); 35 | } catch (e) { 36 | console.log(e); 37 | return res.status(400).send("Init vm failed"); 38 | } 39 | return res.send("Vm created with id:" + vmid); 40 | }); 41 | 42 | router.post('/execute', async function (req, res) { 43 | let vmid = req.body.id; 44 | let vm = vms.get(vmid); 45 | if (vm == null) { 46 | return res.status(400).send("Vm not created:" + vmid); 47 | } 48 | if (req.body.cmd == null || req.body.cmd.length == 0) { 49 | return res.status(400).send("Cmd invalid"); 50 | } 51 | let cmd = req.body.cmd.split(","); 52 | console.log("cmd:",cmd) 53 | let opcode = parseInt(cmd.shift()); 54 | let vmout = vm.execute(opcode, ...cmd) 55 | console.log("result:",vmout) 56 | return res.status(200).send("" + vmout) 57 | }); 58 | 59 | module.exports = router; 60 | -------------------------------------------------------------------------------- /scripts/lnd/docker/lnd/start-lnd.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # exit from script if error was raised. 4 | set -e 5 | 6 | # error function is used within a bash function in order to send the error 7 | # message directly to the stderr output and exit. 8 | error() { 9 | echo "$1" > /dev/stderr 10 | exit 0 11 | } 12 | 13 | # return is used within bash function in order to return the value. 14 | return() { 15 | echo "$1" 16 | } 17 | 18 | # set_default function gives the ability to move the setting of default 19 | # env variable from docker file to the script thereby giving the ability to the 20 | # user override it durin container start. 21 | set_default() { 22 | # docker initialized env variables with blank string and we can't just 23 | # use -z flag as usually. 24 | BLANK_STRING='""' 25 | 26 | VARIABLE="$1" 27 | DEFAULT="$2" 28 | 29 | if [[ -z "$VARIABLE" || "$VARIABLE" == "$BLANK_STRING" ]]; then 30 | 31 | if [ -z "$DEFAULT" ]; then 32 | error "You should specify default variable" 33 | else 34 | VARIABLE="$DEFAULT" 35 | fi 36 | fi 37 | 38 | return "$VARIABLE" 39 | } 40 | 41 | # Set default variables if needed. 42 | RPCUSER=$(set_default "$RPCUSER" "admin") 43 | RPCPASS=$(set_default "$RPCPASS" "admin") 44 | DEBUG=$(set_default "$DEBUG" "debug") 45 | NETWORK=$(set_default "$NETWORK" "simnet") 46 | CHAIN=$(set_default "$CHAIN" "bitcoin") 47 | BACKEND="btcd" 48 | if [[ "$CHAIN" == "litecoin" ]]; then 49 | BACKEND="ltcd" 50 | fi 51 | 52 | exec lnd \ 53 | --noseedbackup \ 54 | --logdir="/data" \ 55 | "--$CHAIN.active" \ 56 | "--$CHAIN.$NETWORK" \ 57 | "--$CHAIN.node"="btcd" \ 58 | "--$BACKEND.rpccert"="/rpc/rpc.cert" \ 59 | "--$BACKEND.rpchost"="blockchain" \ 60 | "--$BACKEND.rpcuser"="$RPCUSER" \ 61 | "--$BACKEND.rpcpass"="$RPCPASS" \ 62 | --debuglevel="$DEBUG" \ 63 | "$@" 64 | -------------------------------------------------------------------------------- /thor-arbitrate-service/src/main/kotlin/org/starcoin/thor/server/GameServiceImpl.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.server 2 | 3 | import org.starcoin.thor.core.* 4 | import org.starcoin.thor.manager.GameManager 5 | import org.starcoin.thor.manager.RoomManager 6 | 7 | class GameServiceImpl(private val gameManager: GameManager, private val roomManager: RoomManager) : GameService { 8 | private val size = 10 9 | 10 | override fun createGame(req: CreateGameReq): CreateGameResp { 11 | val gameInfo = GameInfo(req.gameName, req.engine, req.gui) 12 | return CreateGameResp(gameManager.createGame(gameInfo)) 13 | } 14 | 15 | override fun gameList(page: Int): GameListResp { 16 | val begin = when (page <= 0) { 17 | true -> 0 18 | false -> (page - 1) * size 19 | } 20 | 21 | val count = gameManager.count() 22 | val end = when (begin + size < count) { 23 | true -> begin + size 24 | false -> count 25 | } 26 | 27 | val data = gameManager.list(begin, end) 28 | return GameListResp(count, data) 29 | } 30 | 31 | override fun roomList(page: Int): List { 32 | val begin = when (page <= 0) { 33 | true -> 0 34 | false -> (page - 1) * size 35 | } 36 | 37 | val count = roomManager.count() 38 | val end = when (begin + size < count) { 39 | true -> begin + size 40 | false -> count 41 | } 42 | 43 | return roomManager.queryRoomList(begin, end) 44 | } 45 | 46 | override fun roomList(game: String): List? { 47 | return roomManager.queryRoomListByGame(game) 48 | } 49 | 50 | override fun queryRoom(roomId: String): Room { 51 | return roomManager.queryRoomNotNull(roomId) 52 | } 53 | 54 | override fun queryGame(gameId: String): GameInfo { 55 | return gameManager.queryGameInfoByHash(gameId) 56 | } 57 | } -------------------------------------------------------------------------------- /lightning/src/main/java/org/starcoin/lightning/client/AddressFormatException.java: -------------------------------------------------------------------------------- 1 | package org.starcoin.lightning.client; 2 | 3 | @SuppressWarnings("serial") 4 | public class AddressFormatException extends IllegalArgumentException { 5 | public AddressFormatException() { 6 | super(); 7 | } 8 | 9 | public AddressFormatException(String message) { 10 | super(message); 11 | } 12 | 13 | public static class InvalidCharacter extends AddressFormatException { 14 | public final char character; 15 | public final int position; 16 | 17 | public InvalidCharacter(char character, int position) { 18 | super("Invalid character '" + Character.toString(character) + "' at position " + position); 19 | this.character = character; 20 | this.position = position; 21 | } 22 | } 23 | 24 | public static class InvalidDataLength extends AddressFormatException { 25 | public InvalidDataLength() { 26 | super(); 27 | } 28 | 29 | public InvalidDataLength(String message) { 30 | super(message); 31 | } 32 | } 33 | 34 | public static class InvalidChecksum extends AddressFormatException { 35 | public InvalidChecksum() { 36 | super("Checksum does not validate"); 37 | } 38 | 39 | public InvalidChecksum(String message) { 40 | super(message); 41 | } 42 | } 43 | 44 | public static class InvalidPrefix extends AddressFormatException { 45 | public InvalidPrefix() { 46 | super(); 47 | } 48 | 49 | public InvalidPrefix(String message) { 50 | super(message); 51 | } 52 | } 53 | 54 | public static class WrongNetwork extends InvalidPrefix { 55 | public WrongNetwork(int versionHeader) { 56 | super("Version code of address did not match acceptable versions for network: " + versionHeader); 57 | } 58 | 59 | public WrongNetwork(String hrp) { 60 | super("Human readable part of address did not match acceptable HRPs for network: " + hrp); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /scripts/lnd/docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | 4 | # btc is an image of bitcoin node which used as base image for btcd and 5 | # btccli. The environment variables default values determined on stage of 6 | # container start within starting script. 7 | btc: 8 | image: starcoin/btcd 9 | build: 10 | context: btcd/ 11 | volumes: 12 | - shared:/rpc 13 | - bitcoin:/data 14 | environment: 15 | - RPCUSER 16 | - RPCPASS 17 | - NETWORK 18 | 19 | btcd: 20 | extends: btc 21 | container_name: btcd 22 | environment: 23 | - DEBUG 24 | - MINING_ADDRESS 25 | entrypoint: ["./start-btcd.sh"] 26 | 27 | btcctl: 28 | extends: btc 29 | container_name: btcctl 30 | links: 31 | - "btcd:rpcserver" 32 | entrypoint: ["./start-btcctl.sh"] 33 | 34 | 35 | lnd: 36 | image: starcoin/lnd 37 | build: 38 | context: ../ 39 | dockerfile: docker/lnd/Dockerfile 40 | environment: 41 | - RPCUSER 42 | - RPCPASS 43 | - NETWORK 44 | - CHAIN 45 | - DEBUG 46 | volumes: 47 | - shared:/rpc 48 | - lnd:/root/.lnd 49 | entrypoint: ["./start-lnd.sh","--rpclisten","0.0.0.0:10009","--restlisten","0.0.0.0:80"] 50 | 51 | lnd_btc: 52 | extends: lnd 53 | container_name: lnd_btc 54 | links: 55 | - "btcd:blockchain" 56 | 57 | volumes: 58 | # shared volume is need to store the btcd rpc certificates and use it within 59 | # btcctl and lnd containers. 60 | shared: 61 | driver: local 62 | 63 | # bitcoin volume is needed for maintaining blockchain persistence 64 | # during btcd container recreation. 65 | bitcoin: 66 | driver: local 67 | 68 | lnd: 69 | driver: local 70 | driver_opts: 71 | type: none 72 | device: /tmp/thor/lnd 73 | o: bind 74 | 75 | -------------------------------------------------------------------------------- /scripts/lnd/docker/btcd/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.12-alpine as builder 2 | 3 | MAINTAINER Olaoluwa Osuntokun 4 | 5 | # Install build dependencies such as git and glide. 6 | RUN apk add --no-cache git gcc musl-dev 7 | 8 | WORKDIR $GOPATH/src/github.com/btcsuite/btcd 9 | 10 | # Grab and install the latest version of of btcd and all related dependencies. 11 | RUN git clone https://github.com/btcsuite/btcd.git . \ 12 | && GO111MODULE=on go install -v . ./cmd/... 13 | 14 | # Start a new image 15 | FROM alpine as final 16 | 17 | # Expose mainnet ports (server, rpc) 18 | EXPOSE 8333 8334 19 | 20 | # Expose testnet ports (server, rpc) 21 | EXPOSE 18333 18334 22 | 23 | # Expose simnet ports (server, rpc) 24 | EXPOSE 18555 18556 25 | 26 | # Expose segnet ports (server, rpc) 27 | EXPOSE 28901 28902 28 | 29 | # Copy the compiled binaries from the builder image. 30 | COPY --from=builder /go/bin/addblock /bin/ 31 | COPY --from=builder /go/bin/btcctl /bin/ 32 | COPY --from=builder /go/bin/btcd /bin/ 33 | COPY --from=builder /go/bin/findcheckpoint /bin/ 34 | COPY --from=builder /go/bin/gencerts /bin/ 35 | 36 | COPY "start-btcctl.sh" . 37 | COPY "start-btcd.sh" . 38 | 39 | RUN apk add --no-cache \ 40 | bash \ 41 | ca-certificates \ 42 | && mkdir "/rpc" "/root/.btcd" "/root/.btcctl" \ 43 | && touch "/root/.btcd/btcd.conf" \ 44 | && chmod +x start-btcctl.sh \ 45 | && chmod +x start-btcd.sh \ 46 | # Manually generate certificate and add all domains, it is needed to connect 47 | # "btcctl" and "lnd" to "btcd" over docker links. 48 | && "/bin/gencerts" --host="*" --directory="/rpc" --force 49 | 50 | # Create a volume to house pregenerated RPC credentials. This will be 51 | # shared with any lnd, btcctl containers so they can securely query btcd's RPC 52 | # server. 53 | # You should NOT do this before certificate generation! 54 | # Otherwise manually generated certificate will be overridden with shared 55 | # mounted volume! For more info read dockerfile "VOLUME" documentation. 56 | VOLUME ["/rpc"] 57 | -------------------------------------------------------------------------------- /lightning/src/test/java/org/starcoin/lightning/client/UtilsTest.java: -------------------------------------------------------------------------------- 1 | package org.starcoin.lightning.client; 2 | 3 | import java.io.IOException; 4 | import org.bitcoinj.core.SignatureDecodeException; 5 | import org.junit.Test; 6 | import org.starcoin.lightning.client.core.Invoice; 7 | 8 | public class UtilsTest { 9 | 10 | @Test 11 | public void testDecode() throws IOException, SignatureDecodeException { 12 | String hash ="lnltc241pvjluezpp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqd5d7xmw5fk98klysy043l2ahrqsnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66859t2d55efrxdlgqg9hdqskfstdmyssdw4fjc8qdl522ct885pqk7acn2aczh0jeht0xhuhnkmm3h0qsrxedlwm9x86787zzn4qwwwcpjkl3t2"; 13 | Invoice invoice =Utils.decode(hash); 14 | System.out.println(invoice); 15 | System.out.println(HashUtils.bytesToHex(invoice.getPublicKey())); 16 | 17 | hash ="lnsb2u1pwt6u20pp5ug69a6sc8cqlu6xt5vhx84jlg3yj6ypf8mzpe9665k8j6dketm6sdqqcqzpgejzjas3wv2hzxfzdnnnlshqfykaylq839fm92qcxhpsw5zhshwqhjgqvynchjnjmuyg0qlyavpxelr8g5plx735zgvlk2ju39z08ccqqfjds7j"; 18 | invoice =Utils.decode(hash); 19 | System.out.println(invoice); 20 | System.out.println(HashUtils.bytesToHex(invoice.getPublicKey())); 21 | 22 | hash ="lnbc241pveeq09pp5qqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqqqsyqcyq5rqwzqfqypqdqqnp4q0n326hr8v9zprg8gsvezcch06gfaqqhde2aj730yg0durunfhv66jd3m5klcwhq68vdsmx2rjgxeay5v0tkt2v5sjaky4eqahe4fx3k9sqavvce3capfuwv8rvjng57jrtfajn5dkpqv8yelsewtljwmmycq62k443"; 23 | invoice =Utils.decode(hash); 24 | System.out.println(invoice); 25 | System.out.println(HashUtils.bytesToHex(invoice.getPublicKey())); 26 | 27 | hash ="lntb1m1pdthhh0pp5063r8hu6f6hk7tpauhgvl3nnf4ur3xntcnhujcz5w82yq7nhjuysdq6d4ujqenfwfehggrfdemx76trv5xqrrss6uxhewtmjkumpr7w6prkgttku76azfq7l8cx9v74pcv85hzyvs9n23dhu9u354xcqpnzey45ua3g2m4dywuw7udrt2sdsvjf3rawdqcpas9mah"; 28 | invoice =Utils.decode(hash); 29 | System.out.println(invoice); 30 | System.out.println(HashUtils.bytesToHex(invoice.getPublicKey())); 31 | 32 | } 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /thor-core/src/main/kotlin/org/starcoin/thor/core/arbitrate/ContractImpl.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.core.arbitrate 2 | 3 | import com.github.kittinunf.fuel.Fuel 4 | import com.github.kittinunf.fuel.core.FuelManager 5 | import com.google.common.io.BaseEncoding 6 | import java.lang.RuntimeException 7 | 8 | class ContractImpl(url: String, private val id: String, private val codeSource: ByteArray) : Contract() { 9 | 10 | private val initPath = "/api/vm" 11 | private val execPath = "/api/execute" 12 | 13 | init { 14 | FuelManager.instance.basePath = url 15 | var resp = Fuel.post(initPath, listOf("id" to id, "source" to BaseEncoding.base64Url().encode(getSourceCode()))) 16 | .response().second 17 | 18 | if (resp.statusCode != 200) { 19 | throw RuntimeException("Init contract failed,code:${resp.statusCode}, ${resp.body().asString("text/plain")}") 20 | } 21 | resp = Fuel.post(execPath, listOf("id" to id, "cmd" to "0")).response().second 22 | if (resp.statusCode != 200) { 23 | throw RuntimeException("Call init of contract failed, code:${resp.statusCode}," + 24 | "${resp.body().asString("text/plain")}") 25 | } 26 | } 27 | 28 | 29 | override fun getWinner(): String { 30 | val resp = Fuel.post(execPath, listOf("id" to id, "cmd" to "2")).response().second 31 | if (resp.statusCode != 200) { 32 | throw RuntimeException("Call getWinner of contract failed, code:${resp.statusCode}") 33 | } 34 | return resp.body().asString("text/plain") 35 | } 36 | 37 | override fun update(userId: String, state: String) { 38 | val opcode = 1 39 | val cmd = "$opcode,$userId,$state" 40 | val resp = Fuel.post(execPath, listOf("id" to id, "cmd" to cmd)).response().second 41 | if (resp.statusCode != 200) { 42 | throw RuntimeException("Call update of contract failed, code:${resp.statusCode}") 43 | } 44 | } 45 | 46 | override fun getSourceCode(): ByteArray { 47 | return codeSource 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /scripts/lnd/docker/btcd/start-btcd.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # exit from script if error was raised. 4 | set -e 5 | 6 | # error function is used within a bash function in order to send the error 7 | # message directly to the stderr output and exit. 8 | error() { 9 | echo "$1" > /dev/stderr 10 | exit 0 11 | } 12 | 13 | # return is used within bash function in order to return the value. 14 | return() { 15 | echo "$1" 16 | } 17 | 18 | # set_default function gives the ability to move the setting of default 19 | # env variable from docker file to the script thereby giving the ability to the 20 | # user override it durin container start. 21 | set_default() { 22 | # docker initialized env variables with blank string and we can't just 23 | # use -z flag as usually. 24 | BLANK_STRING='""' 25 | 26 | VARIABLE="$1" 27 | DEFAULT="$2" 28 | 29 | if [[ -z "$VARIABLE" || "$VARIABLE" == "$BLANK_STRING" ]]; then 30 | 31 | if [ -z "$DEFAULT" ]; then 32 | error "You should specify default variable" 33 | else 34 | VARIABLE="$DEFAULT" 35 | fi 36 | fi 37 | 38 | return "$VARIABLE" 39 | } 40 | 41 | # Set default variables if needed. 42 | RPCUSER=$(set_default "$RPCUSER" "admin") 43 | RPCPASS=$(set_default "$RPCPASS" "admin") 44 | DEBUG=$(set_default "$DEBUG" "debug") 45 | NETWORK=$(set_default "$NETWORK" "simnet") 46 | 47 | PARAMS="" 48 | if [ "$NETWORK" != "mainnet" ]; then 49 | PARAMS=$(echo --$NETWORK) 50 | fi 51 | 52 | PARAMS=$(echo $PARAMS \ 53 | "--debuglevel=$DEBUG" \ 54 | "--rpcuser=$RPCUSER" \ 55 | "--rpcpass=$RPCPASS" \ 56 | "--datadir=/data" \ 57 | "--logdir=/data" \ 58 | "--rpccert=/rpc/rpc.cert" \ 59 | "--rpckey=/rpc/rpc.key" \ 60 | "--rpclisten=0.0.0.0" \ 61 | "--txindex" 62 | ) 63 | 64 | # Set the mining flag only if address is non empty. 65 | if [[ -n "$MINING_ADDRESS" ]]; then 66 | PARAMS="$PARAMS --miningaddr=$MINING_ADDRESS" 67 | fi 68 | 69 | # Add user parameters to command. 70 | PARAMS="$PARAMS $@" 71 | 72 | # Print command and start bitcoin node. 73 | echo "Command: btcd $PARAMS" 74 | exec btcd $PARAMS 75 | 76 | -------------------------------------------------------------------------------- /thor-arbitrate-service/src/main/kotlin/org/starcoin/thor/manager/PaymentManager.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.manager 2 | 3 | import org.starcoin.lightning.client.HashUtils 4 | import java.security.SecureRandom 5 | 6 | data class PaymentInfo(val userId: String, val r: ByteArray, val rHash: ByteArray) 7 | 8 | class PaymentManager { 9 | private val paymentMap = mutableMapOf>()//roomId -> userId 10 | 11 | fun generatePayments(roomId: String, firstUserId: String, secondUserId: String): Pair { 12 | return when (paymentMap.containsKey(roomId)) { 13 | false -> { 14 | val first = generatePaymentInfo(firstUserId) 15 | val second = generatePaymentInfo(secondUserId) 16 | 17 | val newPair = Pair(first, second) 18 | synchronized(this) { 19 | paymentMap[roomId] = newPair 20 | } 21 | newPair 22 | } 23 | true -> paymentMap[roomId]!! 24 | } 25 | } 26 | 27 | fun surrenderR(surrender: String, roomId: String): ByteArray? { 28 | val pair = paymentMap[roomId] 29 | return when (surrender) { 30 | pair!!.first.userId -> pair.second.r 31 | pair.second.userId -> pair.first.r 32 | else -> null 33 | } 34 | } 35 | 36 | fun queryRHash(userId: String, roomId: String): ByteArray? { 37 | return when (val pair = paymentMap[roomId]) { 38 | null -> null 39 | else -> when (userId) { 40 | pair.first.userId -> pair.first.rHash 41 | pair.second.userId -> pair.second.rHash 42 | else -> null 43 | } 44 | } 45 | } 46 | 47 | private fun generatePaymentInfo(addr: String): PaymentInfo { 48 | val r = ByteArray(32) 49 | SecureRandom.getInstanceStrong().nextBytes(r) 50 | val rHash = HashUtils.sha256(r)!! 51 | return PaymentInfo(addr, r, rHash) 52 | } 53 | 54 | fun clearPaymentInfoByRoomId(roomId: String) { 55 | paymentMap.remove(roomId) 56 | } 57 | } -------------------------------------------------------------------------------- /thor-arbitrate-service/src/main/kotlin/org/starcoin/thor/server/Main.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.server 2 | 3 | import org.starcoin.sirius.util.WithLogging 4 | import org.starcoin.sirius.util.logger 5 | import org.starcoin.thor.core.GameInfo 6 | import org.starcoin.thor.manager.GameManager 7 | import org.starcoin.thor.manager.RoomManager 8 | import java.nio.file.FileSystems 9 | import java.nio.file.Files 10 | import java.nio.file.Path 11 | import java.nio.file.Paths 12 | import kotlin.streams.toList 13 | 14 | fun loadGames(): List { 15 | val uri = GameService::class.java.getResource("/games").toURI() 16 | val path: Path 17 | path = if (uri.scheme == "jar") { 18 | val fileSystem = FileSystems.newFileSystem(uri, mutableMapOf()) 19 | fileSystem.getPath("/games") 20 | } else { 21 | Paths.get(uri) 22 | } 23 | WithLogging.logger().info("path:${path}") 24 | return Files.list(path).map { p -> 25 | WithLogging.logger().info("scan $p") 26 | val gameName = p.fileName.toString().let { if (it.endsWith("/")) it.substring(0, it.length - 1) else it } 27 | var engine: ByteArray? = null 28 | var gui: ByteArray? = null 29 | Files.list(p).forEach { 30 | WithLogging.logger().info(it.toString()) 31 | if (it.fileName.endsWith("engine.wasm")) { 32 | engine = Files.newInputStream(it).use { it.readBytes() } 33 | } else if (it.fileName.endsWith("gui.wasm")) { 34 | gui = Files.newInputStream(it).use { it.readBytes() } 35 | } 36 | } 37 | check(engine != null && gui != null) { "can not find engine and gui file at path $p" } 38 | GameInfo(gameName, engine!!, gui!!) 39 | }.toList() 40 | } 41 | 42 | fun main(args: Array) { 43 | WithLogging.enableAllLog() 44 | val gameManager = GameManager() 45 | val roomManager = RoomManager() 46 | loadGames().forEach { game -> 47 | WithLogging.logger().info(("create pre config game: ${game.base}")) 48 | gameManager.createGame(game) 49 | } 50 | val websocketServer = WebsocketServer(gameManager, roomManager) 51 | websocketServer.start() 52 | } -------------------------------------------------------------------------------- /thor-core/src/test/kotlin/org/starcoin/thor/core/arbitrate/ArbitrateTest.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.core.arbitrate 2 | 3 | import org.junit.Assert 4 | import org.junit.Test 5 | import org.starcoin.sirius.serialization.ByteArrayWrapper 6 | import org.starcoin.thor.core.WitnessData 7 | import java.util.* 8 | import kotlin.collections.ArrayList 9 | 10 | class MockContractInput(private val userId: String) : ContractInput { 11 | override fun reset() { 12 | it = proof.iterator() 13 | } 14 | 15 | private val proof = ArrayList() 16 | private var it: Iterator 17 | 18 | init { 19 | proof.add(ArbitrateDataImpl(listOf("1"), 20 | WitnessData(this.userId, 21 | ByteArrayWrapper(ByteArray(1)), "", 22 | ByteArrayWrapper(ByteArray(1)), 1, null, null) 23 | )) 24 | it = proof.iterator() 25 | 26 | } 27 | 28 | override fun next(): ArbitrateData { 29 | 30 | return it.next() 31 | } 32 | 33 | override fun hasNext(): Boolean { 34 | return it.hasNext() 35 | } 36 | 37 | override fun getUser(): String { 38 | return this.userId 39 | } 40 | 41 | } 42 | 43 | class ArbitrateTest { 44 | @Test 45 | fun testChallenge() { 46 | val arb = ArbitrateImpl(2000) { it -> println("the winner:$it") } 47 | val proof = MockContractInput("1") 48 | val proof1 = MockContractInput("2") 49 | 50 | val srcCode = this::class.java.getResource("/engine.wasm").readBytes() 51 | Assert.assertTrue(arb.join("1", ContractImpl("http://localhost:3000", "1", srcCode))) 52 | Assert.assertTrue(arb.join("2", ContractImpl("http://localhost:3000", "2", srcCode))) 53 | arb.challenge(proof) 54 | arb.challenge(proof1) 55 | Thread.sleep(3000) 56 | } 57 | 58 | @Test 59 | fun testTimer() { 60 | fun current() = Calendar.getInstance().timeInMillis 61 | val t = Timer(2000) { println("notify run on:${current()}") } 62 | 63 | t.start() 64 | while (t.getLeftTime() != 0.toLong()) { 65 | Thread.sleep(1000) 66 | println("timer knock knock,${current()}") 67 | } 68 | Thread.sleep(1000) 69 | 70 | } 71 | } -------------------------------------------------------------------------------- /thor-app/src/renderer/components/storage.ts: -------------------------------------------------------------------------------- 1 | import crypto from "../sdk/crypto"; 2 | import {WitnessData, Witnesses} from "../sdk/client"; 3 | 4 | 5 | namespace storage { 6 | const keyPairKey: string = 'key_pair'; 7 | const witnessDataKey: string = 'witness_data'; 8 | const configKey: string = 'config'; 9 | 10 | export function loadOrGenerateKeyPair(): crypto.ECPair { 11 | let keyPair: crypto.ECPair; 12 | let keyPairWIF = localStorage.getItem(keyPairKey); 13 | if (keyPairWIF != null) { 14 | keyPair = crypto.fromWIF(keyPairWIF); 15 | } else { 16 | keyPair = crypto.generateKeyPair(); 17 | localStorage.setItem(keyPairKey, keyPair.toWIF()) 18 | } 19 | return keyPair 20 | } 21 | 22 | export function addWitnessData(roomId: string, witnessData: WitnessData) { 23 | let witnesses = loadWitnesses(roomId); 24 | witnesses.witnessList.push(witnessData) 25 | saveWitnesses(roomId, witnesses); 26 | } 27 | 28 | export function loadWitnesses(roomId: string): Witnesses { 29 | let key = roomId + "-" + witnessDataKey; 30 | let witnessDataStr = localStorage.getItem(key); 31 | let witnesses = new Witnesses(); 32 | witnesses.roomId = roomId; 33 | if (witnessDataStr != null) { 34 | try { 35 | let jsonObj = JSON.parse(witnessDataStr); 36 | witnesses.initWithJson(jsonObj); 37 | } catch (e) { 38 | console.error(e); 39 | localStorage.removeItem(key); 40 | } 41 | } 42 | return witnesses; 43 | } 44 | 45 | export function saveWitnesses(roomId: string, witnesses: Witnesses) { 46 | localStorage.setItem(roomId + "-" + witnessDataKey, JSON.stringify(witnesses.toJSONObj())); 47 | } 48 | 49 | export function getLatestWitnessData(roomId: string): WitnessData | null { 50 | let witnesses = loadWitnesses(roomId); 51 | if (witnesses.witnessList.length == 0) { 52 | return null 53 | } 54 | return witnesses.witnessList[0]; 55 | } 56 | 57 | export function saveConfig(config: any) { 58 | localStorage.setItem(configKey, JSON.stringify(config)); 59 | } 60 | 61 | export function loadConfig(): any { 62 | let configStr = localStorage.getItem(configKey); 63 | if (configStr == null) { 64 | return {} 65 | } else { 66 | return JSON.parse(configStr); 67 | } 68 | } 69 | } 70 | 71 | // @ts-ignore 72 | export default storage 73 | -------------------------------------------------------------------------------- /thor-core/src/test/kotlin/org/statcoin/thor/test/sign/SignTest.kt: -------------------------------------------------------------------------------- 1 | package org.statcoin.thor.test.sign 2 | 3 | import org.junit.Assert 4 | import org.junit.Test 5 | import org.starcoin.sirius.lang.hexToByteArray 6 | import org.starcoin.thor.sign.SignService 7 | import org.starcoin.thor.sign.toByteArray 8 | import org.starcoin.thor.sign.toECKey 9 | import org.starcoin.thor.sign.toHEX 10 | import org.starcoin.thor.utils.decodeBase64 11 | 12 | class SignTest { 13 | 14 | @Test 15 | fun testSign() { 16 | val key = SignService.generateKeyPair() 17 | 18 | val str = "ssssss".toByteArray() 19 | val sign = SignService.sign(str, key.private) 20 | 21 | assert(SignService.verifySign(str, sign, key.public)) 22 | } 23 | 24 | @Test 25 | fun testString() { 26 | val key = SignService.generateKeyPair() 27 | val pubStr = key.public.toHEX() 28 | val priStr = key.private.toHEX() 29 | 30 | assert(key.public == SignService.hexToPubKey(pubStr)) 31 | assert(key.private == SignService.hexToPriKey(priStr)) 32 | } 33 | 34 | @Test 35 | fun testPublicKey() { 36 | val key = SignService.generateKeyPair() 37 | val bytes = key.public.toByteArray() 38 | println(bytes.size) 39 | val key2 = SignService.toPubKey(bytes) 40 | Assert.assertArrayEquals(bytes, key2.toByteArray()) 41 | } 42 | 43 | @Test 44 | fun testPublicString() { 45 | val pubKeyStr = "0x02ee56571afcbd565eff25c95eb30eaf3438acc507c07ace063a8edb40788c6e04" 46 | val bytes = pubKeyStr.hexToByteArray() 47 | println(bytes.size) 48 | val key = SignService.toPubKey(bytes) 49 | Assert.assertEquals(pubKeyStr, key.toHEX()) 50 | } 51 | 52 | @Test 53 | fun testECKey() { 54 | val key = SignService.generateKeyPair() 55 | val ecKey = key.private.toECKey(); 56 | println(ecKey) 57 | } 58 | 59 | @Test 60 | fun testMsgSignature() { 61 | val key = SignService.generateKeyPair() 62 | val signature = SignService.signMessage("test", key.private) 63 | val signatureBytes = signature.decodeBase64() 64 | println(signatureBytes.size) 65 | } 66 | 67 | @Test 68 | fun testHex() { 69 | val hex = "a22303235383239336266623834386434353534343538366539316438636330356136386362373663333634653932623730333233633631653238623262373438343962227d7d" 70 | val str = hex.hexToByteArray().toString(Charsets.UTF_8) 71 | println(str) 72 | } 73 | } -------------------------------------------------------------------------------- /thor-core/src/main/kotlin/org/starcoin/thor/core/arbitrate/ArbitrateImpl.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.core.arbitrate 2 | 3 | import java.lang.RuntimeException 4 | import java.util.* 5 | import kotlin.collections.HashMap 6 | import kotlinx.coroutines.* 7 | 8 | class ArbitrateImpl(periodMils: Long, finishNotify: (winner: String) -> Unit) : Arbitrate { 9 | private var winner: String = "0" 10 | private var status: Status = Status.NOTOPEN 11 | private var users: MutableMap = HashMap() 12 | private val timer = Timer(periodMils) { println("the winner is ${this.winner}");finishNotify(this.winner) } 13 | 14 | init { 15 | this.status = Status.OPEN 16 | timer.start() 17 | } 18 | 19 | override fun join(userId: String, contract: Contract): Boolean { 20 | if (this.users[userId] != null) { 21 | return false 22 | } 23 | 24 | this.users[userId] = contract 25 | return true 26 | } 27 | 28 | override fun challenge(proof: ContractInput) { 29 | val userId = proof.getUser() 30 | val contract = this.users[userId] 31 | ?: throw RuntimeException("User not join in arbitrate") 32 | if (timer.getLeftTime() == 0.toLong()) { 33 | this.status = Status.FINISH 34 | return 35 | } 36 | 37 | contract.updateAll(proof) 38 | this.winner = contract.getWinner() 39 | if (this.winner != "0") { 40 | this.status = Status.FINISH 41 | return 42 | } 43 | proof.reset() 44 | this.status = Status.FINISH 45 | this.winner = contract.checkTimeout(proof, 10) ?: userId 46 | } 47 | 48 | override fun getLeftTime() = this.timer.getLeftTime() 49 | 50 | override fun getWinner() = this.winner 51 | 52 | override fun getStatus() = this.status 53 | } 54 | 55 | enum class Status { 56 | NOTOPEN, 57 | OPEN, 58 | FINISH, 59 | } 60 | 61 | class Timer(periodMils: Long, private val notify: () -> Unit) { 62 | private var leftTime: Long = periodMils 63 | var startTime: Long = 0 64 | 65 | fun start() { 66 | startTime = current() 67 | GlobalScope.launch { 68 | while (leftTime > 0) { 69 | leftTime -= (current() - startTime) 70 | delay(500) 71 | } 72 | notify() 73 | } 74 | } 75 | 76 | fun getLeftTime(): Long = if ((this.leftTime) < 0) 0 else this.leftTime 77 | 78 | private fun current() = Calendar.getInstance().timeInMillis 79 | } 80 | 81 | -------------------------------------------------------------------------------- /thor-app/src/renderer/sdk/vm-minimal.ts: -------------------------------------------------------------------------------- 1 | //import "@types/webassembly-js-api"; 2 | 3 | import {ASUtil, TypedArrayConstructor} from "assemblyscript/lib/loader"; 4 | import * as fs from 'fs'; 5 | 6 | const env = { 7 | memoryBase: 0, 8 | tableBase: 0, 9 | memory: new WebAssembly.Memory({ 10 | initial: 0 11 | }), 12 | abort(msg: number, file: number, line: number, column: number) { 13 | console.error("abort called at " + file + ":" + line + ":" + column + ", msg:" + msg); 14 | } 15 | }; 16 | 17 | class ASModuleWrapper { 18 | module: ASUtil | null = null; 19 | 20 | init(module: ASUtil): void { 21 | this.module = module; 22 | } 23 | 24 | protected getString = (value: number) => { 25 | if (this.module == null) { 26 | return value; 27 | } else { 28 | return this.module.getString(value); 29 | } 30 | }; 31 | 32 | protected getArray = (type: TypedArrayConstructor, value: number) => { 33 | if (this.module == null) { 34 | return value; 35 | } else { 36 | return this.module.getArray(type, value); 37 | } 38 | }; 39 | } 40 | 41 | //function use array style for keep this ref. 42 | //see https://github.com/Microsoft/TypeScript/wiki/'this'-in-TypeScript 43 | class Console extends ASModuleWrapper { 44 | 45 | public log = (value: number) => { 46 | console.log(this.getString(value)); 47 | }; 48 | public logf = (msg: number, value: number) => { 49 | console.log(this.getString(msg), value) 50 | }; 51 | public logi = (msg: number, value: number) => { 52 | console.log(this.getString(msg), value) 53 | }; 54 | public logAction = (msg: number, player: number, state: number) => { 55 | console.log(this.getString(msg) + " player:", player, this.getArray(Int8Array, state)) 56 | }; 57 | public error = (value: number) => { 58 | alert(this.getString(value)); 59 | }; 60 | } 61 | 62 | class Listener extends ASModuleWrapper { 63 | 64 | public onUpdate = (player: number, state: number) => { 65 | console.log("listener onUpdate", player, this.getArray(Int8Array, state)); 66 | }; 67 | 68 | public onGameOver = (player: number) => { 69 | console.log("listener onGameOver", player); 70 | alert("Game Over Winner is:" + player); 71 | } 72 | } 73 | 74 | 75 | const engineConsole = new Console(); 76 | const listener = new Listener(); 77 | 78 | let mod = new WebAssembly.Module(fs.readFileSync("../../../wasm/engine_optimized.wasm")); 79 | 80 | const instance=new WebAssembly.Instance(mod, { 81 | env: env, 82 | console: engineConsole, 83 | listener: listener, 84 | }); 85 | module.exports=instance.exports -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /scripts/lnd/util.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | SCRIPTPATH="$( cd "$(dirname "$0")" ; pwd -P )" 3 | COMPOSE_FILE="$SCRIPTPATH/docker/docker-compose.yml" 4 | export NETWORK="simnet" 5 | init(){ 6 | echo "===============init env=================" 7 | docker-compose -f $COMPOSE_FILE pull btcd lnd 8 | } 9 | 10 | startlnd(){ 11 | echo -e "===============start lnd for $1===================" 12 | docker-compose -f $COMPOSE_FILE run -p$2:10009 -p$3:80 -d --name $1 lnd_btc --lnddir=/root/.lnd/lnd_$1 13 | sleep 6 14 | } 15 | 16 | 17 | new_address(){ 18 | docker exec -i -t $1 lncli --lnddir=/root/.lnd/lnd_$1 --network=simnet newaddress np2wkh|grep address | awk -F "\"" '{print $4}' 19 | } 20 | 21 | 22 | start_btcd(){ 23 | address=$(new_address $1) 24 | echo -e "=======start btcd with mining address: $address=======" 25 | MINING_ADDRESS=$address docker-compose -f $COMPOSE_FILE up -d btcd 26 | docker-compose -f $COMPOSE_FILE run btcctl generate 400 27 | address=$(new_address $2) 28 | echo -e "=======start btcd with mining address: $address=======" 29 | docker-compose -f $COMPOSE_FILE stop btcd 30 | MINING_ADDRESS=$address docker-compose -f $COMPOSE_FILE up -d btcd 31 | docker-compose -f $COMPOSE_FILE run btcctl generate 400 32 | sleep 6 33 | } 34 | 35 | 36 | clean(){ 37 | echo "==============clean env==================" 38 | docker rm alice -f 39 | docker rm bob -f 40 | docker-compose -f $COMPOSE_FILE stop 41 | docker-compose -f $COMPOSE_FILE rm -f 42 | docker volume rm docker_bitcoin 43 | docker volume rm docker_shared 44 | docker volume rm docker_lnd 45 | rm -rf /tmp/thor 46 | mkdir -p /tmp/thor/lnd 47 | } 48 | 49 | lncl(){ 50 | local user=$1 51 | shift 1 52 | docker exec -i -t $user lncli --lnddir=/root/.lnd/lnd_$user --network=simnet $@ 53 | } 54 | 55 | create_channel(){ 56 | bob_pubkey=$(lncl bob getinfo|grep identity_pubkey | awk -F "\"" '{print $4}') 57 | bob_address=$(docker inspect bob | grep IPAddress | awk -F "\"" '{print $4}'|grep -v '^$') 58 | lncl alice connect $bob_pubkey@$bob_address 59 | lncl alice --network=simnet listpeers 60 | lncl bob --network=simnet listpeers 61 | lncl alice openchannel --node_key=$bob_pubkey --local_amt=11000000 --push_amt=10000000 62 | docker-compose -f $COMPOSE_FILE run btcctl generate 10 63 | } 64 | 65 | get_macaroon(){ 66 | local lndpath="/tmp/thor/lnd/lnd_$1/data/chain/bitcoin/simnet/admin.macaroon" 67 | local macaroon=$(xxd -ps -c 200 $lndpath | tr -d '\n') 68 | echo $macaroon 69 | } 70 | 71 | 72 | setup_lnd(){ 73 | init 74 | clean 75 | startlnd alice 10009 8080 76 | startlnd bob 20009 8081 77 | start_btcd alice bob 78 | create_channel 79 | } 80 | -------------------------------------------------------------------------------- /thor-core/src/main/kotlin/org/starcoin/thor/core/Room.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.core 2 | 3 | import kotlinx.serialization.SerialId 4 | import kotlinx.serialization.Serializable 5 | import org.starcoin.sirius.serialization.ByteArrayWrapper 6 | import org.starcoin.thor.sign.toByteArray 7 | import org.starcoin.thor.utils.randomString 8 | 9 | @Serializable 10 | data class PlayerInfo(@SerialId(1) val playerUserId: String, @SerialId(2) val playerName: String, @SerialId(3) val playerPubKey: ByteArrayWrapper, @SerialId(4) var ready: Boolean = false, @SerialId(5) var rHash: ByteArrayWrapper? = null) 11 | 12 | @Serializable 13 | data class Room(@SerialId(1) val roomId: String, @SerialId(2) val gameId: String, @SerialId(3) val name: String, @SerialId(4) var players: MutableList, @SerialId(5) val capacity: Int, @SerialId(6) val cost: Long = 0, @SerialId(7) val timeout: Long = 0, @SerialId(8) var begin: Long = 0) : MsgObject() { 14 | 15 | @kotlinx.serialization.Transient 16 | val isFull: Boolean 17 | get() = !this.players.isNullOrEmpty() && this.players.size >= capacity 18 | 19 | @kotlinx.serialization.Transient 20 | val payment: Boolean 21 | get() = cost > 0 22 | 23 | 24 | constructor(gameId: String, name: String, cost: Long, timeout: Long) : this(randomString(), gameId, name, mutableListOf(), 2, cost, timeout, System.currentTimeMillis()) { 25 | check(cost >= 0) 26 | } 27 | 28 | fun addPlayer(userInfo: UserInfo) { 29 | synchronized(this) { 30 | check(!isFull) 31 | if (!this.players.map { playerInfo -> playerInfo.playerUserId }.contains(userInfo.id)) { 32 | this.players.add(PlayerInfo(userInfo.id, userInfo.name, ByteArrayWrapper(userInfo.publicKey.toByteArray()))) 33 | } 34 | } 35 | } 36 | 37 | fun userReady(userId: String) { 38 | synchronized(this) { 39 | this.players.filter { playerInfo -> playerInfo.playerUserId == userId }[0].ready = true 40 | } 41 | } 42 | 43 | fun rHash(userId: String, rHash: ByteArrayWrapper) { 44 | synchronized(this) { 45 | this.players.filter { playerInfo -> playerInfo.playerUserId == userId }[0].rHash = rHash 46 | } 47 | } 48 | 49 | fun roomReady(): Boolean { 50 | var flag = true 51 | players.forEach { flag = (it.ready && flag) } 52 | return isFull && flag 53 | } 54 | 55 | fun deepCopy(): Room { 56 | return this.copy(players = this.players.map { it.copy() }.toMutableList()) 57 | } 58 | 59 | fun rivalPlayer(currentUserId: String): String? { 60 | return this.players.firstOrNull { it.playerUserId != currentUserId }?.playerUserId 61 | } 62 | 63 | fun isInRoom(userId: String): Boolean { 64 | return this.players.any { it.playerUserId == userId } 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /lightning/src/main/java/org/starcoin/lightning/client/HashUtils.java: -------------------------------------------------------------------------------- 1 | package org.starcoin.lightning.client; 2 | 3 | import java.security.MessageDigest; 4 | import java.security.NoSuchAlgorithmException; 5 | import java.security.Security; 6 | import org.bouncycastle.jce.provider.BouncyCastleProvider; 7 | 8 | public class HashUtils { 9 | static { 10 | Security.addProvider(new BouncyCastleProvider()); 11 | } 12 | 13 | public static byte[] intToByteArray(int value) { 14 | return new byte[] { 15 | (byte) (value >>> 24), (byte) (value >>> 16), (byte) (value >>> 8), (byte) value 16 | }; 17 | } 18 | 19 | public static byte[] longToByteArray(long value) { 20 | return new byte[] { 21 | (byte) (value >>> 56), 22 | (byte) (value >>> 48), 23 | (byte) (value >>> 40), 24 | (byte) (value >>> 32), 25 | (byte) (value >>> 24), 26 | (byte) (value >>> 16), 27 | (byte) (value >>> 8), 28 | (byte) value 29 | }; 30 | } 31 | 32 | public static byte[] sha256(int src) { 33 | return sha256(intToByteArray(src)); 34 | } 35 | 36 | public static byte[] sha256(byte[] src) { 37 | MessageDigest digest; 38 | try { 39 | digest = MessageDigest.getInstance("SHA-256"); 40 | byte[] hash = digest.digest(src); 41 | return hash; 42 | } catch (NoSuchAlgorithmException e) { 43 | // TODO Auto-generated catch block 44 | e.printStackTrace(); 45 | } 46 | 47 | assert false; 48 | return null; 49 | } 50 | 51 | public static byte[] hash160(byte[] src) { 52 | MessageDigest digest; 53 | try { 54 | digest = MessageDigest.getInstance("RIPEMD160"); 55 | byte[] hash = digest.digest(src); 56 | return hash; 57 | } catch (NoSuchAlgorithmException e) { 58 | throw new RuntimeException(e); 59 | } 60 | } 61 | 62 | public static String md5Hex(byte[] src) { 63 | return bytesToHex(md5(src)); 64 | } 65 | 66 | public static byte[] md5(byte[] src) { 67 | MessageDigest digest; 68 | try { 69 | digest = MessageDigest.getInstance("MD5"); 70 | byte[] hash = digest.digest(src); 71 | return hash; 72 | } catch (NoSuchAlgorithmException e) { 73 | // TODO Auto-generated catch block 74 | e.printStackTrace(); 75 | } 76 | 77 | return null; 78 | } 79 | 80 | public static String bytesToHex(byte[] bytes) { 81 | StringBuilder result = new StringBuilder(); 82 | for (byte b : bytes) result.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1)); 83 | return result.toString(); 84 | } 85 | 86 | public static byte[] hexToBytes(String hexString) { 87 | int len = hexString.length(); 88 | byte[] data = new byte[len / 2]; 89 | for (int i = 0; i < len; i += 2) { 90 | data[i / 2] = (byte) ((Character.digit(hexString.charAt(i), 16) << 4) 91 | + Character.digit(hexString.charAt(i+1), 16)); 92 | } 93 | return data; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /thor-app/src/renderer/sdk/util.ts: -------------------------------------------------------------------------------- 1 | import crypto from "./crypto"; 2 | import {Int64BE} from "int64-buffer"; 3 | 4 | namespace util { 5 | 6 | export function decodeSignature(signStr: string): Buffer { 7 | let signature = Buffer.from(signStr, 'base64'); 8 | //server signature length is 65, but bitcoinjs is 64. 9 | if (signature.length == 65) { 10 | signature = signature.slice(1); 11 | } 12 | return signature; 13 | } 14 | 15 | export function doVerifyByJson(jsonObj: any, signStr: string, pubKey: crypto.ECPair): boolean { 16 | let json = JSON.stringify(jsonObj); 17 | let msgData = Buffer.from(json); 18 | let result = doVerifyByData(msgData, signStr, pubKey); 19 | if (!result) { 20 | console.error("verify signature fail:", "originJsonObj:", jsonObj, "json:", json); 21 | } 22 | return result; 23 | } 24 | 25 | export function doVerify(hash: Buffer, signStr: string, pubKey: crypto.ECPair): boolean { 26 | let signature = decodeSignature(signStr) 27 | //bitcoinjs bug, result should be boolean. 28 | let result = pubKey.verify(hash, signature); 29 | if (!result) { 30 | console.error("verify signature fail:", result, "hash:", hash.toString('hex'), "signature:", signStr); 31 | } 32 | return result; 33 | } 34 | 35 | export function doVerifyByData(data: Buffer, signStr: string, pubKey: crypto.ECPair) { 36 | return doVerify(crypto.hash(data), signStr, pubKey); 37 | } 38 | 39 | export function doSign(data: Buffer, key: crypto.ECPair): string { 40 | console.debug("doSign data:", data.toString('hex')); 41 | let hash = crypto.hash(data); 42 | console.debug("doSign hash:", hash.toString('hex')); 43 | return key.sign(hash).toString('base64'); 44 | } 45 | 46 | export function doSignWithString(data: string, key: crypto.ECPair) { 47 | console.debug("doSign string:", data); 48 | return doSign(Buffer.from(data), key); 49 | } 50 | 51 | export function numberToBuffer(number: number): Buffer { 52 | let big = new Int64BE(number); 53 | let buf = big.toBuffer(); 54 | //console.debug("numberToBuffer ", number, buf.toString('hex')); 55 | return buf; 56 | } 57 | 58 | export function check(condition: boolean, msg?: string): void { 59 | if (!condition) { 60 | if (msg) { 61 | throw msg; 62 | } else { 63 | console.error("check error"); 64 | throw "check error."; 65 | } 66 | } 67 | } 68 | 69 | export function decodeHex(hex: string): Buffer { 70 | if (hex.startsWith('0x')) { 71 | hex = hex.slice(2); 72 | } 73 | return Buffer.from(hex, 'hex'); 74 | } 75 | 76 | export function unmarshal(obj: T, jsonObj: any): T { 77 | try { 78 | // @ts-ignore 79 | if (typeof obj["initWithJSON"] === "function") { 80 | // @ts-ignore 81 | obj["initWithJSON"](jsonObj); 82 | } else { 83 | for (var propName in jsonObj) { 84 | // @ts-ignore 85 | obj[propName] = jsonObj[propName] 86 | } 87 | } 88 | } catch (e) { 89 | console.error("unmarshal error.", e); 90 | } 91 | return obj; 92 | } 93 | } 94 | 95 | export default util 96 | 97 | -------------------------------------------------------------------------------- /thor-core/src/main/kotlin/org/starcoin/thor/core/HttpMsg.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.core 2 | 3 | import kotlinx.serialization.* 4 | import org.starcoin.sirius.serialization.ByteArrayWrapper 5 | 6 | enum class HttpType { 7 | DEF, PUB_KEY, GAME_LIST, GAME_INFO, ROOM_LIST, ALL_ROOM_LIST, ROOM; 8 | } 9 | 10 | @Serializable 11 | @UseExperimental(ImplicitReflectionSerializer::class) 12 | data class HttpMsg(val type: HttpType, val data: Data) : MsgObject() { 13 | 14 | @Serializer(HttpMsg::class) 15 | companion object : KSerializer { 16 | private val desc = descriptor 17 | override fun deserialize(decoder: Decoder): HttpMsg { 18 | val cd = decoder.beginStructure(desc) 19 | val mt = cd.decodeStringElement(desc, cd.decodeElementIndex(desc)) 20 | 21 | val index = cd.decodeElementIndex(desc) 22 | val data = when (mt) { 23 | HttpType.PUB_KEY.name -> HttpMsg(HttpType.PUB_KEY, cd.decodeSerializableElement(desc, index, PubKeyReq::class.serializer())) 24 | HttpType.GAME_LIST.name -> HttpMsg(HttpType.GAME_LIST, cd.decodeSerializableElement(desc, index, GameListReq::class.serializer())) 25 | HttpType.GAME_INFO.name -> HttpMsg(HttpType.GAME_INFO, cd.decodeSerializableElement(desc, index, GameInfoReq::class.serializer())) 26 | HttpType.ROOM_LIST.name -> HttpMsg(HttpType.ROOM_LIST, cd.decodeSerializableElement(desc, index, RoomListByGameReq::class.serializer())) 27 | HttpType.ALL_ROOM_LIST.name -> HttpMsg(HttpType.ALL_ROOM_LIST, cd.decodeSerializableElement(desc, index, RoomListReq::class.serializer())) 28 | HttpType.ROOM.name -> HttpMsg(HttpType.ROOM, cd.decodeSerializableElement(desc, index, GetRoomReq::class.serializer())) 29 | else -> { 30 | throw Exception("unknown http type") 31 | } 32 | } 33 | cd.endStructure(desc) 34 | return data 35 | } 36 | 37 | override fun serialize(encoder: Encoder, obj: HttpMsg) { 38 | val ce = encoder.beginStructure(desc) 39 | ce.encodeStringElement(desc, 0, obj.type.name) 40 | ce.encodeSerializableElement(desc, 1, obj.data.javaClass.kotlin.serializer(), obj.data) 41 | ce.endStructure(desc) 42 | } 43 | } 44 | } 45 | 46 | @Serializable 47 | class PubKeyReq : Data() 48 | 49 | @Serializable 50 | data class PubKeyResp(val pubKey: ByteArrayWrapper?) : Data() 51 | 52 | @Serializable 53 | data class CreateGameReq(val gameName: String, val engine: ByteArrayWrapper, val gui: ByteArrayWrapper) : Data() 54 | 55 | @Serializable 56 | data class CreateGameResp(val game: GameBaseInfo?) : Data() 57 | 58 | @Serializable 59 | data class GameListReq(val page: Int) : Data() 60 | 61 | @Serializable 62 | data class GameListResp(val count: Int, val data: List?) : Data() 63 | 64 | @Serializable 65 | data class GameInfoReq(val gameId: String) : Data() 66 | 67 | @Serializable 68 | data class GetRoomReq(val roomId: String) : Data() 69 | 70 | @Serializable 71 | data class RoomListReq(val page: Int) : Data() 72 | 73 | @Serializable 74 | data class RoomListResp(val data: List?) : Data() 75 | 76 | @Serializable 77 | data class RoomListByGameReq(val gameId: String) : Data() 78 | 79 | @Serializable 80 | data class RoomListByGameResp(val data: List?) : Data() 81 | -------------------------------------------------------------------------------- /vm-service/vm.ts: -------------------------------------------------------------------------------- 1 | // import "@types/webassembly-js-api"; 2 | 3 | import {ASUtil, TypedArrayConstructor} from "assemblyscript/lib/loader"; 4 | 5 | 6 | class ASModuleWrapper { 7 | module: ASUtil | null = null; 8 | 9 | init(module: ASUtil): void { 10 | this.module = module; 11 | } 12 | 13 | protected getString = (value: number) => { 14 | if (this.module == null) { 15 | return value; 16 | } else { 17 | return this.module.getString(value); 18 | } 19 | }; 20 | 21 | protected getArray = (type: TypedArrayConstructor, value: number) => { 22 | if (this.module == null) { 23 | return value; 24 | } else { 25 | return this.module.getArray(type, value); 26 | } 27 | }; 28 | } 29 | 30 | //function use array style for keep this ref. 31 | //see https://github.com/Microsoft/TypeScript/wiki/'this'-in-TypeScript 32 | class Console extends ASModuleWrapper { 33 | 34 | public log = (value: number) => { 35 | console.log(this.getString(value)); 36 | }; 37 | public logf = (msg: number, value: number) => { 38 | console.log(this.getString(msg), value) 39 | }; 40 | public logi = (msg: number, value: number) => { 41 | console.log(this.getString(msg), value) 42 | }; 43 | public logAction = (msg: number, player: number, state: number) => { 44 | console.log(this.getString(msg) + " player:", player, this.getArray(Int8Array, state)) 45 | }; 46 | public error = (value: number) => { 47 | alert(this.getString(value)); 48 | }; 49 | } 50 | 51 | class Listener extends ASModuleWrapper { 52 | 53 | public onUpdate = (player: number, state: number) => { 54 | console.log("listener onUpdate", player, this.getArray(Int8Array, state)); 55 | }; 56 | 57 | public onGameOver = (player: number) => { 58 | console.log("listener onGameOver", player); 59 | alert("Game Over Winner is:" + player); 60 | } 61 | } 62 | 63 | 64 | export function createVm(bufSource: Buffer): Vm { 65 | let mod = new WebAssembly.Module(bufSource); 66 | let engineConsole = new Console(); 67 | let listener = new Listener(); 68 | let env = { 69 | memoryBase: 0, 70 | tableBase: 0, 71 | memory: new WebAssembly.Memory({ 72 | initial: 0 73 | }), 74 | abort(msg: number, file: number, line: number, column: number) { 75 | console.error("Abort called at " + file + ":" + line + ":" + column + ", msg:" + msg); 76 | } 77 | }; 78 | const instance = new WebAssembly.Instance(mod, { 79 | env: env, 80 | console: engineConsole, 81 | listener: listener, 82 | }); 83 | return new Vm(instance); 84 | } 85 | 86 | class Vm { 87 | readonly opcodes: Map; 88 | readonly wasm: WebAssembly.Instance; 89 | 90 | constructor(wasm: WebAssembly.Instance) { 91 | this.opcodes = new Map(); 92 | this.wasm = wasm; 93 | this.opcodes.set(0, this.wasm.exports.init); 94 | this.opcodes.set(1, this.wasm.exports.update); 95 | this.opcodes.set(2, this.wasm.exports.getWinner); 96 | this.opcodes.set(3, this.wasm.exports.isGameOver); 97 | } 98 | 99 | execute(opcode, ...argument): any { 100 | let fn = this.opcodes.get(opcode) 101 | if (fn == null) { 102 | // Todo: return error 103 | // callback("Invalid opcode") 104 | return null 105 | } 106 | return fn(...argument) 107 | } 108 | } 109 | 110 | -------------------------------------------------------------------------------- /thor-app/src/main/index.ts: -------------------------------------------------------------------------------- 1 | import {app, BrowserWindow, Menu} from "electron"; 2 | import MenuItemConstructorOptions = Electron.MenuItemConstructorOptions; 3 | 4 | const contextMenu = require("electron-context-menu"); 5 | const debug = require('electron-debug'); 6 | 7 | debug({showDevTools: false}); 8 | 9 | let mainWindow: BrowserWindow | null; 10 | 11 | const isDevelopment = process.env.NODE_ENV !== 'production'; 12 | 13 | contextMenu(); 14 | 15 | const appMenu: MenuItemConstructorOptions = { 16 | label: "Application",//this name is set by project name in package.json on macos. 17 | role: "appMenu", 18 | submenu: [ 19 | {label: "About", role: "about"}, 20 | {label: 'Quit', role: 'quit'} 21 | ] 22 | }; 23 | 24 | const editMenu: MenuItemConstructorOptions = { 25 | label: "Edit", 26 | role: "editMenu" 27 | }; 28 | 29 | const devMenu: MenuItemConstructorOptions = { 30 | label: 'Development', 31 | submenu: [ 32 | {label: 'Reload', role: 'reload'}, 33 | {label: 'ForceReload', role: 'forceReload'}, 34 | {label: 'Toggle DevTools', role: 'toggleDevTools'}, 35 | {label: 'Quit', role: 'quit'} 36 | ] 37 | }; 38 | 39 | function createWindow() { 40 | // Create the browser window. 41 | mainWindow = new BrowserWindow({ 42 | height: 800, 43 | width: 1024, 44 | webPreferences: { 45 | nodeIntegration: true, 46 | devTools: true, 47 | 48 | } 49 | }); 50 | 51 | const url = isDevelopment 52 | ? `http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}` 53 | : `file://${__dirname}/index.html` 54 | 55 | mainWindow.loadURL(url) 56 | 57 | mainWindow.webContents.on('devtools-opened', () => { 58 | mainWindow!.focus() 59 | setImmediate(() => { 60 | mainWindow!.focus() 61 | }) 62 | }); 63 | 64 | // Emitted when the window is closed. 65 | mainWindow.on("closed", () => { 66 | // Dereference the window object, usually you would store windows 67 | // in an array if your app supports multi windows, this is the time 68 | // when you should delete the corresponding element. 69 | mainWindow = null; 70 | }); 71 | 72 | // Create the Application's main menu 73 | let template: MenuItemConstructorOptions[] = [appMenu, editMenu, devMenu]; 74 | 75 | Menu.setApplicationMenu(Menu.buildFromTemplate(template)); 76 | } 77 | 78 | // This method will be called when Electron has finished 79 | // initialization and is ready to create browser windows. 80 | // Some APIs can only be used after this event occurs. 81 | app.on("ready", createWindow); 82 | 83 | // Quit when all windows are closed. 84 | app.on("window-all-closed", () => { 85 | // On OS X it is common for applications and their menu bar 86 | // to stay active until the user quits explicitly with Cmd + Q 87 | if (process.platform !== "darwin") { 88 | app.quit(); 89 | } 90 | }); 91 | 92 | app.on("activate", () => { 93 | // On OS X it"s common to re-create a window in the app when the 94 | // dock icon is clicked and there are no other windows open. 95 | if (mainWindow === null) { 96 | createWindow(); 97 | } 98 | }); 99 | 100 | // In this file you can include the rest of your app"s specific main process 101 | // code. You can also put them in separate files and require them here. 102 | 103 | // SSL/TSL: this is the self signed certificate support 104 | app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { 105 | // On certificate error we disable default behaviour (stop loading the page) 106 | // and we then say "it is all fine - true" to the callback 107 | event.preventDefault(); 108 | callback(true); 109 | }); 110 | -------------------------------------------------------------------------------- /thor-app/src/renderer/components/Gameboard.ts: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import * as vm from "../sdk/vm"; 3 | import {ICanvasSYS} from "as2d/src/util/ICanvasSYS"; 4 | import * as loader from "assemblyscript/lib/loader"; 5 | import {GameGUI} from "../sdk/GameGUI"; 6 | import util from "../sdk/util"; 7 | 8 | let VueCountdown = require('@chenfengyuan/vue-countdown'); 9 | 10 | Vue.component(VueCountdown.name, VueCountdown); 11 | 12 | interface ComponentData { 13 | game?: ICanvasSYS & loader.ASUtil & GameGUI | null; 14 | countDownStarted: boolean; 15 | } 16 | 17 | export default Vue.extend({ 18 | template: ` 19 | 20 | 21 | 22 | {{gameInfo.base.gameName}} 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | `, 35 | props: { 36 | role: { 37 | type: Number, 38 | default: 1 39 | }, 40 | gameInfo: { 41 | type: Object 42 | }, 43 | startTime: { 44 | type: Number, 45 | default: Date.now() 46 | }, 47 | timeout: { 48 | type: Number, 49 | default: 60 50 | }, 51 | playWithAI: { 52 | type: Boolean, 53 | default: false 54 | } 55 | }, 56 | data(): ComponentData { 57 | return { 58 | game: null, 59 | countDownStarted: false 60 | } 61 | }, 62 | created() { 63 | 64 | }, 65 | methods: { 66 | startGame: function () { 67 | let engineBuffer = util.decodeHex(this.gameInfo.engineBytes); 68 | let guiBuffer = util.decodeHex(this.gameInfo.guiBytes); 69 | let self = this; 70 | vm.init(this.role, function (player, fullState, state) { 71 | if (player == self.role) { 72 | // @ts-ignore 73 | self.$refs.countdown.pause(); 74 | } else { 75 | if (self.countDownStarted) { 76 | // @ts-ignore 77 | self.$refs.countdown.continue(); 78 | } else { 79 | self.startCountDown(); 80 | } 81 | } 82 | self.$emit("gameStateUpdate", {player: player, fullState: fullState, state: state}); 83 | }, engineBuffer, guiBuffer, function (player: number) { 84 | // @ts-ignore 85 | self.$refs.countdown.abort(); 86 | self.$emit("gameOver", player); 87 | }, function (error: string) { 88 | self.$emit("error", error); 89 | }, this.playWithAI).then(module => { 90 | this.game = module; 91 | this.game.startGame(); 92 | if (this.role == 1) { 93 | this.startCountDown(); 94 | } 95 | return module; 96 | }); 97 | }, 98 | startCountDown: function () { 99 | // @ts-ignore 100 | this.$refs.countdown.start(); 101 | this.countDownStarted = true; 102 | }, 103 | onCountDownEnd: function () { 104 | this.$emit("gameTimeout"); 105 | }, 106 | rivalStateUpdate: function (state: Int8Array) { 107 | this.game!.rivalUpdate(this.game!.newArray(state)); 108 | }, 109 | getState: function () { 110 | let pointer = this.game!.getState(); 111 | let fullState = this.game!.getArray(Int8Array, pointer); 112 | return fullState; 113 | } 114 | }, 115 | computed: {}, 116 | components: {} 117 | }); 118 | -------------------------------------------------------------------------------- /lightning-proto/src/main/proto/invoices.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | import "google/api/annotations.proto"; 4 | import "lightning.proto"; 5 | 6 | package invoicesrpc; 7 | 8 | option java_package = "org.starcoin.lightning.proto"; 9 | 10 | // Invoices is a service that can be used to create, accept, settle and cancel 11 | // invoices. 12 | service Invoices { 13 | /** 14 | SubscribeSingleInvoice returns a uni-directional stream (server -> client) 15 | to notify the client of state transitions of the specified invoice. 16 | Initially the current invoice state is always sent out. 17 | */ 18 | rpc SubscribeSingleInvoice (lnrpc.PaymentHash) returns (stream lnrpc.Invoice); 19 | 20 | /** 21 | CancelInvoice cancels a currently open invoice. If the invoice is already 22 | canceled, this call will succeed. If the invoice is already settled, it will 23 | fail. 24 | */ 25 | rpc CancelInvoice(CancelInvoiceMsg) returns (CancelInvoiceResp); 26 | 27 | /** 28 | AddHoldInvoice creates a hold invoice. It ties the invoice to the hash 29 | supplied in the request. 30 | */ 31 | rpc AddHoldInvoice(AddHoldInvoiceRequest) returns (AddHoldInvoiceResp); 32 | 33 | /** 34 | SettleInvoice settles an accepted invoice. If the invoice is already 35 | settled, this call will succeed. 36 | */ 37 | rpc SettleInvoice(SettleInvoiceMsg) returns (SettleInvoiceResp); 38 | } 39 | 40 | message CancelInvoiceMsg { 41 | /// Hash corresponding to the (hold) invoice to cancel. 42 | bytes payment_hash = 1; 43 | } 44 | message CancelInvoiceResp {} 45 | 46 | message AddHoldInvoiceRequest { 47 | /** 48 | An optional memo to attach along with the invoice. Used for record keeping 49 | purposes for the invoice's creator, and will also be set in the description 50 | field of the encoded payment request if the description_hash field is not 51 | being used. 52 | */ 53 | string memo = 1 [json_name = "memo"]; 54 | 55 | /// The hash of the preimage 56 | bytes hash = 2 [json_name = "hash"]; 57 | 58 | /// The value of this invoice in satoshis 59 | int64 value = 3 [json_name = "value"]; 60 | 61 | /** 62 | Hash (SHA-256) of a description of the payment. Used if the description of 63 | payment (memo) is too long to naturally fit within the description field 64 | of an encoded payment request. 65 | */ 66 | bytes description_hash = 4 [json_name = "description_hash"]; 67 | 68 | /// Payment request expiry time in seconds. Default is 3600 (1 hour). 69 | int64 expiry = 5 [json_name = "expiry"]; 70 | 71 | /// Fallback on-chain address. 72 | string fallback_addr = 6 [json_name = "fallback_addr"]; 73 | 74 | /// Delta to use for the time-lock of the CLTV extended to the final hop. 75 | uint64 cltv_expiry = 7 [json_name = "cltv_expiry"]; 76 | 77 | /** 78 | Route hints that can each be individually used to assist in reaching the 79 | invoice's destination. 80 | */ 81 | repeated lnrpc.RouteHint route_hints = 8 [json_name = "route_hints"]; 82 | 83 | /// Whether this invoice should include routing hints for private channels. 84 | bool private = 9 [json_name = "private"]; 85 | } 86 | 87 | message AddHoldInvoiceResp { 88 | /** 89 | A bare-bones invoice for a payment within the Lightning Network. With the 90 | details of the invoice, the sender has all the data necessary to send a 91 | payment to the recipient. 92 | */ 93 | string payment_request = 1 [json_name = "payment_request"]; 94 | } 95 | 96 | message SettleInvoiceMsg { 97 | /// Externally discovered pre-image that should be used to settle the hold invoice. 98 | bytes preimage = 1; 99 | } 100 | 101 | message SettleInvoiceResp {} 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Thor - A smart contract and arbitrate oracle service on Lightning Network 2 | 3 | ## About 4 | 5 | Thor is a bitcoin layout 2 implement base on Lightning Network, users can develop 6 | smart contract with WebAssembly on it and we also implement a arbitrate oracle 7 | service to guarantee trustworthy of the applications running on it. Project is 8 | under heavy development, not production ready. 9 | 10 | Inspiration of the name "Thor" comes from the Germanic mythology which is the 11 | god of lightning. 12 | 13 | ## Overview 14 | 15 | Since bitcoin network does not have a mature layout 2 smart contract implementation 16 | currently, Thor is a experimental project for it. The smart contracts on Thor are 17 | compiled to WebAssembly, and the arbitrate oracle service is a hash time locked 18 | contract implementation through Lightning Network. Developers can write the smart 19 | contract by implement the interface of the thor contract abi. 20 | 21 | ## Architecture 22 | ```text 23 | +------------------+ +--------------------+ 24 | | Thor client |----StatesManage---| thor Arbitrater | 25 | +------------------+ +--------------------+ 26 | | |----Arbitrate------| | 27 | | Smart contract | | Smart contract | 28 | | Vm (WebAssembly)| | Vm (WebAssembly) | 29 | +--------+---------+ +---------+----------+ 30 | | | 31 | | | 32 | | HTLC | 33 | +-----------------+---------------------+ 34 | | 35 | Funding 36 | | 37 | | 38 | +------------v--------------+ 39 | | Lightning Network | 40 | +---------------------------+ 41 | 42 | ``` 43 | ## How Thor works 44 | Assume Bob and Alice want to play Gomoku throw Thor, the workflow is below 45 | 46 | 1. Authorization 47 | Both of the game client Alice and Bob request arbitrate service to generate a `nonce with signature`, then sending back that `nonce with their signature` after verifying the nonce with the `public key of arbitrate`, after the arbitrate verified those signatures, the authorization finished. 48 | 49 | 2. Create game room 50 | Alice request arbitrate service to create a game room and join it then waiting for Bob to join by giving the `room id` to him. 51 | 52 | 3. Funding btc for game 53 | 1. Arbitrate service generated a pair of `(r, rhash)` for both Alice and Bob and just giving the `rhash` to them. 54 | 2. Game clients connect to the LN node, exchange the invoice which generated with the rhash by `INVOICE_DATA`, then pay for the invoice. 55 | 3. Both game client confirm the state of their invoice is `ACCEPTED`. 56 | 57 | 4. Play game 58 | 1. Arbitrate service will broadcast a `GAME_BEGIN` message to the room after the `READY` message beening sent from all game clients in the same room. 59 | 2. Game playing will be proceed through runing the `witness data` in vm which generated by both game clients and arbitrate service. 60 | 61 | 5. Finish game and challenging 62 | 1. If someone surrender to another, just finish peacefully. 63 | 2. both clients can request a challenge with the proof of data during a challenge period. 64 | 3. The arbitrate serivice will replay the game by running the proof data in vm after a challenge recived and find the winner. 65 | 4. The winner will get the "secrect" `r` of the loser to unlock the funding btc in lightning network after the broadcast of arbitrate. If tie happen, 66 | the `r` would not be broadcast, and both clients cancel the invoice payed before. After all, game end. 67 | 68 | ![image](https://github.com/starcoinorg/thor/blob/master/docs/thor_workflow.png?raw=true) 69 | -------------------------------------------------------------------------------- /thor-app/src/renderer/sdk/lightning.ts: -------------------------------------------------------------------------------- 1 | // Setup lnd rpc 2 | 3 | import * as https from "https"; 4 | 5 | const fetch = require('node-fetch'); 6 | let config: Config; 7 | 8 | const agent = new https.Agent({ 9 | keepAlive: true, 10 | rejectUnauthorized: false 11 | }); 12 | 13 | class Config { 14 | lndUrl: string; 15 | lndMacaroon: string; 16 | 17 | 18 | constructor(lndUrl: string, lndMacaroon: string) { 19 | this.lndUrl = lndUrl; 20 | this.lndMacaroon = lndMacaroon; 21 | } 22 | } 23 | 24 | export function hasInit(): boolean { 25 | return config != undefined; 26 | } 27 | 28 | export function init(cfg: any) { 29 | if (cfg.lndUrl && cfg.lndMacaroon) { 30 | config = new Config(cfg.lndUrl, cfg.lndMacaroon); 31 | console.log("lnd init", config) 32 | } else { 33 | console.log("can not find lnd config from cfg:", cfg); 34 | } 35 | } 36 | 37 | function get(api: string): Promise { 38 | let url = config.lndUrl + "/v1/" + api; 39 | return fetch(url, { 40 | method: "GET", 41 | mode: "cors", 42 | //credentials: "omit", 43 | headers: { 44 | "Content-Type": "application/json;charset=UTF-8", 45 | "Grpc-Metadata-macaroon": config.lndMacaroon 46 | }, 47 | protocol: "https", 48 | agent: agent 49 | }).then((response: any) => { 50 | return response.json(); 51 | }).then((json: any) => { 52 | console.debug("lighting", "GET", "api:", api, "resp:", JSON.stringify(json)); 53 | if (json.error) { 54 | throw json.error 55 | } 56 | return json; 57 | }) 58 | } 59 | 60 | function post(api: string, body: string): Promise { 61 | let url = config.lndUrl + "/v1/" + api; 62 | return fetch(url, { 63 | method: "POST", 64 | mode: "cors", 65 | credentials: "omit", 66 | headers: { 67 | "Content-Type": "application/json;charset=UTF-8", 68 | "Grpc-Metadata-macaroon": config.lndMacaroon 69 | }, 70 | body: body, 71 | protocol: "https", 72 | agent: agent 73 | }).then((response: any) => { 74 | return response.json() 75 | }).then((json: any) => { 76 | console.debug("lighting", "POST", "api", api, "body:", body, "resp:", JSON.stringify(json)); 77 | if (json.error) { 78 | throw json.error 79 | } 80 | return json; 81 | }) 82 | } 83 | 84 | export function invoice() { 85 | return get("invoices") 86 | } 87 | 88 | export function addInvoice(rHash: Buffer, value: number) { 89 | let requestBody = { 90 | r_hash: rHash.toString('base64'), 91 | value:value, 92 | }; 93 | return post("invoices",JSON.stringify(requestBody)) 94 | } 95 | 96 | export function sendPayment(requestString:string){ 97 | let requestBody = { 98 | payment_request: requestString, 99 | }; 100 | return post("channels/transactions", JSON.stringify(requestBody)).then(json => { 101 | if (json.payment_error) { 102 | throw json.payment_error; 103 | } 104 | return json; 105 | }) 106 | } 107 | 108 | export function decodePayReq(requestString:string){ 109 | return get("payreq/"+requestString) 110 | } 111 | 112 | export function settleInvoice(preimage: Buffer) { 113 | let requestBody = { 114 | preimage: preimage.toString('base64'), 115 | }; 116 | return post("invoice/settle", JSON.stringify(requestBody)) 117 | } 118 | 119 | export function cancelInvoice(rHash: Buffer) { 120 | let requestBody = { 121 | payment_hash: rHash.toString("base64") 122 | }; 123 | return post("invoice/cancel", JSON.stringify(requestBody)) 124 | } 125 | 126 | export function chainBalance() { 127 | return get("balance/blockchain") 128 | } 129 | 130 | export function channelBalance() { 131 | return get("balance/channels") 132 | } 133 | 134 | export function lookupInvoice(rHash: Buffer) { 135 | return get("invoice/" + rHash.toString('hex')) 136 | } 137 | 138 | export function getinfo() { 139 | return get("getinfo") 140 | } 141 | 142 | export enum InvoiceState { 143 | OPEN = 0, 144 | SETTLED = 1, 145 | CANCELED = 2, 146 | ACCEPTED = 3, 147 | } 148 | -------------------------------------------------------------------------------- /lightning/src/main/java/org/starcoin/lightning/client/core/ChannelResponse.java: -------------------------------------------------------------------------------- 1 | package org.starcoin.lightning.client.core; 2 | 3 | public class ChannelResponse { 4 | 5 | public ChannelResponse(org.starcoin.lightning.proto.LightningOuterClass.Channel channel) { 6 | active = channel.getActive(); 7 | remotePubkey = channel.getRemotePubkey(); 8 | channelPoint = channel.getChannelPoint(); 9 | chanId = channel.getChanId(); 10 | capacity = channel.getCapacity(); 11 | localBanance = channel.getLocalBalance(); 12 | remoteBalance = channel.getRemoteBalance(); 13 | commitFee = channel.getCommitFee(); 14 | commitWeight = channel.getCommitWeight(); 15 | feePerKw = channel.getFeePerKw(); 16 | unsettledBalance = channel.getUnsettledBalance(); 17 | totalSatoshisSent = channel.getTotalSatoshisSent(); 18 | totalSatoshisReceived = channel.getTotalSatoshisReceived(); 19 | numUpdates = channel.getNumUpdates(); 20 | csvDelay = channel.getCsvDelay(); 21 | initiator = channel.getInitiator(); 22 | pendingHtlcs = channel.getPendingHtlcsOrBuilderList().toString(); 23 | } 24 | 25 | public boolean isActive() { 26 | return active; 27 | } 28 | 29 | public String getRemotePubkey() { 30 | return remotePubkey; 31 | } 32 | 33 | public String getChannelPoint() { 34 | return channelPoint; 35 | } 36 | 37 | public long getChanId() { 38 | return chanId; 39 | } 40 | 41 | public long getCapacity() { 42 | return capacity; 43 | } 44 | 45 | public long getLocalBanance() { 46 | return localBanance; 47 | } 48 | 49 | public long getRemoteBalance() { 50 | return remoteBalance; 51 | } 52 | 53 | public long getCommitFee() { 54 | return commitFee; 55 | } 56 | 57 | public long getCommitWeight() { 58 | return commitWeight; 59 | } 60 | 61 | public long getFeePerKw() { 62 | return feePerKw; 63 | } 64 | 65 | public long getUnsettledBalance() { 66 | return unsettledBalance; 67 | } 68 | 69 | public long getTotalSatoshisSent() { 70 | return totalSatoshisSent; 71 | } 72 | 73 | public long getTotalSatoshisReceived() { 74 | return totalSatoshisReceived; 75 | } 76 | 77 | public long getNumUpdates() { 78 | return numUpdates; 79 | } 80 | 81 | public long getCsvDelay() { 82 | return csvDelay; 83 | } 84 | 85 | public boolean isInitiator() { 86 | return initiator; 87 | } 88 | 89 | public String getPendingHtlcs() { 90 | return pendingHtlcs; 91 | } 92 | 93 | private boolean active; 94 | private String remotePubkey; 95 | private String channelPoint; 96 | private long chanId; 97 | private long capacity; 98 | private long localBanance; 99 | private long remoteBalance; 100 | private long commitFee; 101 | private long commitWeight; 102 | private long feePerKw; 103 | private long unsettledBalance; 104 | private long totalSatoshisSent; 105 | private long totalSatoshisReceived; 106 | private long numUpdates; 107 | private long csvDelay; 108 | private boolean initiator; 109 | private String pendingHtlcs; 110 | 111 | @Override 112 | public String toString() { 113 | return "ChannelResponse{" + 114 | "active=" + active + 115 | ", remotePubkey='" + remotePubkey + '\'' + 116 | ", channelPoint='" + channelPoint + '\'' + 117 | ", chanId=" + chanId + 118 | ", capacity=" + capacity + 119 | ", localBanance=" + localBanance + 120 | ", remoteBalance=" + remoteBalance + 121 | ", commitFee=" + commitFee + 122 | ", commitWeight=" + commitWeight + 123 | ", feePerKw=" + feePerKw + 124 | ", unsettledBalance=" + unsettledBalance + 125 | ", totalSatoshisSent=" + totalSatoshisSent + 126 | ", totalSatoshisReceived=" + totalSatoshisReceived + 127 | ", numUpdates=" + numUpdates + 128 | ", csvDelay=" + csvDelay + 129 | ", initiator=" + initiator + 130 | ", pendingHtlcs='" + pendingHtlcs + '\'' + 131 | '}'; 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /thor-arbitrate-service/src/main/kotlin/org/starcoin/thor/manager/RoomManager.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.manager 2 | 3 | import io.ktor.features.NotFoundException 4 | import io.ktor.util.KtorExperimentalAPI 5 | import org.starcoin.sirius.serialization.ByteArrayWrapper 6 | import org.starcoin.thor.core.GameBaseInfo 7 | import org.starcoin.thor.core.PlayerInfo 8 | import org.starcoin.thor.core.Room 9 | import org.starcoin.thor.core.UserInfo 10 | import org.starcoin.thor.sign.toByteArray 11 | 12 | //all userId 13 | @UseExperimental(KtorExperimentalAPI::class) 14 | class RoomManager { 15 | private val rooms = mutableMapOf() 16 | private val roomLock = Object() 17 | 18 | private val roomSet = mutableSetOf() 19 | private val game2Room = mutableMapOf>() 20 | private val joinLock = Object() 21 | 22 | fun createRoom(game: GameBaseInfo, name: String, cost: Long, time: Long, userInfo: UserInfo? = null): Room { 23 | val room = Room(game.hash, name, cost, time) 24 | userInfo?.let { room.addPlayer(userInfo) } 25 | synchronized(roomLock) { 26 | roomSet.add(room.roomId) 27 | when (game2Room[game.hash]) { 28 | null -> { 29 | val list = ArrayList() 30 | list.add(room.roomId) 31 | game2Room[game.hash] = list 32 | } 33 | else -> { 34 | game2Room[game.hash]!!.add(room.roomId) 35 | } 36 | } 37 | rooms[room.roomId] = room 38 | } 39 | return room 40 | } 41 | 42 | fun queryRoomListByGame(gameId: String): List? { 43 | val roomIds = game2Room[gameId] 44 | roomIds?.let { return rooms.filterKeys { roomIds.contains(it) }.values.toList() } 45 | return null 46 | } 47 | 48 | fun queryUserIndex(roomId: String, userId: String): Int { 49 | return rooms[roomId]!!.players.map { playerInfo -> playerInfo.playerUserId }.indexOf(userId) + 1 50 | } 51 | 52 | fun queryUserList(roomId: String): List = rooms[roomId]!!.players.map { playerInfo -> playerInfo.playerUserId } 53 | 54 | fun queryUserIdByIndex(roomId: String, userIndex: Int): String { 55 | return rooms[roomId]!!.players.map { playerInfo -> playerInfo.playerUserId }[userIndex - 1] 56 | } 57 | 58 | fun queryRoomList(begin: Int, end: Int): List { 59 | val keys = roomSet.toList().subList(begin, end).toSet() 60 | return rooms.filterKeys { keys.contains(it) }.values.toList() 61 | } 62 | 63 | fun count(): Int { 64 | return this.rooms.size 65 | } 66 | 67 | private fun queryRoomOrNull(roomId: String): Room? { 68 | return rooms[roomId] 69 | } 70 | 71 | fun queryRoomNotNull(roomId: String): Room { 72 | return this.queryRoomOrNull(roomId) 73 | ?: throw NotFoundException("Can not find room by id $roomId") 74 | } 75 | 76 | fun roomBegin(roomId: String, time: Long) { 77 | rooms[roomId]!!.begin = time 78 | } 79 | 80 | fun clearRoom(roomId: String) { 81 | synchronized(roomLock) { 82 | roomSet.remove(roomId) 83 | val r = rooms[roomId] 84 | r?.let { 85 | game2Room[r.gameId]!!.remove(roomId) 86 | rooms.remove(roomId) 87 | } 88 | } 89 | } 90 | 91 | fun rHash(roomId: String, userId: String, rHash: ByteArrayWrapper) { 92 | queryRoomNotNull(roomId).rHash(userId, rHash) 93 | } 94 | 95 | fun joinRoom(userInfo: UserInfo, roomId: String): Room { 96 | return queryRoomNotNull(roomId).let { 97 | synchronized(joinLock) { 98 | if (it.isFull) { 99 | throw RuntimeException("room $roomId is full.") 100 | } 101 | if (!it.isInRoom(userInfo.id)) { 102 | it.players.add(PlayerInfo(userInfo.id, userInfo.name, ByteArrayWrapper(userInfo.publicKey.toByteArray()))) 103 | } 104 | it 105 | } 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /thor-app/src/renderer/components/Wallet.ts: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import * as lightning from "../sdk/lightning"; 3 | import Msgbus, {errorHandler} from "./Msgbus"; 4 | import JsonObjViewComponent from "./JsonObjView"; 5 | 6 | export default Vue.extend({ 7 | template: ` 8 | 9 | 10 | 11 | Wallet 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Invoices 21 | 22 | 30 | 42 | 53 | 58 | 59 | 60 | 61 | `, 62 | data() { 63 | return { 64 | expand: false, 65 | headers: [ 66 | {"text": "Add Index", "value": "add_index"}, 67 | {"text": "r Preimage", "value": "r_preimage"}, 68 | {"text": "r Hash", "value": "r_hash"}, 69 | {"text": "Value", "value": "value"}, 70 | {"text": "Settled", "value": "settled"}, 71 | {"text": "Expiry", "value": "expiry"}, 72 | {"text": "State", "value": "state"} 73 | ], 74 | pagination: { 75 | descending: true, 76 | rowsPerPage: 20, 77 | sortBy: "add_index", 78 | }, 79 | invoices: [], 80 | chainBalance: null, 81 | channelBalance: null 82 | } 83 | }, 84 | created() { 85 | this.init(); 86 | }, 87 | methods: { 88 | init: function () { 89 | if (!lightning.hasInit()) { 90 | Msgbus.$emit("error", "Please config lightning network first."); 91 | this.$router.push({name: "config"}); 92 | return 93 | } 94 | Msgbus.$on("refresh", () => { 95 | this.refresh(); 96 | }); 97 | this.refresh(); 98 | }, 99 | fetchInvoices: function () { 100 | Msgbus.$emit("loading", true); 101 | lightning.invoice().then(json => { 102 | this.invoices = json.invoices; 103 | Msgbus.$emit("loading", false); 104 | }).catch(errorHandler) 105 | }, 106 | fetchChainBalance: function () { 107 | lightning.chainBalance().then(json => this.chainBalance = json 108 | ).catch(errorHandler) 109 | }, 110 | fetchChannelBalance: function () { 111 | lightning.channelBalance().then(json => this.channelBalance = json 112 | ).catch(errorHandler) 113 | }, 114 | getInfo: function () { 115 | lightning.getinfo().catch(errorHandler); 116 | }, 117 | refresh: function () { 118 | this.fetchChainBalance(); 119 | this.fetchChannelBalance(); 120 | this.fetchInvoices(); 121 | } 122 | }, 123 | computed: {}, 124 | components: { 125 | "json-object-view": JsonObjViewComponent, 126 | } 127 | }); 128 | -------------------------------------------------------------------------------- /thor-core/src/main/kotlin/org/starcoin/thor/utils/ThorUtil.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.utils 2 | 3 | import java.util.* 4 | 5 | private const val ENCODED_ZERO = '1' 6 | 7 | private const val alphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" 8 | private val alphabetIndices by lazy { 9 | IntArray(128) { alphabet.indexOf(it.toChar()) } 10 | } 11 | 12 | /** 13 | * Encodes the bytes as a base58 string (no checksum is appended). 14 | * 15 | * @return the base58-encoded string 16 | */ 17 | fun ByteArray.encodeToBase58String(): String { 18 | val input = copyOf(size) // since we modify it in-place 19 | if (input.isEmpty()) { 20 | return "" 21 | } 22 | // Count leading zeros. 23 | var zeros = 0 24 | while (zeros < input.size && input[zeros].toInt() == 0) { 25 | ++zeros 26 | } 27 | // Convert base-256 digits to base-58 digits (plus conversion to ASCII characters) 28 | val encoded = CharArray(input.size * 2) // upper bound 29 | var outputStart = encoded.size 30 | var inputStart = zeros 31 | while (inputStart < input.size) { 32 | encoded[--outputStart] = alphabet[divmod(input, inputStart.toUInt(), 256.toUInt(), 58.toUInt()).toInt()] 33 | if (input[inputStart].toInt() == 0) { 34 | ++inputStart // optimization - skip leading zeros 35 | } 36 | } 37 | // Preserve exactly as many leading encoded zeros in output as there were leading zeros in data. 38 | while (outputStart < encoded.size && encoded[outputStart] == ENCODED_ZERO) { 39 | ++outputStart 40 | } 41 | while (--zeros >= 0) { 42 | encoded[--outputStart] = ENCODED_ZERO 43 | } 44 | // Return encoded string (including encoded leading zeros). 45 | return String(encoded, outputStart, encoded.size - outputStart) 46 | } 47 | 48 | /** 49 | * Decodes the base58 string into a [ByteArray] 50 | * 51 | * @return the decoded data bytes 52 | * @throws NumberFormatException if the string is not a valid base58 string 53 | */ 54 | @Throws(NumberFormatException::class) 55 | fun String.decodeBase58(): ByteArray { 56 | if (isEmpty()) { 57 | return ByteArray(0) 58 | } 59 | // Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits). 60 | val input58 = ByteArray(length) 61 | for (i in 0 until length) { 62 | val c = this[i] 63 | val digit = if (c.toInt() < 128) alphabetIndices[c.toInt()] else -1 64 | if (digit < 0) { 65 | throw NumberFormatException("Illegal character $c at position $i") 66 | } 67 | input58[i] = digit.toByte() 68 | } 69 | // Count leading zeros. 70 | var zeros = 0 71 | while (zeros < input58.size && input58[zeros].toInt() == 0) { 72 | ++zeros 73 | } 74 | // Convert base-58 digits to base-256 digits. 75 | val decoded = ByteArray(length) 76 | var outputStart = decoded.size 77 | var inputStart = zeros 78 | while (inputStart < input58.size) { 79 | decoded[--outputStart] = divmod(input58, inputStart.toUInt(), 58.toUInt(), 256.toUInt()).toByte() 80 | if (input58[inputStart].toInt() == 0) { 81 | ++inputStart // optimization - skip leading zeros 82 | } 83 | } 84 | // Ignore extra leading zeroes that were added during the calculation. 85 | while (outputStart < decoded.size && decoded[outputStart].toInt() == 0) { 86 | ++outputStart 87 | } 88 | // Return decoded data (including original number of leading zeros). 89 | return Arrays.copyOfRange(decoded, outputStart - zeros, decoded.size) 90 | } 91 | 92 | /** 93 | * Divides a number, represented as an array of bytes each containing a single digit 94 | * in the specified base, by the given divisor. The given number is modified in-place 95 | * to contain the quotient, and the return value is the remainder. 96 | * 97 | * @param number the number to divide 98 | * @param firstDigit the index within the array of the first non-zero digit 99 | * (this is used for optimization by skipping the leading zeros) 100 | * @param base the base in which the number's digits are represented (up to 256) 101 | * @param divisor the number to divide by (up to 256) 102 | * @return the remainder of the division operation 103 | */ 104 | private fun divmod(number: ByteArray, firstDigit: UInt, base: UInt, divisor: UInt): UInt { 105 | // this is just long division which accounts for the base of the input digits 106 | var remainder = 0.toUInt() 107 | for (i in firstDigit until number.size.toUInt()) { 108 | val digit = number[i.toInt()].toUByte() 109 | val temp = remainder * base + digit 110 | number[i.toInt()] = (temp / divisor).toByte() 111 | remainder = temp % divisor 112 | } 113 | return remainder 114 | } 115 | 116 | fun randomString(): String { 117 | return UUID.randomUUID().toString().replace("-", "") 118 | } -------------------------------------------------------------------------------- /vm-service/vm.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | // import "@types/webassembly-js-api"; 3 | var __extends = (this && this.__extends) || (function () { 4 | var extendStatics = function (d, b) { 5 | extendStatics = Object.setPrototypeOf || 6 | ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || 7 | function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; 8 | return extendStatics(d, b); 9 | }; 10 | return function (d, b) { 11 | extendStatics(d, b); 12 | function __() { this.constructor = d; } 13 | d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); 14 | }; 15 | })(); 16 | exports.__esModule = true; 17 | var ASModuleWrapper = /** @class */ (function () { 18 | function ASModuleWrapper() { 19 | var _this = this; 20 | this.module = null; 21 | this.getString = function (value) { 22 | if (_this.module == null) { 23 | return value; 24 | } 25 | else { 26 | return _this.module.getString(value); 27 | } 28 | }; 29 | this.getArray = function (type, value) { 30 | if (_this.module == null) { 31 | return value; 32 | } 33 | else { 34 | return _this.module.getArray(type, value); 35 | } 36 | }; 37 | } 38 | ASModuleWrapper.prototype.init = function (module) { 39 | this.module = module; 40 | }; 41 | return ASModuleWrapper; 42 | }()); 43 | //function use array style for keep this ref. 44 | //see https://github.com/Microsoft/TypeScript/wiki/'this'-in-TypeScript 45 | var Console = /** @class */ (function (_super) { 46 | __extends(Console, _super); 47 | function Console() { 48 | var _this = _super !== null && _super.apply(this, arguments) || this; 49 | _this.log = function (value) { 50 | console.log(_this.getString(value)); 51 | }; 52 | _this.logf = function (msg, value) { 53 | console.log(_this.getString(msg), value); 54 | }; 55 | _this.logi = function (msg, value) { 56 | console.log(_this.getString(msg), value); 57 | }; 58 | _this.logAction = function (msg, player, state) { 59 | console.log(_this.getString(msg) + " player:", player, _this.getArray(Int8Array, state)); 60 | }; 61 | _this.error = function (value) { 62 | alert(_this.getString(value)); 63 | }; 64 | return _this; 65 | } 66 | return Console; 67 | }(ASModuleWrapper)); 68 | var Listener = /** @class */ (function (_super) { 69 | __extends(Listener, _super); 70 | function Listener() { 71 | var _this = _super !== null && _super.apply(this, arguments) || this; 72 | _this.onUpdate = function (player, state) { 73 | console.log("listener onUpdate", player, _this.getArray(Int8Array, state)); 74 | }; 75 | _this.onGameOver = function (player) { 76 | console.log("listener onGameOver", player); 77 | alert("Game Over Winner is:" + player); 78 | }; 79 | return _this; 80 | } 81 | return Listener; 82 | }(ASModuleWrapper)); 83 | function createVm(bufSource) { 84 | var mod = new WebAssembly.Module(bufSource); 85 | var engineConsole = new Console(); 86 | var listener = new Listener(); 87 | var env = { 88 | memoryBase: 0, 89 | tableBase: 0, 90 | memory: new WebAssembly.Memory({ 91 | initial: 0 92 | }), 93 | abort: function (msg, file, line, column) { 94 | console.error("Abort called at " + file + ":" + line + ":" + column + ", msg:" + msg); 95 | } 96 | }; 97 | var instance = new WebAssembly.Instance(mod, { 98 | env: env, 99 | console: engineConsole, 100 | listener: listener 101 | }); 102 | return new Vm(instance); 103 | } 104 | exports.createVm = createVm; 105 | var Vm = /** @class */ (function () { 106 | function Vm(wasm) { 107 | this.opcodes = new Map(); 108 | this.wasm = wasm; 109 | this.opcodes.set(0, this.wasm.exports.init); 110 | this.opcodes.set(1, this.wasm.exports.update); 111 | this.opcodes.set(2, this.wasm.exports.getWinner); 112 | this.opcodes.set(3, this.wasm.exports.isGameOver); 113 | } 114 | Vm.prototype.execute = function (opcode) { 115 | var argument = []; 116 | for (var _i = 1; _i < arguments.length; _i++) { 117 | argument[_i - 1] = arguments[_i]; 118 | } 119 | var fn = this.opcodes.get(opcode); 120 | if (fn == null) { 121 | // Todo: return error 122 | // callback("Invalid opcode") 123 | return null; 124 | } 125 | return fn.apply(void 0, argument); 126 | }; 127 | return Vm; 128 | }()); 129 | -------------------------------------------------------------------------------- /thor-arbitrate-service/src/main/kotlin/org/starcoin/thor/manager/CommonUserManager.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.manager 2 | 3 | import io.ktor.http.cio.websocket.DefaultWebSocketSession 4 | import org.starcoin.sirius.util.WithLogging 5 | import org.starcoin.thor.core.UserInfo 6 | import org.starcoin.thor.core.UserStatus 7 | import org.starcoin.thor.utils.randomString 8 | 9 | data class CommonUser(val userInfo: UserInfo, var stat: UserStatus = UserStatus.NORMAL, var currentRoomId: String? = null) 10 | 11 | //all sessionId 12 | class SessionManager { 13 | 14 | companion object : WithLogging() 15 | 16 | private val sessions = mutableMapOf()//SessionId -> Socket 17 | private val sessionLock = Object() 18 | 19 | private val nonces = mutableMapOf()//SessionId -> nonce 20 | private val nonceLock = Object() 21 | 22 | private val s2u = mutableMapOf()//SessionId -> UserId 23 | private val u2s = mutableMapOf()//UserId -> SessionId 24 | 25 | fun storeSocket(sessionId: String, session: DefaultWebSocketSession) { 26 | synchronized(sessionLock) { 27 | sessions[sessionId] = session 28 | } 29 | } 30 | 31 | fun querySocketBySessionId(sessionId: String): DefaultWebSocketSession? { 32 | return sessions[sessionId] 33 | } 34 | 35 | fun querySocketByUserId(userId: String): DefaultWebSocketSession? { 36 | val sessionId = querySessionIdByUserId(userId) 37 | return sessionId?.let { sessions[sessionId] } 38 | } 39 | 40 | fun createNonce(sessionId: String): String { 41 | val nonce = randomString() 42 | synchronized(nonceLock) { 43 | nonces[sessionId] = nonce 44 | } 45 | return nonce 46 | } 47 | 48 | fun queryNonce(sessionId: String): String? { 49 | return nonces[sessionId] 50 | } 51 | 52 | fun storeUserId(sessionId: String, userId: String) { 53 | synchronized(this) { 54 | nonces.remove(sessionId) 55 | s2u[sessionId] = userId 56 | u2s[userId] = sessionId 57 | } 58 | } 59 | 60 | fun queryUserIdBySessionId(sessionId: String): String? { 61 | return s2u[sessionId] 62 | } 63 | 64 | private fun querySessionIdByUserId(userId: String): String? { 65 | return u2s[userId] 66 | } 67 | 68 | fun clearSession(sessionId: String) { 69 | synchronized(this) { 70 | sessions.remove(sessionId) 71 | nonces.remove(sessionId) 72 | val userId = u2s[sessionId] 73 | userId?.let { s2u.remove(userId) } 74 | u2s.remove(sessionId) 75 | } 76 | } 77 | } 78 | 79 | //all userId 80 | class CommonUserManager { 81 | private val users = mutableMapOf()//userId -> CommonUser 82 | private val userLock = Object() 83 | 84 | companion object : WithLogging() 85 | 86 | fun storeUser(userInfo: UserInfo) { 87 | synchronized(userLock) { 88 | if (!users.containsKey(userInfo.id)) { 89 | users[userInfo.id] = CommonUser(userInfo) 90 | } else { 91 | LOG.warning("${userInfo.id} is not exist") 92 | } 93 | } 94 | } 95 | 96 | fun queryUser(userId: String): UserInfo? { 97 | return users[userId]?.let { users[userId]!!.userInfo } 98 | } 99 | 100 | fun queryDetailUser(userId: String): CommonUser? { 101 | return users[userId] 102 | } 103 | 104 | fun queryCurrentRoom(userId: String): String? { 105 | return users[userId]?.let { users[userId]!!.currentRoomId } 106 | } 107 | 108 | fun currentRoom(userId: String, roomId: String) { 109 | synchronized(userLock) { 110 | users[userId]?.let { 111 | users[userId]!!.currentRoomId = roomId 112 | users[userId]!!.stat = UserStatus.ROOM 113 | } 114 | } 115 | } 116 | 117 | fun gameBegin(addrs: Pair) { 118 | synchronized(userLock) { 119 | if (roomUserStatus(addrs.first) && roomUserStatus(addrs.second)) { 120 | users[addrs.first]!!.stat = UserStatus.PLAYING 121 | users[addrs.second]!!.stat = UserStatus.PLAYING 122 | } else { 123 | LOG.warning("user status is err") 124 | } 125 | } 126 | } 127 | 128 | fun normalUserStatus(userId: String): Boolean { 129 | return checkUserStatus(userId, UserStatus.NORMAL) 130 | } 131 | 132 | private fun roomUserStatus(userId: String): Boolean { 133 | return checkUserStatus(userId, UserStatus.ROOM) 134 | } 135 | 136 | private fun checkUserStatus(userId: String, stat: UserStatus): Boolean { 137 | return (users[userId] != null && users[userId]!!.stat == stat) 138 | } 139 | 140 | fun clearRoom(userId: String) { 141 | synchronized(userLock) { 142 | users[userId]?.let { 143 | users[userId]!!.currentRoomId = null 144 | users[userId]!!.stat = UserStatus.NORMAL 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /lightning/src/main/java/org/starcoin/lightning/client/core/Invoice.java: -------------------------------------------------------------------------------- 1 | package org.starcoin.lightning.client.core; 2 | 3 | import com.google.protobuf.ByteString; 4 | import java.util.Arrays; 5 | import org.starcoin.lightning.client.HashUtils; 6 | import org.starcoin.lightning.proto.LightningOuterClass; 7 | 8 | public class Invoice { 9 | 10 | public enum InvoiceState { 11 | OPEN, 12 | SETTLED, 13 | CANCELED, 14 | ACCEPTED 15 | } 16 | private byte[] publicKey ; 17 | private String memo; 18 | private byte[] rPreimage; 19 | private byte[] rHash; 20 | private long value; 21 | private String paymentRequest; 22 | private byte[] descriptionHash; 23 | private long expiry; 24 | private String fallbackAddr; 25 | private InvoiceState state; 26 | private long timeStamp; 27 | 28 | public Invoice(){} 29 | 30 | public Invoice(byte[] rHash, long value) { 31 | this.rHash = rHash; 32 | this.value = value; 33 | } 34 | 35 | private Invoice(String memo, byte[] rPreimage, byte[] rHash, long value, 36 | String paymentRequest, byte[] descriptionHash, long expiry, String fallbackAddr, 37 | InvoiceState state) { 38 | this.memo = memo; 39 | this.rPreimage = rPreimage; 40 | this.rHash = rHash; 41 | this.value = value; 42 | this.paymentRequest = paymentRequest; 43 | this.descriptionHash = descriptionHash; 44 | this.expiry = expiry; 45 | this.fallbackAddr = fallbackAddr; 46 | this.state = state; 47 | } 48 | 49 | public String getMemo() { 50 | return memo; 51 | } 52 | 53 | public byte[] getrPreimage() { 54 | return rPreimage; 55 | } 56 | 57 | public byte[] getrHash() { 58 | return rHash; 59 | } 60 | 61 | public long getValue() { 62 | return value; 63 | } 64 | 65 | public String getPaymentRequest() { 66 | return paymentRequest; 67 | } 68 | 69 | public byte[] getDescriptionHash() { 70 | return descriptionHash; 71 | } 72 | 73 | public long getExpiry() { 74 | return expiry; 75 | } 76 | 77 | public String getFallbackAddr() { 78 | return fallbackAddr; 79 | } 80 | 81 | public InvoiceState getState() { 82 | return state; 83 | } 84 | 85 | public boolean invoiceDone() { 86 | return Invoice.InvoiceState.SETTLED == this.state || Invoice.InvoiceState.CANCELED == this.state; 87 | } 88 | 89 | public void setMemo(String memo) { 90 | this.memo = memo; 91 | } 92 | 93 | public void setrPreimage(byte[] rPreimage) { 94 | this.rPreimage = rPreimage; 95 | } 96 | 97 | public void setrHash(byte[] rHash) { 98 | this.rHash = rHash; 99 | } 100 | 101 | public void setValue(long value) { 102 | this.value = value; 103 | } 104 | 105 | public void setPaymentRequest(String paymentRequest) { 106 | this.paymentRequest = paymentRequest; 107 | } 108 | 109 | public void setDescriptionHash(byte[] descriptionHash) { 110 | this.descriptionHash = descriptionHash; 111 | } 112 | 113 | public void setExpiry(long expiry) { 114 | this.expiry = expiry; 115 | } 116 | 117 | public void setFallbackAddr(String fallbackAddr) { 118 | this.fallbackAddr = fallbackAddr; 119 | } 120 | 121 | public void setState(InvoiceState state) { 122 | this.state = state; 123 | } 124 | 125 | public byte[] getPublicKey() { 126 | return publicKey; 127 | } 128 | 129 | public void setPublicKey(byte[] publicKey) { 130 | this.publicKey = publicKey; 131 | } 132 | 133 | public long getTimeStamp() { 134 | return timeStamp; 135 | } 136 | 137 | public void setTimeStamp(long timeStamp) { 138 | this.timeStamp = timeStamp; 139 | } 140 | 141 | public LightningOuterClass.Invoice toProto(){ 142 | LightningOuterClass.Invoice.Builder invoiceBuilder = LightningOuterClass.Invoice.newBuilder(); 143 | invoiceBuilder.setRHash(ByteString.copyFrom(this.getrHash())); 144 | invoiceBuilder.setValue(this.getValue()); 145 | return invoiceBuilder.build(); 146 | } 147 | 148 | public static Invoice copyFrom(LightningOuterClass.Invoice invoice){ 149 | InvoiceState state=InvoiceState.OPEN; 150 | switch (invoice.getStateValue()){ 151 | case LightningOuterClass.Invoice.InvoiceState.OPEN_VALUE: 152 | state=InvoiceState.OPEN; 153 | break; 154 | case LightningOuterClass.Invoice.InvoiceState.SETTLED_VALUE: 155 | state=InvoiceState.SETTLED; 156 | break; 157 | case LightningOuterClass.Invoice.InvoiceState.CANCELED_VALUE: 158 | state=InvoiceState.CANCELED; 159 | break; 160 | case LightningOuterClass.Invoice.InvoiceState.ACCEPTED_VALUE: 161 | state=InvoiceState.ACCEPTED; 162 | break; 163 | } 164 | return new Invoice(invoice.getMemo(),invoice.getRPreimage().toByteArray(),invoice.getRHash().toByteArray(),invoice.getValue(), 165 | invoice.getPaymentRequest(),invoice.getDescriptionHash().toByteArray(),invoice.getExpiry(),invoice.getFallbackAddr(),state); 166 | } 167 | 168 | @Override 169 | public String toString() { 170 | return "Invoice{" + 171 | "memo='" + memo + '\'' + 172 | // ", rPreimage=" + HashUtils.bytesToHex(rPreimage) + 173 | ", rHash=" + HashUtils.bytesToHex(rHash) + 174 | ", value=" + value + 175 | ", paymentRequest='" + paymentRequest + '\'' + 176 | ", descriptionHash=" + Arrays.toString(descriptionHash) + 177 | ", expiry=" + expiry + 178 | ", fallback_addr='" + fallbackAddr + '\'' + 179 | ", state=" + state + 180 | '}'; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /thor-app/tsconfig.json: -------------------------------------------------------------------------------- 1 | //{ 2 | // "compilerOptions": { 3 | // /* Basic Options */ 4 | // "target": "es5", 5 | // /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 6 | // "module": "commonjs", 7 | // /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 8 | // "lib": [ 9 | // "dom", 10 | // "esnext" 11 | // ], 12 | // /* Specify library files to be included in the compilation. */ 13 | // // "allowJs": true, /* Allow javascript files to be compiled. */ 14 | // // "checkJs": true, /* Report errors in .js files. */ 15 | // // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 16 | // // "declaration": true, /* Generates corresponding '.d.ts' file. */ 17 | // // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 18 | // // "sourceMap": true, /* Generates corresponding '.map' file. */ 19 | // // "outFile": "./", /* Concatenate and emit output to single file. */ 20 | // // "outDir": "./", /* Redirect output structure to the directory. */ 21 | // // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 22 | // // "composite": true, /* Enable project compilation */ 23 | // // "removeComments": true, /* Do not emit comments to output. */ 24 | // // "noEmit": true, /* Do not emit outputs. */ 25 | // // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 26 | // // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 27 | // // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 28 | // 29 | // /* Strict Type-Checking Options */ 30 | // "strict": true, 31 | // /* Enable all strict type-checking options. */ 32 | // "noImplicitAny": true, 33 | // /* Raise error on expressions and declarations with an implied 'any' type. */ 34 | // // "strictNullChecks": true, /* Enable strict null checks. */ 35 | // // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 36 | // // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 37 | // // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 38 | // // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 39 | // "alwaysStrict": true, 40 | // /* Parse in strict mode and emit "use strict" for each source file. */ 41 | // /* Additional Checks */ 42 | // // "noUnusedLocals": true, /* Report errors on unused locals. */ 43 | // // "noUnusedParameters": true, /* Report errors on unused parameters. */ 44 | // // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 45 | // // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 46 | // 47 | // /* Module Resolution Options */ 48 | // // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 49 | // // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 50 | // // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 51 | // // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 52 | // // "typeRoots": [], /* List of folders to include type definitions from. */ 53 | // // "types": [], /* Type declaration files to be included in compilation. */ 54 | // // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 55 | // "esModuleInterop": true 56 | // /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 57 | // // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 58 | // 59 | // /* Source Map Options */ 60 | // // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 61 | // // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 62 | // // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 63 | // // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 64 | // 65 | // /* Experimental Options */ 66 | // // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 67 | // // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 68 | // } 69 | //} 70 | 71 | { 72 | "strictNullChecks": false, 73 | "extends": "./node_modules/electron-webpack/tsconfig-base.json" 74 | } 75 | -------------------------------------------------------------------------------- /thor-app/src/renderer/components/Solo.ts: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import Msgbus, {errorHandler} from "./Msgbus"; 3 | import GamebordComponent from "./Gameboard"; 4 | import * as client from "../sdk/client"; 5 | import util from "../sdk/util"; 6 | 7 | export default Vue.extend({ 8 | template: ` 9 | 10 | 14 | 15 | 21 | 22 | 23 | 24 | 27 | Restart Game 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Start {{gameInfo.base.gameName}} game with AI: 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 59 | Start Game 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | You Win!!!
70 | You Lost!!
71 | You time out.
72 |
73 | 74 | 75 | 76 | 79 | Restart Game 80 | 81 | 82 | 85 | Quit to home 86 | 87 | 88 | 89 |
90 | 91 |
92 |
93 |
94 | `, 95 | props: ['gameId'], 96 | data() { 97 | return { 98 | prepare: true, 99 | timeout: 60, 100 | gameInfo: null, 101 | myRole: 1, 102 | gameOver: false, 103 | gameTimeout: false, 104 | winner: 0, 105 | } 106 | }, 107 | created() { 108 | this.init(); 109 | }, 110 | methods: { 111 | init: function () { 112 | Msgbus.$emit("loading", true); 113 | client.gameInfo(this.gameId).then(gameInfo => { 114 | this.gameInfo = gameInfo; 115 | Msgbus.$emit("loading", false); 116 | console.debug("gameInfo", gameInfo); 117 | let engineBuffer = util.decodeHex(gameInfo.engineBytes); 118 | let guiBuffer = util.decodeHex(gameInfo.guiBytes); 119 | console.debug("engineBuffer length", engineBuffer.length); 120 | console.debug("guiBuffer length", guiBuffer.length); 121 | }).catch(errorHandler) 122 | }, 123 | startGame: function () { 124 | // @ts-ignore 125 | this.$refs.gameboard.startGame(); 126 | this.prepare = false; 127 | }, 128 | restart: function () { 129 | console.debug("restart"); 130 | this.$router.go(0); 131 | }, 132 | quit: function () { 133 | this.$router.push({name: "home"}); 134 | }, 135 | onGameOver: function (event: any) { 136 | this.gameOver = true; 137 | this.winner = event; 138 | }, 139 | onGameStateUpdate: function (event: any) { 140 | console.log(event) 141 | }, 142 | onGameTimout: function () { 143 | this.gameOver = true; 144 | this.gameTimeout = true; 145 | }, 146 | onError: function (error: string) { 147 | Msgbus.$emit("error", error); 148 | } 149 | }, 150 | computed: {}, 151 | components: { 152 | "gameboard": GamebordComponent, 153 | } 154 | }); 155 | -------------------------------------------------------------------------------- /thor-app/src/renderer/sdk/vm.ts: -------------------------------------------------------------------------------- 1 | //import "@types/webassembly-js-api"; 2 | // this is a shallow wrapper for the assemblyscript loader 3 | import * as as2d from "as2d"; 4 | import * as loader from "assemblyscript/lib/loader"; 5 | import {GameEngine} from "./GameEngine"; 6 | import {GameGUI} from "./GameGUI"; 7 | import {ICanvasSYS} from "as2d/src/util/ICanvasSYS"; 8 | 9 | const env = { 10 | memoryBase: 0, 11 | tableBase: 0, 12 | memory: new WebAssembly.Memory({ 13 | initial: 0 14 | }), 15 | abort(msg: number, file: number, line: number, column: number) { 16 | console.error("abort called at " + file + ":" + line + ":" + column + ", msg:" + msg); 17 | } 18 | }; 19 | 20 | class ASModuleWrapper { 21 | module: loader.ASUtil | null = null; 22 | 23 | init(module: loader.ASUtil): void { 24 | this.module = module; 25 | } 26 | 27 | protected getString = (value: number) => { 28 | if (this.module == null) { 29 | return value; 30 | } else { 31 | return this.module.getString(value); 32 | } 33 | }; 34 | 35 | protected getArray = (type: loader.TypedArrayConstructor, value: number) => { 36 | if (this.module == null) { 37 | return value; 38 | } else { 39 | return this.module.getArray(type, value); 40 | } 41 | }; 42 | } 43 | 44 | //function use array style for keep this ref. 45 | //see https://github.com/Microsoft/TypeScript/wiki/'this'-in-TypeScript 46 | class Console extends ASModuleWrapper { 47 | 48 | public log = (value: number) => { 49 | console.log(this.getString(value)); 50 | }; 51 | public logf = (msg: number, value: number) => { 52 | console.log(this.getString(msg), value) 53 | }; 54 | public logi = (msg: number, value: number) => { 55 | console.log(this.getString(msg), value) 56 | }; 57 | public logAction = (msg: number, player: number, state: number) => { 58 | console.log(this.getString(msg) + " player:", player, this.getArray(Int8Array, state)) 59 | }; 60 | public error = (value: number) => { 61 | alert(this.getString(value)); 62 | }; 63 | } 64 | 65 | class Listener extends ASModuleWrapper { 66 | 67 | public onUpdate = (player: number, state: number) => { 68 | console.log("listener onUpdate", player, this.getArray(Int8Array, state)); 69 | }; 70 | 71 | public onGameOver = (player: number) => { 72 | console.log("listener onGameOver", player); 73 | alert("Game Over Winner is:" + player); 74 | } 75 | } 76 | 77 | 78 | const engineConsole = new Console(); 79 | const guiConsole = new Console(); 80 | const listener = new Listener(); 81 | 82 | let engineModule: loader.ASUtil & GameEngine; 83 | let module: ICanvasSYS & loader.ASUtil & GameGUI; 84 | let modulePromise: Promise; 85 | 86 | export function init(playerRole: number, onStateUpdate: (player: number, fullState: Int8Array, state: Int8Array) => void, engineBuffer: Buffer, guiBuffer: Buffer, onGameOver: (player: number) => void, errorHandler: (error: string) => void = console.error, playWithAI: boolean = false): Promise { 87 | const engineBlob = new Blob([engineBuffer], {type: "application/wasm"}); 88 | const guiBlob = new Blob([guiBuffer], {type: "application/wasm"}); 89 | const engineURL = URL.createObjectURL(engineBlob); 90 | const guiURL = URL.createObjectURL(guiBlob); 91 | 92 | modulePromise = loader.instantiateStreaming(fetch(engineURL), { 93 | env: env, 94 | console: engineConsole, 95 | listener: { 96 | onUpdate: function (player: number, statePointer: number) { 97 | console.log("onUpdate", player, statePointer); 98 | let state: Int8Array = engineModule.getArray(Int8Array, statePointer); 99 | let fullState: Int8Array = engineModule.getArray(Int8Array, engineModule.getState()); 100 | onStateUpdate(player, fullState, state); 101 | }, 102 | onGameOver: function (player: number) { 103 | onGameOver(player); 104 | } 105 | } 106 | }).then(engine => { 107 | engineModule = engine; 108 | engineConsole.init(engine); 109 | listener.init(engine); 110 | engine.init(); 111 | URL.revokeObjectURL(engineURL); 112 | return as2d.instantiateStreaming(fetch(guiURL), { 113 | env: env, console: guiConsole, engine: { 114 | 115 | update(player: number, state: number) { 116 | let pointer = engine.newArray(module.getArray(Int8Array, state)); 117 | return engine.update(player, pointer) 118 | }, 119 | loadState(fullState: number) { 120 | let pointer = engine.newArray(module.getArray(Int8Array, fullState)); 121 | engine.loadState(pointer) 122 | }, 123 | getState() { 124 | return module.newArray(engine.getArray(Int8Array, engine.getState())) 125 | }, 126 | isGameOver() { 127 | return engine.isGameOver() 128 | } 129 | } 130 | }).then(gui => { 131 | module = gui; 132 | guiConsole.init(module); 133 | const canvas = document.querySelector("#as2d"); 134 | const ctx = canvas!.getContext("2d")!; 135 | 136 | ctx.canvas.addEventListener("click", (e: MouseEvent) => { 137 | let rect: ClientRect = (e.target as HTMLCanvasElement).getBoundingClientRect(); 138 | let statePointer = module.onClick(e.clientX - rect.left, e.clientY - rect.top); 139 | let state: Int8Array = module.getArray(Int8Array, statePointer); 140 | if (state.length == 0) { 141 | errorHandler("Not your turn.") 142 | } 143 | }); 144 | 145 | module.useContext("main", ctx); 146 | module.init(playerRole, playWithAI); 147 | module.draw(); 148 | URL.revokeObjectURL(engineURL); 149 | return module; 150 | }); 151 | }); 152 | return modulePromise; 153 | } 154 | 155 | export async function startGame() { 156 | let module = await modulePromise; 157 | module.startGame(); 158 | } 159 | -------------------------------------------------------------------------------- /thor-arbitrate-service/src/test/kotlin/org/starcoin/thor/server/ArbitrateServerTest.kt: -------------------------------------------------------------------------------- 1 | package org.starcoin.thor.server 2 | 3 | import kotlinx.coroutines.delay 4 | import kotlinx.coroutines.runBlocking 5 | import org.jetbrains.kotlin.daemon.common.toHexString 6 | import org.junit.After 7 | import org.junit.Before 8 | import org.junit.Test 9 | import org.starcoin.sirius.serialization.ByteArrayWrapper 10 | import org.starcoin.thor.core.* 11 | import org.starcoin.thor.manager.GameManager 12 | import org.starcoin.thor.manager.RoomManager 13 | import org.starcoin.thor.sign.SignService 14 | import java.io.FileInputStream 15 | import java.nio.file.Files 16 | import java.nio.file.Paths 17 | 18 | class ArbitrateServerTest { 19 | 20 | lateinit var aliceMsgClient: MsgClientServiceImpl 21 | lateinit var bobMsgClient: MsgClientServiceImpl 22 | lateinit var websocketServer: WebsocketServer 23 | 24 | @Before 25 | fun before() { 26 | val gameManager = GameManager() 27 | val roomManager = RoomManager() 28 | loadGames().forEach { game -> 29 | gameManager.createGame(game) 30 | } 31 | websocketServer = WebsocketServer(gameManager, roomManager) 32 | websocketServer.start(false) 33 | aliceMsgClient = newClientUser("/tmp/thor/lnd/lnd_alice/tls.cert", "localhost", 10009, "/tmp/thor/lnd/lnd_alice/data/chain/bitcoin/simnet/admin.macaroon") 34 | bobMsgClient = newClientUser("/tmp/thor/lnd/lnd_bob/tls.cert", "localhost", 20009, "/tmp/thor/lnd/lnd_bob/data/chain/bitcoin/simnet/admin.macaroon") 35 | } 36 | 37 | private fun newClientUser(fileName: String, host: String, port: Int, macarron: String): MsgClientServiceImpl { 38 | val keyPair = SignService.generateKeyPair() 39 | val userInfo = UserInfo(keyPair.public) 40 | val cert = FileInputStream(fileName) 41 | val path = Paths.get(macarron) 42 | val data = Files.readAllBytes(path).toHexString() 43 | val config = LnConfig(cert, host, port, data) 44 | val clientUser = ClientUser(UserSelf(keyPair.private, userInfo), config) 45 | val msgClientService = MsgClientServiceImpl(clientUser) 46 | msgClientService.start() 47 | 48 | return msgClientService 49 | } 50 | 51 | @Test 52 | fun testSendMsg() { 53 | val resp = aliceMsgClient.queryGameList() 54 | runBlocking { 55 | delay(1000) 56 | } 57 | aliceMsgClient.createRoom(resp!!.data!![0].hash, "test-1-room", 1) 58 | val room = MsgObject.fromJson(aliceMsgClient.channelMsg(), Room::class) 59 | runBlocking { 60 | delay(1000) 61 | } 62 | aliceMsgClient.joinRoom(room.roomId) 63 | 64 | runBlocking { 65 | delay(1000) 66 | } 67 | bobMsgClient.joinRoom(room.roomId) 68 | 69 | runBlocking { 70 | delay(1000) 71 | } 72 | 73 | aliceMsgClient.doReady(room.roomId, true) 74 | bobMsgClient.doReady(room.roomId, true) 75 | 76 | aliceMsgClient.roomMsg(room.roomId, "test1 msg") 77 | } 78 | 79 | @Test 80 | fun testGame() { 81 | val gameListResp = aliceMsgClient.queryGameList() 82 | runBlocking { 83 | delay(1000) 84 | } 85 | val datas = mutableListOf() 86 | gameListResp?.let { 87 | aliceMsgClient.doCreateRoom(gameListResp.data!![0].hash, "test-2-room", 1) 88 | 89 | runBlocking { 90 | delay(1000) 91 | } 92 | 93 | val aliceJson = aliceMsgClient.channelMsg() 94 | val aliceRoom = MsgObject.fromJson(aliceJson, Room::class) 95 | 96 | aliceMsgClient.joinRoom(aliceRoom.roomId) 97 | aliceMsgClient.channelMsg() 98 | bobMsgClient.joinRoom(aliceRoom.roomId) 99 | 100 | val bobJson = bobMsgClient.channelMsg() 101 | val bobRoom = MsgObject.fromJson(bobJson, Room::class) 102 | 103 | println("wait begin") 104 | runBlocking { 105 | delay(10000) 106 | } 107 | println("wait end") 108 | aliceMsgClient.doReady(aliceRoom.roomId, false) 109 | bobMsgClient.doReady(bobRoom.roomId, false) 110 | 111 | runBlocking { 112 | delay(10000) 113 | } 114 | 115 | aliceMsgClient.roomMsg(aliceRoom.roomId, "test2 msg") 116 | 117 | val resp = aliceMsgClient.queryRoomList(gameListResp.data!![0].hash) 118 | println(resp!!.toJson()) 119 | 120 | runBlocking { 121 | delay(1000) 122 | } 123 | 124 | val leave = java.util.Random().nextBoolean() 125 | if (leave) { 126 | bobMsgClient.doLeaveRoom(bobRoom.roomId) 127 | } else { 128 | 129 | val wd = WitnessData(bobMsgClient.clientUser.self.userInfo.id, ByteArrayWrapper("stateHash".toByteArray()), SignService.sign(longToBytes(System.currentTimeMillis()), bobMsgClient.priKey()), ByteArrayWrapper("test game msg".toByteArray())) 130 | bobMsgClient.doRoomGameDataReq(bobRoom.roomId, wd) 131 | datas.add(wd) 132 | runBlocking { 133 | delay(10000) 134 | } 135 | 136 | val challengeFlag = java.util.Random().nextBoolean() 137 | if (challengeFlag) { 138 | aliceMsgClient.doSurrenderReq(aliceRoom.roomId) 139 | runBlocking { 140 | delay(1000) 141 | } 142 | assert(bobMsgClient.hasR()) 143 | } else 144 | bobMsgClient.doChallenge(datas) 145 | } 146 | } 147 | 148 | println("test end") 149 | } 150 | 151 | @After 152 | fun after() { 153 | aliceMsgClient.stop() 154 | bobMsgClient.stop() 155 | websocketServer.stop() 156 | } 157 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /thor-app/src/renderer/index.ts: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import VueRouter from "vue-router"; 3 | import Vuetify from 'vuetify' 4 | import 'vuetify/dist/vuetify.min.css'; 5 | import 'material-design-icons/iconfont/material-icons.css' 6 | import 'typeface-roboto/index.css' 7 | import GamelobbyComponent from "./components/Gamelobby"; 8 | import GameroomComponent from "./components/Gameroom"; 9 | import WalletComponent from "./components/Wallet"; 10 | import ConfigComponent from "./components/Config"; 11 | import SoloComponent from "./components/Solo"; 12 | import DebugComponent from "./components/Debug"; 13 | import Msgbus from "./components/Msgbus"; 14 | import * as client from "./sdk/client"; 15 | import {WSMsgType} from "./sdk/client"; 16 | 17 | import colors from 'vuetify/es5/util/colors' 18 | 19 | Vue.use(VueRouter); 20 | Vue.use(Vuetify, { 21 | theme: { 22 | primary: colors.indigo.base, 23 | secondary: colors.lightBlue.base, 24 | accent: colors.lightGreen.base, 25 | error: colors.red.base, 26 | warning: colors.orange.base, 27 | info: colors.grey.base, 28 | success: colors.green.base 29 | } 30 | }); 31 | 32 | const routes = [ 33 | {name: "home", path: '/', component: GamelobbyComponent}, 34 | {name: "lobby", path: '/lobby', component: GamelobbyComponent}, 35 | {name: "config", path: '/config', component: ConfigComponent}, 36 | {name: "wallet", path: '/wallet', component: WalletComponent}, 37 | {name: "room", path: '/room/:roomId', component: GameroomComponent, props: true}, 38 | {name: "solo", path: '/solo/:gameId', component: SoloComponent, props: true}, 39 | {name: "debug", path: '/debug', component: DebugComponent, props: true}, 40 | ]; 41 | 42 | const router = new VueRouter({ 43 | routes 44 | }); 45 | 46 | 47 | const app = new Vue({ 48 | template: ` 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | refresh 57 | 58 | 59 | 60 | 61 | 62 | 63 | Thor App 64 | 65 | 66 | Home 67 | Wallet 68 | Config 69 | Debug 70 | 71 | refresh 72 | 73 | 74 | 75 | 76 | 77 | 83 | {{ message }} 84 | 90 | close 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | @Thor 102 | 103 |
address:{{address}}   location:{{location}} 104 |
105 |
106 |
107 |
108 | 109 |
110 |
111 | `, 112 | router, 113 | data() { 114 | return { 115 | loading: false, 116 | message: "", 117 | color: "", 118 | showMessage: false, 119 | isDevelopment: process.env.NODE_ENV !== 'production' 120 | } 121 | }, 122 | created() { 123 | console.log("app create", this.$refs); 124 | Msgbus.init(); 125 | this.initGlobalEventsHander(); 126 | }, 127 | watch: {}, 128 | methods: { 129 | initGlobalEventsHander: function () { 130 | let self = this; 131 | Msgbus.$on(WSMsgType[WSMsgType.JOIN_ROOM_RESP], function (event: any) { 132 | console.log("handle JOIN_ROOM_RESP event", event); 133 | if (event.succ) { 134 | let room = event.room; 135 | self.$router.push({name: 'room', params: {roomId: room.roomId}}) 136 | } 137 | }); 138 | Msgbus.$on(WSMsgType[WSMsgType.CREATE_ROOM_RESP], function (event: any) { 139 | console.log("handle CREATE_ROOM_RESP event", event); 140 | }); 141 | Msgbus.$on(WSMsgType[WSMsgType.ERR], function (event: any) { 142 | Msgbus.$emit("error", "server error:" + event.err); 143 | if (event.code == 404) { 144 | self.$router.push({name: "home"}); 145 | } 146 | }); 147 | Msgbus.$on("loading", function (loading: any) { 148 | self.loading = loading; 149 | }); 150 | Msgbus.$on("error", function (message: any) { 151 | self.message = message; 152 | self.color = "error"; 153 | self.showMessage = true; 154 | }); 155 | Msgbus.$on("info", function (message: any) { 156 | self.color = "info"; 157 | self.message = message; 158 | self.showMessage = true; 159 | }); 160 | Msgbus.$on("success", function (message: any) { 161 | self.color = "success"; 162 | self.message = message; 163 | self.showMessage = true; 164 | }) 165 | }, 166 | refresh: function () { 167 | Msgbus.$emit("refresh"); 168 | } 169 | }, 170 | computed: { 171 | location: function () { 172 | return window.location; 173 | }, 174 | address: function () { 175 | return client.getMyAddress(); 176 | } 177 | }, 178 | components: {} 179 | }); 180 | app.$mount('#app'); 181 | -------------------------------------------------------------------------------- /lightning/src/main/java/org/starcoin/lightning/client/SyncClient.java: -------------------------------------------------------------------------------- 1 | package org.starcoin.lightning.client; 2 | 3 | import com.google.protobuf.ByteString; 4 | import io.grpc.Channel; 5 | import java.util.List; 6 | import java.util.concurrent.TimeUnit; 7 | import java.util.logging.Logger; 8 | import java.util.stream.Collectors; 9 | 10 | import org.starcoin.lightning.client.core.*; 11 | import org.starcoin.lightning.proto.InvoicesGrpc; 12 | import org.starcoin.lightning.proto.InvoicesOuterClass; 13 | import org.starcoin.lightning.proto.LightningGrpc; 14 | import org.starcoin.lightning.proto.LightningOuterClass; 15 | import org.starcoin.lightning.proto.LightningOuterClass.GetInfoRequest; 16 | import org.starcoin.lightning.proto.LightningOuterClass.GetInfoResponse; 17 | import org.starcoin.lightning.proto.LightningOuterClass.ListInvoiceResponse; 18 | import org.starcoin.lightning.proto.LightningOuterClass.WalletBalanceRequest; 19 | import org.starcoin.lightning.proto.LightningOuterClass.WalletBalanceResponse; 20 | 21 | public class SyncClient { 22 | 23 | private Logger logger = Logger.getLogger(SyncClient.class.getName()); 24 | private Channel channel; 25 | 26 | private int deadlineMs = 60 * 1000; 27 | 28 | public SyncClient(Channel channel) { 29 | this.channel = channel; 30 | } 31 | 32 | public AddInvoiceResponse addInvoice(Invoice invoice) { 33 | LightningGrpc.LightningBlockingStub stub = LightningGrpc.newBlockingStub(channel); 34 | LightningOuterClass.Invoice invoiceProto = invoice.toProto(); 35 | logger.info(invoiceProto.toString()); 36 | LightningOuterClass.AddInvoiceResponse response = 37 | stub.withDeadlineAfter(deadlineMs, TimeUnit.MILLISECONDS).addInvoice(invoiceProto); 38 | logger.info(response.toString()); 39 | return AddInvoiceResponse.copyFrom(response); 40 | } 41 | 42 | public PaymentResponse sendPayment(Payment payment) { 43 | LightningGrpc.LightningBlockingStub stub = LightningGrpc.newBlockingStub(channel); 44 | LightningOuterClass.SendResponse response = 45 | stub.withDeadlineAfter(deadlineMs, TimeUnit.MILLISECONDS) 46 | .sendPaymentSync(payment.toProto()); 47 | logger.info(response.toString()); 48 | return PaymentResponse.copyFrom(response); 49 | } 50 | 51 | public InvoiceList listInvoices(long offset, long count, boolean pendingOnly, boolean reversed) { 52 | LightningGrpc.LightningBlockingStub stub = LightningGrpc.newBlockingStub(channel); 53 | LightningOuterClass.ListInvoiceRequest.Builder requestBuilder = 54 | LightningOuterClass.ListInvoiceRequest.newBuilder(); 55 | requestBuilder.setNumMaxInvoices(count); 56 | requestBuilder.setIndexOffset(offset); 57 | requestBuilder.setReversed(reversed); 58 | requestBuilder.setPendingOnly(pendingOnly); 59 | ListInvoiceResponse response = 60 | stub.withDeadlineAfter(deadlineMs, TimeUnit.MILLISECONDS) 61 | .listInvoices(requestBuilder.build()); 62 | logger.info(response.toString()); 63 | return InvoiceList.copyFrom(response); 64 | } 65 | 66 | public Invoice lookupInvoice(String rHash) { 67 | LightningGrpc.LightningBlockingStub stub = LightningGrpc.newBlockingStub(channel); 68 | LightningOuterClass.PaymentHash.Builder requestBuilder = 69 | LightningOuterClass.PaymentHash.newBuilder(); 70 | requestBuilder.setRHashStr(rHash); 71 | LightningOuterClass.Invoice invoice = 72 | stub.withDeadlineAfter(deadlineMs, TimeUnit.MILLISECONDS) 73 | .lookupInvoice(requestBuilder.build()); 74 | logger.info(invoice.toString()); 75 | return Invoice.copyFrom(invoice); 76 | } 77 | 78 | public PayReq decodePayReq(String requestString) { 79 | LightningGrpc.LightningBlockingStub stub = LightningGrpc.newBlockingStub(channel); 80 | LightningOuterClass.PayReqString.Builder requestBuilder = 81 | LightningOuterClass.PayReqString.newBuilder(); 82 | requestBuilder.setPayReq(requestString); 83 | LightningOuterClass.PayReq payReq = 84 | stub.withDeadlineAfter(deadlineMs, TimeUnit.MILLISECONDS) 85 | .decodePayReq(requestBuilder.build()); 86 | logger.info(payReq.toString()); 87 | return PayReq.copyFrom(payReq); 88 | } 89 | 90 | public List listChannels(org.starcoin.lightning.client.core.Channel channel) { 91 | LightningGrpc.LightningBlockingStub stub = LightningGrpc.newBlockingStub(this.channel); 92 | return stub.withDeadlineAfter(deadlineMs, TimeUnit.MILLISECONDS).listChannels(channel.toProto()) 93 | .getChannelsList().stream() 94 | .map(c -> new ChannelResponse(c)) 95 | .collect(Collectors.toList()); 96 | } 97 | 98 | public String getIdentityPubkey() { 99 | LightningGrpc.LightningBlockingStub stub = LightningGrpc.newBlockingStub(this.channel); 100 | GetInfoResponse resp = 101 | stub.withDeadlineAfter(deadlineMs, TimeUnit.MILLISECONDS) 102 | .getInfo(GetInfoRequest.newBuilder().build()); 103 | return resp.getIdentityPubkey(); 104 | } 105 | 106 | public void settleInvoice(SettleInvoiceRequest request) { 107 | InvoicesGrpc.InvoicesBlockingStub stub = InvoicesGrpc.newBlockingStub(this.channel); 108 | logger.info( 109 | stub.withDeadlineAfter(deadlineMs, TimeUnit.MILLISECONDS) 110 | .settleInvoice(request.toProto()) 111 | .toString()); 112 | } 113 | 114 | public void cancelInvoice(byte[] paymentHash) { 115 | InvoicesGrpc.InvoicesBlockingStub stub = InvoicesGrpc.newBlockingStub(this.channel); 116 | InvoicesOuterClass.CancelInvoiceMsg.Builder builder = 117 | InvoicesOuterClass.CancelInvoiceMsg.newBuilder(); 118 | builder.setPaymentHash(ByteString.copyFrom(paymentHash)); 119 | InvoicesOuterClass.CancelInvoiceResp resp = 120 | stub.withDeadlineAfter(deadlineMs, TimeUnit.MILLISECONDS).cancelInvoice(builder.build()); 121 | logger.info(resp.toString()); 122 | } 123 | 124 | public org.starcoin.lightning.client.core.WalletBalanceResponse walletBalance() { 125 | LightningGrpc.LightningBlockingStub stub = LightningGrpc.newBlockingStub(this.channel); 126 | WalletBalanceResponse response = 127 | stub.withDeadlineAfter(deadlineMs, TimeUnit.MILLISECONDS) 128 | .walletBalance(WalletBalanceRequest.newBuilder().build()); 129 | logger.info(response.toString()); 130 | return org.starcoin.lightning.client.core.WalletBalanceResponse.copyFrom(response); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /thor-app/src/renderer/components/Gamelobby.ts: -------------------------------------------------------------------------------- 1 | import Vue from "vue"; 2 | import * as client from "../sdk/client"; 3 | import {Room} from "../sdk/client"; 4 | import Msgbus, {errorHandler} from "./Msgbus"; 5 | import * as lightning from "../sdk/lightning"; 6 | import util from "../sdk/util"; 7 | 8 | export default Vue.extend({ 9 | template: ` 10 | 11 | 12 | 13 | Game List 14 | 15 | 16 | 17 | 23 | 24 | 25 | id:{{game.hash}} 26 | 27 | 28 | add Room 29 | 30 | 31 | play_arrow with AI 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | Room List 40 | 41 | 42 | 43 | 49 | 50 | attach_money 51 | money_off 52 | 53 | 54 | 55 | game:{{getGame(room.gameId).gameName}} 56 | 57 | 58 | 59 | 60 | 63 | Join Room 64 | 65 | 66 | 67 | 68 | No Room. Please create a new Room. 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | Create Room for Game {{createRoomGame.gameName}} 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | Create 86 | Close 87 | 88 | 89 | 90 | 91 | 92 | `, 93 | data() { 94 | return { 95 | createRoomGame: null, 96 | roomCost: 0, 97 | roomName: "", 98 | roomTimeout: 60, 99 | gameList: [], 100 | roomList: [], 101 | myAddress: client.getMyAddress() 102 | } 103 | }, 104 | created() { 105 | this.init(); 106 | }, 107 | watch: {}, 108 | methods: { 109 | init: function () { 110 | Msgbus.$on("refresh", () => { 111 | this.refresh(); 112 | }); 113 | this.refresh(); 114 | }, 115 | showCreateRoom: function (game: any) { 116 | this.createRoomGame = game; 117 | this.roomName = game.gameName + '-' + Math.random().toString(36).substr(2, 3); 118 | }, 119 | createRoom: function () { 120 | // @ts-ignore 121 | client.createRoom(this.createRoomGame!.hash, this.roomName, this.roomCost, this.roomTimeout); 122 | this.createRoomGame = null; 123 | setTimeout(() => this.fetchRoomList(), 500); 124 | 125 | }, 126 | fetchGameList: function () { 127 | Msgbus.$emit("loading", true); 128 | return client.gameList().then(resp => { 129 | this.gameList = resp.data; 130 | Msgbus.$emit("loading", false); 131 | return resp; 132 | }).catch(errorHandler); 133 | }, 134 | fetchRoomList: function () { 135 | this.roomList = []; 136 | Msgbus.$emit("loading", true); 137 | client.roomList().then(resp => { 138 | resp.data.forEach((jsonObj: any) => { 139 | // @ts-ignore 140 | this.roomList.push(util.unmarshal(new Room(), jsonObj)) 141 | }); 142 | Msgbus.$emit("loading", false); 143 | return resp; 144 | }).catch(errorHandler); 145 | }, 146 | refresh: function () { 147 | this.fetchGameList().then(() => { 148 | this.fetchRoomList(); 149 | }); 150 | }, 151 | getGame(gameId: string): any { 152 | return this.gameList.find((value: any) => value.hash == gameId); 153 | }, 154 | getRoom(roomId: string): any { 155 | return this.roomList.find((value: any) => value.roomId == roomId) 156 | }, 157 | joinRoom: function (roomId: string) { 158 | let room = this.getRoom(roomId); 159 | if (room && !room.isFree()) { 160 | //check lnd config 161 | if (!lightning.hasInit()) { 162 | Msgbus.$emit("error", "Please config lightning network first."); 163 | this.$router.push({name: "config"}); 164 | return 165 | } 166 | } 167 | if (room && room.players.find((value: any) => value.playerUserId == this.myAddress)) { 168 | this.$router.push({name: 'room', params: {roomId: room.roomId}}) 169 | } else { 170 | client.joinRoom(roomId); 171 | } 172 | }, 173 | soloPlay(gameId: string) { 174 | this.$router.push({name: 'solo', params: {gameId: gameId}}) 175 | } 176 | } 177 | }); 178 | -------------------------------------------------------------------------------- /lightning/src/main/java/org/starcoin/lightning/client/Bech32.java: -------------------------------------------------------------------------------- 1 | package org.starcoin.lightning.client; 2 | 3 | import com.google.common.primitives.Bytes; 4 | import java.io.ByteArrayOutputStream; 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | import java.util.Locale; 9 | 10 | public class Bech32 { 11 | /** The Bech32 character set for encoding. */ 12 | private static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; 13 | 14 | /** The Bech32 character set for decoding. */ 15 | private static final byte[] CHARSET_REV = { 16 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 17 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 18 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 19 | 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, 20 | -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 21 | 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, 22 | -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 23 | 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1 24 | }; 25 | 26 | public static class Bech32Data { 27 | public final String hrp; 28 | public final byte[] data; 29 | 30 | private Bech32Data(final String hrp, final byte[] data) { 31 | this.hrp = hrp; 32 | this.data = data; 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return "Bech32Data{" + 38 | "hrp='" + hrp + '\'' + 39 | ", data=" + Arrays.toString(data) + 40 | '}'; 41 | } 42 | } 43 | 44 | /** Find the polynomial with value coefficients mod the generator as 30-bit. */ 45 | private static int polymod(final byte[] values) { 46 | int c = 1; 47 | for (byte v_i: values) { 48 | int c0 = (c >>> 25) & 0xff; 49 | c = ((c & 0x1ffffff) << 5) ^ (v_i & 0xff); 50 | if ((c0 & 1) != 0) c ^= 0x3b6a57b2; 51 | if ((c0 & 2) != 0) c ^= 0x26508e6d; 52 | if ((c0 & 4) != 0) c ^= 0x1ea119fa; 53 | if ((c0 & 8) != 0) c ^= 0x3d4233dd; 54 | if ((c0 & 16) != 0) c ^= 0x2a1462b3; 55 | } 56 | return c; 57 | } 58 | 59 | /** Expand a HRP for use in checksum computation. */ 60 | private static byte[] expandHrp(final String hrp) { 61 | int hrpLength = hrp.length(); 62 | byte ret[] = new byte[hrpLength * 2 + 1]; 63 | for (int i = 0; i < hrpLength; ++i) { 64 | int c = hrp.charAt(i) & 0x7f; // Limit to standard 7-bit ASCII 65 | ret[i] = (byte) ((c >>> 5) & 0x07); 66 | ret[i + hrpLength + 1] = (byte) (c & 0x1f); 67 | } 68 | ret[hrpLength] = 0; 69 | return ret; 70 | } 71 | 72 | /** Verify a checksum. */ 73 | private static boolean verifyChecksum(final String hrp, final byte[] values) { 74 | byte[] hrpExpanded = expandHrp(hrp); 75 | byte[] combined = new byte[hrpExpanded.length + values.length]; 76 | System.arraycopy(hrpExpanded, 0, combined, 0, hrpExpanded.length); 77 | System.arraycopy(values, 0, combined, hrpExpanded.length, values.length); 78 | return polymod(combined) == 1; 79 | } 80 | 81 | /** Create a checksum. */ 82 | private static byte[] createChecksum(final String hrp, final byte[] values) { 83 | byte[] hrpExpanded = expandHrp(hrp); 84 | byte[] enc = new byte[hrpExpanded.length + values.length + 6]; 85 | System.arraycopy(hrpExpanded, 0, enc, 0, hrpExpanded.length); 86 | System.arraycopy(values, 0, enc, hrpExpanded.length, values.length); 87 | int mod = polymod(enc) ^ 1; 88 | byte[] ret = new byte[6]; 89 | for (int i = 0; i < 6; ++i) { 90 | ret[i] = (byte) ((mod >>> (5 * (5 - i))) & 31); 91 | } 92 | return ret; 93 | } 94 | 95 | /** Encode a Bech32 string. */ 96 | public static String encode(final Bech32Data bech32) { 97 | return encode(bech32.hrp, bech32.data); 98 | } 99 | 100 | /** Encode a Bech32 string. */ 101 | public static String encode(String hrp, final byte[] values) { 102 | if(hrp.length() >= 83 ){ 103 | throw new IllegalArgumentException("Human-readable part is too long"); 104 | } 105 | if(hrp.length() <1){ 106 | throw new IllegalArgumentException("Human-readable part is too short"); 107 | } 108 | 109 | hrp = hrp.toLowerCase(Locale.ROOT); 110 | byte[] checksum = createChecksum(hrp, values); 111 | byte[] combined = new byte[values.length + checksum.length]; 112 | System.arraycopy(values, 0, combined, 0, values.length); 113 | System.arraycopy(checksum, 0, combined, values.length, checksum.length); 114 | StringBuilder sb = new StringBuilder(hrp.length() + 1 + combined.length); 115 | sb.append(hrp); 116 | sb.append('1'); 117 | for (byte b : combined) { 118 | sb.append(CHARSET.charAt(b)); 119 | } 120 | return sb.toString(); 121 | } 122 | 123 | /** Decode a Bech32 string. */ 124 | public static Bech32Data decode(final String str) throws AddressFormatException { 125 | boolean lower = false, upper = false; 126 | if (str.length() < 8) 127 | throw new AddressFormatException.InvalidDataLength("Input too short: " + str.length()); 128 | //if (str.length() > 90) 129 | // throw new AddressFormatException.InvalidDataLength("Input too long: " + str.length()); 130 | for (int i = 0; i < str.length(); ++i) { 131 | char c = str.charAt(i); 132 | if (c < 33 || c > 126) throw new AddressFormatException.InvalidCharacter(c, i); 133 | if (c >= 'a' && c <= 'z') { 134 | if (upper) 135 | throw new AddressFormatException.InvalidCharacter(c, i); 136 | lower = true; 137 | } 138 | if (c >= 'A' && c <= 'Z') { 139 | if (lower) 140 | throw new AddressFormatException.InvalidCharacter(c, i); 141 | upper = true; 142 | } 143 | } 144 | final int pos = str.lastIndexOf('1'); 145 | if (pos < 1) throw new AddressFormatException.InvalidPrefix("Missing human-readable part"); 146 | final int dataPartLength = str.length() - 1 - pos; 147 | if (dataPartLength < 6) throw new AddressFormatException.InvalidDataLength("Data part too short: " + dataPartLength); 148 | byte[] values = new byte[dataPartLength]; 149 | for (int i = 0; i < dataPartLength; ++i) { 150 | char c = str.charAt(i + pos + 1); 151 | if (CHARSET_REV[c] == -1) throw new AddressFormatException.InvalidCharacter(c, i + pos + 1); 152 | values[i] = CHARSET_REV[c]; 153 | } 154 | String hrp = str.substring(0, pos).toLowerCase(Locale.ROOT); 155 | if (!verifyChecksum(hrp, values)) throw new AddressFormatException.InvalidChecksum(); 156 | return new Bech32Data(hrp, Arrays.copyOfRange(values, 0, values.length - 6)); 157 | } 158 | 159 | static byte[] convertBits(byte[] data, int fromBits, int toBits, boolean pad) { 160 | int acc = 0; 161 | int bits = 0; 162 | int maxv = (1 << toBits) - 1; 163 | List ret = new ArrayList<>(); 164 | 165 | for(Byte value : data) { 166 | 167 | short b = (short)(value.byteValue() & 0xff); 168 | 169 | if (b < 0) { 170 | throw new IllegalArgumentException(); 171 | } 172 | else if ((b >> fromBits) > 0) { 173 | throw new IllegalArgumentException(); 174 | } 175 | 176 | acc = (acc << fromBits) | b; 177 | bits += fromBits; 178 | while (bits >= toBits) { 179 | bits -= toBits; 180 | ret.add((byte)((acc >> bits) & maxv)); 181 | } 182 | } 183 | 184 | if(pad && (bits > 0)) { 185 | ret.add((byte)((acc << (toBits - bits)) & maxv)); 186 | } 187 | else if (bits >= fromBits || (byte)(((acc << (toBits - bits)) & maxv)) != 0) { 188 | throw new IllegalArgumentException("panic"); 189 | } 190 | 191 | 192 | return Bytes.toArray(ret); 193 | } 194 | } 195 | --------------------------------------------------------------------------------